protected virtual void UpdateForeignKeyFields(object child) { if (listType != GentleListType.StandAlone && mappings != null) { foreach (FieldMap cfm in mappings.Keys) { FieldMap pfm = mappings[cfm] as FieldMap; cfm.SetValue(child, pfm.GetValue(parent)); } } }
/// <summary> /// Obtain a key for the specified object instance and property names. The returned /// key will contain the corresponding column names for the type, and thus foreign /// key columns must use identical naming for this to work. /// </summary> /// <param name="broker">The optional PersistenceBroker instance to use for obtaining the /// ObjectMap for the supplied object instance. If this parameter is null, Gentle will try /// to infer the broker from the object instance. If that fails, it will use the default /// provider.</param> /// <param name="key">An optional existing key to add the values to</param> /// <param name="isPropertyKeys">False is key indexers are column names, true for property names</param> /// <param name="instance">The object instance whose property values will be used</param> /// <param name="members">The names of the properties to include in the key</param> /// <returns>The key</returns> public static Key GetKey(PersistenceBroker broker, Key key, bool isPropertyKeys, object instance, params string[] members) { Check.VerifyNotNull(instance, Error.NullParameter, "instance"); // try to infer broker from instance if (broker == null && instance is IBrokerLock) { broker = (instance as IBrokerLock).SessionBroker; } // WARNING/TODO if broker is null here and no ObjectMap yet exists for the type, // the DefaultProvider will be used to create the ObjectMap ObjectMap map = ObjectFactory.GetMap(broker, instance); // only set source type reference if this is a new key if (key == null) { key = new Key(map.GetTableName(instance), instance.GetType(), isPropertyKeys); } //else // Check.Verify( ! key.isPropertyKeys, Error.DeveloperError, // "Unable to combine keys containing property names due to possible name clashes." ); Check.VerifyEquals(key.isPropertyKeys, isPropertyKeys, "Cannot combine property and " + "column names in a single key - use one or the other."); Check.VerifyNotNull(members, Error.NullParameter, "members"); // process the list of specified properties foreach (string memberName in members) { FieldMap fm = isPropertyKeys ? map.GetFieldMap(memberName) : map.GetFieldMapFromColumn(memberName); Check.VerifyNotNull(fm, Error.NoProperty, map.Type, memberName); // FIXME outdated error message object memberValue = fm.GetValue(instance); // translate foreign references to local names if (key.SourceType != map.Type) { // WARNING/TODO if broker is null here and no ObjectMap yet exists for the type, // the DefaultProvider will be used to create the ObjectMap ObjectMap keyMap = ObjectFactory.GetMap(broker, key.SourceType); fm = keyMap.GetForeignKeyFieldMap(map.TableName, fm.ColumnName); } key[isPropertyKeys ? fm.MemberName : fm.ColumnName] = memberValue; } // add concurrency value if enabled and instance has revision column if (GentleSettings.ConcurrencyControl && map.ConcurrencyMap != null) { long version = Convert.ToInt64(map.ConcurrencyMap.GetValue(instance)); key[isPropertyKeys ? map.ConcurrencyMap.MemberName : map.ConcurrencyMap.ColumnName] = version; } return(key); }
/// <summary> /// Internal helper method used for standard CRUD operations on known types. /// </summary> /// <param name="obj">The object instance being operated on</param> /// <param name="st">The statement type</param> /// <param name="conn">An existing database connection to reuse. This is useful /// when you need to execute statements in the same session as a previous statement.</param> /// <param name="tr">The database transaction for when participating in transactions.</param> /// <returns>The result of the operation</returns> internal SqlResult Execute(object obj, StatementType st, IDbConnection conn, IDbTransaction tr) { ObjectMap map = ObjectFactory.GetMap(this, obj); // perform validations first if (StatementType.Insert == st || StatementType.Update == st) { ValidationBroker.Validate(obj); } if (map.IsSoftDelete && st == StatementType.Delete) { st = StatementType.SoftDelete; } SqlStatement stmt = GetStatement(obj, st); stmt.SetParameters(obj, true); // connections are supplied from outside when in a transaction or executing batch queries conn = tr != null ? tr.Connection : conn ?? stmt.SessionBroker.Provider.GetConnection(); SqlResult sr = stmt.Execute(conn, tr); // throw an error if execution failed Check.Verify(sr.ErrorCode == 0, Error.StatementError, sr.Error, stmt.Sql); // check that statement affected a row if (st == StatementType.Insert || st == StatementType.Update || st == StatementType.Delete || st == StatementType.SoftDelete) { Check.Verify(sr.RowsAffected >= 1, Error.UnexpectedRowCount, sr.RowsAffected, "1+"); } // update identity values for inserts if (st == StatementType.Insert) { if (sr.LastRowId != 0 && map.IdentityMap != null) { map.SetIdentity(obj, sr.LastRowId); } if (obj is IEntity) { (obj as IEntity).IsPersisted = true; } // this is not really necessary but nice for error checking (also used in test cases) if (map.InheritanceMap != null) { map.InheritanceMap.SetValue(obj, obj.GetType().AssemblyQualifiedName); } } // update/invalidate the cache if (GentleSettings.CacheObjects && map.CacheStrategy != CacheStrategy.Never) { if (st == StatementType.Insert) { // insert new object into cache CacheManager.Insert(map.GetInstanceHashKey(obj), obj, map.CacheStrategy); // invalidate query results for select statements for this type, reducing the // effectiveness of the cache (but ensuring correct results) if (GentleSettings.CacheStatements) { CacheManager.ClearQueryResultsByType(map.Type); } } else if (st == StatementType.Delete || st == StatementType.SoftDelete) { // remove invalidated object from the cache CacheManager.Remove(obj); // invalidate query results for select statements for this type, reducing the // effectiveness of the cache (but ensuring correct results) if (GentleSettings.CacheStatements) { CacheManager.ClearQueryResultsByType(map.Type); } } } // update the in-memory version/revision counter for objects under concurrency control if (st == StatementType.Update && GentleSettings.ConcurrencyControl) { if (map.ConcurrencyMap != null) { FieldMap fm = map.ConcurrencyMap; long version = Convert.ToInt64(fm.GetValue(obj)); // handle wrap-around of the version counter if ((fm.Type.Equals(typeof(int)) && version == int.MaxValue) || (fm.Type.Equals(typeof(long)) && version == long.MaxValue)) { version = 1; } else { version += 1; } map.ConcurrencyMap.SetValue(obj, version); } } // update object with database-created values if UpdateAfterWrite is set to true if (map.IsUpdateAfterWrite && (st == StatementType.Insert || st == StatementType.Update)) { if (tr != null) { Refresh(obj, tr); } else { Refresh(obj); } } return(sr); }
protected virtual void InitList(Type viaType, params Type[] relationTypes) { if (listType == GentleListType.StandAlone) { broker.RetrieveList(containedMap.Type, this); } else if (listType == GentleListType.OneToMany) { // no relation objects for 1:n relationships viaInstances = null; mappings = containedMap.GetForeignKeyMappings(parentMap, true); if (mappings.Count == 0) { mappings = viaMap.GetForeignKeyMappings(parentMap, false); } Check.Verify(mappings.Count > 0, Error.DeveloperError, "The type {0} does not contain a foreign key reference to type {1}.", parentMap.Type, containedMap.Type); Check.Verify(mappings.Count == 1, Error.NotImplemented, "GentleList for 1:n relations can not be used with composite keys."); // populate self with any existing entries matching the current parent Key key = new Key(parentMap.Type, true); IDictionaryEnumerator iterator = mappings.GetEnumerator(); while (iterator.MoveNext()) { // construct a key to read the data; first obtain the referenced value from // the parent object (this is the constraint value used in the select) FieldMap fm = iterator.Value as FieldMap; object referencedValue = fm.GetValue(parent); // if class references self make sure to pick the outgoing column if (containedMap.Type == parentMap.Type && !fm.IsForeignKey) { fm = iterator.Key as FieldMap; } key[fm.MemberName] = referencedValue; } broker.RetrieveList(containedMap.Type, key, this); } else if (listType == GentleListType.ManyToMany) { // create relation for n:m management Type[] relatedTypes = Merge(containedMap.Type, relationTypes); viaInstances = new GentleRelation(broker, viaType, parent, relatedTypes); // populate the list with any existing entries matching the current relation entries ObjectMap viaMap = ObjectFactory.GetMap(broker, viaType); SqlBuilder sb = new SqlBuilder(broker, StatementType.Select, containedMap.Type); // assume the relation object is the child, i.e. refers to the contained type mappings = viaMap.GetForeignKeyMappings(containedMap, true); if (mappings.Count == 0) { mappings = viaMap.GetForeignKeyMappings(containedMap, false); } Check.Verify(mappings.Count > 0, Error.DeveloperError, "The type {0} does not contain a foreign key reference to type {1}.", viaMap.Type, containedMap.Type); Check.Verify(mappings.Count == 1, Error.NotImplemented, "GentleList for n:m relations can not be used with composite keys."); // verify that references point to unique instance //Check.Verify( mappings.Count == parentMap.PrimaryKeyCount, Error.DeveloperError, // "The number of fields ({0}) referencing {1} from {2} must equal the primary key count ({3}).", // mappings.Count, parentMap.Type, containedMap.Type, parentMap.PrimaryKeyCount ); if (viaInstances.Count > 0) { foreach (FieldMap remote in mappings.Keys) { FieldMap local = (FieldMap)mappings[remote]; // viaMap.GetForeignKeyFieldMap( containedMap.Type, local.PropertyName ); sb.AddConstraint(Operator.In, local.MemberName, viaInstances, remote.MemberName); } ObjectFactory.GetCollection(containedMap.Type, sb.GetStatement(true).Execute(), this); } } }
/// <summary> /// This method is for adding set membership constraints using either Operator.In or /// Operator.NotIn. If an invalid operator is used this method will throw an exception. /// </summary> /// <param name="op">The operator used for this constraint.</param> /// <param name="fieldMap">The FieldMap instance for the field to constrain. The framework /// assumes property names but /// also checks column names if no property could be found.</param> /// <param name="data">The set of data to constrain on.</param> /// <param name="constraintMap">If the constraint data set holds objects this is the property /// name to gather from all objects. If the data set is an SqlResult this is the column name /// to use from all rows. String-type fields are quoted, all other types are not. This method /// performs no additional type checking and is likely to fail (when the statement is executed) /// for esoteric types such as decimals or dates.</param> public void AddConstraint( Operator op, FieldMap fieldMap, ICollection data, FieldMap constraintMap ) { Check.VerifyNotNull( fieldMap, Error.NullParameter, "field" ); Check.VerifyNotNull( data, Error.NullParameter, "data" ); Check.IsTrue( op == Operator.In || op == Operator.NotIn, Error.DeveloperError, "This AddConstraint method must be used only with the In and NotIn operators." ); // TODO make this a config option (e.g. "strict" behavior) // Check.Verify( data.Count > 0, Error.EmptyListParameter, "data" ); if( data.Count > 0 ) { // convert supplied collection into comma-delimited list of values string[] list = new string[data.Count]; int count = 0; foreach( object obj in data ) { if( constraintMap == null ) { list[ count++ ] = Convert.ToString( obj ); } else { list[ count++ ] = Convert.ToString( constraintMap.GetValue( obj ) ); } } // determine whether field type needs quoting bool needsQuoting = fieldMap.Type.Equals( typeof(string) ); needsQuoting |= fieldMap.Type.Equals( typeof(DateTime) ); // quote GUIDs (only when they are not in binary form) needsQuoting |= fieldMap.Type.Equals( typeof(Guid) ) && (fieldMap.Size == 0 || fieldMap.Size > 16); // add the constraint AddConstraint( String.Format( "{0} {1} {2} {3}", fieldMap.QuotedColumnName, AsOperatorBegin( op, false ), AsCommaList( list, needsQuoting ), AsOperatorEnd( op ) ) ); } }