private static object[] CreateCompositePk(IEntity child, DbRelation fk) { // Child and parent columns in the relation are in the same order. // However, that order may not match the order of PK definition in the parent's IDbTable class. // Thus, the FK values may have to be reordered to be used as PK. IDbColumn[] orderedPkFields = fk.Parent.PrimaryKey.ToArray(); object[] pk = new object[orderedPkFields.Length]; int nullValCount = 0; for (int idxOrderedPkPart = 0; idxOrderedPkPart < orderedPkFields.Length; idxOrderedPkPart++) { IDbColumn currPkField = orderedPkFields[idxOrderedPkPart]; int positionOfCurrentPkColumnInRelation = IndexOfColumnInArray(fk.ParentPrimaryKey, currPkField.ColumnName); IDbColumn currFkField = fk.ChildForeignKey[positionOfCurrentPkColumnInRelation]; object fkPart = child.GetField(currFkField); pk[idxOrderedPkPart] = fkPart; if (fkPart == null) nullValCount++; } bool fkIsSet = (nullValCount == 0); if (!fkIsSet) { bool allFkFieldsAreNull = (nullValCount == orderedPkFields.Length); if (allFkFieldsAreNull) pk = null; else throw new ArgumentException(Messages.PkUtil_FkMustBeCompleteOrNull, "child"); } return pk; }
/// <summary> /// Initializes a new instance of the ManyToManyRelation class. /// </summary> /// <param name="firstTableToJunction">Relation between the junction table and the first primary table.</param> /// <param name="secondTableToJunction">Relation between the junction table and the second primary table.</param> public ManyToManyRelation(DbRelation firstTableToJunction, DbRelation secondTableToJunction) { if (!firstTableToJunction.Child.HasEqualAliasAndNameAs(secondTableToJunction.Child)) throw new InvalidOperationException(Messages.ManyToManyRelation_RelationsSpecifyDiffJunctionTables); this.firstTableToJunction = firstTableToJunction; this.secondTableToJunction = secondTableToJunction; }
private static object[] CreateSingleFieldPk(IEntity child, DbRelation fk) { object[] pk; // Complex algorithm not required if simple FK is used. object fkVal = child.GetField(fk.ChildForeignKey[0]); pk = (fkVal != null) ? new object[] { fkVal } : null; return pk; }
/// <summary> /// Creates an object array which represents PK with the FK values stored in the given child. /// </summary> /// <returns>PK or <b>null</b> if child's FK is not set.</returns> /// <remarks>An exception is generated if a child contains an invalid, i.e. partially set, FK. /// Either all, or none of the FK properties must be set.</remarks> public static object[] CreatePkFromFk(IEntity child, DbRelation fk) { object[] pk; if (fk.ChildForeignKey.Length == 1) pk = CreateSingleFieldPk(child, fk); else pk = CreateCompositePk(child, fk); return pk; }
internal FromClause(IDbTable firstTable, DbRelation[] orderedRelations, TableWithJoinMode[] orderedFromTablesIncludingFirstTable) { this.FromTable = firstTable; JoinClause[] joins = new JoinClause[orderedRelations.Length]; for (int relationIdx = 0; relationIdx < orderedRelations.Length; relationIdx++) { int joinedTableIdx = relationIdx + 1; joins[relationIdx] = new JoinClause(orderedRelations[relationIdx], orderedFromTablesIncludingFirstTable[joinedTableIdx].Table, orderedFromTablesIncludingFirstTable[joinedTableIdx].JoinAsOuter); } this.Joins = joins; this.JoinCount = joins.Length; }
/// <summary> /// Adds a relation with a unique name to the collection. /// If a relation with the same name already exists, a new name is automatically set. /// </summary> /// <param name="relation">New relation.</param> public void Add(DbRelation relation) { int i = 2; while (GetByName(relation.Name) != null) { relation.Name += "_" + i.ToString(CultureInfo.InvariantCulture); i++; } this.list.Add(relation); if (this.tables.Contains(relation.Parent) == false) this.tables.Add(relation.Parent); if (this.tables.Contains(relation.Child) == false) this.tables.Add(relation.Child); }
/// <summary> /// Gets the parent entity defined by the given foreign key. /// <seealso cref="RelationshipNavigationEnabled"/> /// </summary> /// <param name="foreignKey">FK. Child table of the given FK must be the current entity's table; /// otherwise an exception is generated.</param> /// <returns>Parent entity. <b>Null</b> if the FK fields haven't been set or if the entity with the given key values doesn't exist.</returns> /// <remarks>If the entity hasn't been initialized yet but the FK values are set and relationship navigation /// is enabled then the method will automatically fetch it from the data source.</remarks> public abstract IEntity GetParent(DbRelation foreignKey);
/// <summary>Not implemented.</summary> string IDbTable.GetParentProperty(DbRelation fk, IDbColumn parentColumn) { throw new NotImplementedException(); }
/// <summary> /// Creates a deep copy of current instance. /// </summary> public DbRelation Clone() { DbRelation clone = new DbRelation(); clone.CopyFrom(this); return clone; }
private static bool GetNextRelationAndTable(DbRelationCollection allRelations, List<JoinMode> allJoinModes, TableWithJoinMode[] usedTables, out DbRelation nextRelation, out TableWithJoinMode nextTable) { nextRelation = null; nextTable = null; for (int idxRelation = 0; idxRelation < allRelations.Count; idxRelation++) { DbRelation currentRelation = allRelations[idxRelation]; IDbTable newTableCandidate = null; TableWithJoinMode alreadyUsedTableFromCurrentRelation = null; bool connectAsOuter = false; int numOfTablesCurrentRelationConnects = 0; // Check how many of the used tables current relation connects. for (int idxTable = 0; idxTable < usedTables.Length; idxTable++) { if (usedTables[idxTable] == null) continue; if (currentRelation.Parent.HasEqualAliasAndNameAs(usedTables[idxTable].Table)) { // Parent table is already in the ordered from tables list. numOfTablesCurrentRelationConnects++; newTableCandidate = currentRelation.Child; alreadyUsedTableFromCurrentRelation = usedTables[idxTable]; if (allJoinModes[idxRelation].ChildrenAsOuter == true) connectAsOuter = true; } if (currentRelation.Child.HasEqualAliasAndNameAs(usedTables[idxTable].Table)) { // Child table is already in the ordered from tables list. numOfTablesCurrentRelationConnects++; newTableCandidate = currentRelation.Parent; alreadyUsedTableFromCurrentRelation = usedTables[idxTable]; if (allJoinModes[idxRelation].ParentAsOuter == true) connectAsOuter = true; } } // If current relation connects only one used table than the other one is a new table. // Return the new relation and table. if (numOfTablesCurrentRelationConnects == 1) { // If the already used table is connected as outer then the new one MUST be outer, too. if (alreadyUsedTableFromCurrentRelation.JoinAsOuter == true) connectAsOuter = true; nextRelation = currentRelation; nextTable = new TableWithJoinMode(newTableCandidate, connectAsOuter); return true; } } return false; }
private void EnsureTheRelationIsNotAlreadyInTheBucket(DbRelation newRelation) { bool alreadyInTheBucket = (this.relations[newRelation.Parent, newRelation.Child] != null); if (alreadyInTheBucket) { //throw new InvalidOperationException("The bucket already contains a relation that connects the " + newRelation.Parent.Alias + " and " + newRelation.Child.Alias + " tables."); throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Messages.RelationBucket_AlreadyConnectsXAndYTables, newRelation.Parent.Alias, newRelation.Child.Alias)); } }
/// <summary> /// Creates a deep copy of current instance. /// </summary> /// <param name="setPrefixedAliasesForChildColumns">Specifies whether child columns in cloned relation will have prefixed aliases. /// This parameter overrides the value set in <see cref="IDbTable.ColumnAliasesArePrefixed"/> property.</param> /// <param name="setPrefixedAliasesForParentColumns">Specifies whether parent columns in cloned relation will have prefixed aliases. /// This parameter overrides the value set in <see cref="IDbTable.ColumnAliasesArePrefixed"/> property.</param> public DbRelation Clone(bool setPrefixedAliasesForChildColumns, bool setPrefixedAliasesForParentColumns) { DbRelation clone = new DbRelation(); clone.CopyFrom(this, null, setPrefixedAliasesForChildColumns, null, setPrefixedAliasesForParentColumns); return clone; }
private static int IndexOfRelation(DbRelation[] foreignKeys, DbRelation rel) { for (int idx = 0; idx < foreignKeys.Length; idx++) { if (rel.HasEqualForeignKeyFieldsAs(foreignKeys[idx])) return idx; } return -1; }
private void CopyFrom(DbRelation source, string childAlias, bool setPrefixedAliasesForChildColumns, string parentAlias, bool setPrefixedAliasesForParentColumns) { this.name = source.name; this.child = source.child.Clone(childAlias ?? source.child.Alias, setPrefixedAliasesForChildColumns); this.childFK = GetColumns(this.child, source.childFK); this.parent = source.parent.Clone(parentAlias ?? source.parent.Alias, setPrefixedAliasesForParentColumns); this.parentPK = GetColumns(this.parent, source.parentPK); }
/// <summary> /// Creates a deep copy of current instance. /// </summary> /// <param name="childAlias">New child table alias. If <b>null</b> then current alias is used.</param> /// <param name="setPrefixedAliasesForChildColumns">Specifies whether child columns in cloned relation will have prefixed aliases. /// This parameter overrides the value set in <see cref="IDbTable.ColumnAliasesArePrefixed"/> property.</param> /// <param name="parentAlias">New parent table alias. If <b>null</b> then current alias is used.</param> /// <param name="setPrefixedAliasesForParentColumns">Specifies whether parent columns in cloned relation will have prefixed aliases. /// This parameter overrides the value set in <see cref="IDbTable.ColumnAliasesArePrefixed"/> property.</param> public DbRelation Clone(string childAlias, bool setPrefixedAliasesForChildColumns, string parentAlias, bool setPrefixedAliasesForParentColumns) { DbRelation clone = new DbRelation(); clone.CopyFrom(this, childAlias, setPrefixedAliasesForChildColumns, parentAlias, setPrefixedAliasesForParentColumns); return clone; }
/// <summary> /// Sets the given value into the member that represents the parent entity defined by the foreign key. /// </summary> /// <param name="foreignKey">FK. Child table of the given FK must be the current entity's table; /// otherwise an exception is generated.</param> /// <param name="entity">Parent entity. Must be compatibile with the targeted member.</param> /// <remarks>Inherited classes must implement the required logic.</remarks> public abstract void SetParent(DbRelation foreignKey, IEntity entity);
/// <summary> /// Adds a relation to the bucket. /// </summary> /// <param name="relation">Relation.</param> /// <remarks>Child table is connected as outer. Parent table is connected as outer if foreign /// key is made of exactly one nullable column. If parallel relations are used then tables must /// use aliases, otherwise an error will occur.</remarks> /// <exception cref="ArgumentException">is generated if the bucket already contains a relation /// which contains the same tables with the same aliases as the new relation.</exception> public void Add(DbRelation relation) { EnsureTheRelationIsNotAlreadyInTheBucket(relation); this.relations.Add(relation); bool parentAsOuter = IsNullableFk(relation.ChildForeignKey); this.joinModes.Add(new JoinMode(parentAsOuter, true)); }
/// <summary> /// Gets the full property path for the specified parent entity field. /// </summary> /// <param name="fk">Relation to the parent entity.</param> /// <param name="parentColumn">Parent entity field. If null only property name generated for parent entity is returned.</param> /// <returns>Parent entity property name followed by dot operator and parent field property name if <b>parentColumn</b> is defined; otherwise only parent entity property name.</returns> /// <remarks>Eg. the following code snippet returns "RegionParent.RegionDescription" property path. /// <code> /// public string GetRegionDescriptionPropertyForDataBinder() /// { /// TerritoriesMeta territories = new TerritoriesMeta(); /// RegionMeta regions = new RegionMeta(); /// return territories.GetParentProperty(territories.FK_RegionID, regions.RegionDescription); /// } /// </code> /// </remarks> public string GetParentProperty(DbRelation fk, IDbColumn parentColumn) { int idxParent = IndexOfRelation(fk); string fullPropertyName = (idxParent >= 0) ? GetParentProperty(idxParent, parentColumn) : null; return fullPropertyName; }
private void CopyFrom(DbRelation source) { this.name = source.name; this.parent = source.parent.Clone(source.parent.Alias, source.parent.ColumnAliasesArePrefixed); //EnsureEqualColumnAliases(source.parent, this.parent); this.parentPK = GetColumns(this.parent, source.parentPK); this.child = source.child.Clone(source.child.Alias, source.child.ColumnAliasesArePrefixed); //EnsureEqualColumnAliases(this.child, source.child); this.childFK = GetColumns(this.child, source.childFK); }
/// <summary> /// Adds a relation to the bucket. /// </summary> /// <remarks>If parallel relations are used then tables must use aliases, otherwise an error will occur.</remarks> /// <param name="relation">Relation.</param> /// <param name="parentAsOuter">Specifies whether the parent table is connected as outer.</param> /// <param name="childrenAsOuter">Specifies whether the child table is connected as outer.</param> /// <remarks>Child table is connected as outer. Parent table is connected as outer if foreign /// key is made of exactly one nullable column. If parallel relations are used then tables must /// use aliases, otherwise an error will occur.</remarks> /// <exception cref="ArgumentException">is generated if the bucket already contains a relation /// which contains the same tables with the same aliases as the new relation.</exception> public void Add(DbRelation relation, bool parentAsOuter, bool childrenAsOuter) { EnsureTheRelationIsNotAlreadyInTheBucket(relation); this.relations.Add(relation); this.joinModes.Add(new JoinMode(parentAsOuter, childrenAsOuter)); }
private int IndexOfRelation(DbRelation rel) { for (int idx = 0; idx < this.ForeignKeys.Length; idx++) { if (rel.HasEqualForeignKeyFieldsAs(this.ForeignKeys[idx])) return idx; } return -1; }
/// <summary> /// Adds a relation to the bucket. /// </summary> /// <param name="parent">Parent/master table.</param> /// <param name="child">Child/data table.</param> /// <param name="childForeignKey">Child foreign key.</param> /// <remarks>Child table is connected as outer. Parent table is connected as outer if foreign /// key is made of exactly one nullable column. If parallel relations are used then tables must /// use aliases, otherwise an error will occur.</remarks> /// <exception cref="ArgumentException">is generated if the bucket already contains a relation /// which contains the same tables with the same aliases as the new relation.</exception> public void Add(IDbTable parent, IDbTable child, IDbColumn childForeignKey) { DbRelation relation = new DbRelation(parent, child, childForeignKey); Add(relation); }
/// <summary> /// Create instance with parent relation. /// </summary> /// <param name="column">Column to create.</param> /// <param name="parentRelation">Relation to column.</param> public ColumnKey(IDbColumn column, DbRelation parentRelation) { this.Column = column; this.ParentRelation = parentRelation; }
private static void SortRelationsAndJoinModes(DbRelationCollection allRelations, List<JoinMode> allJoinModes, IDbTable firstTable, out DbRelation[] orderedRelations, out TableWithJoinMode[] orderedFromTables) { // Sorts relations in a fashion so that only INNER and LEFT OUTER joins are used. RIGHT joins will never be required. orderedRelations = new DbRelation[allRelations.Count]; int relationIdx = 0; orderedFromTables = new TableWithJoinMode[allJoinModes.Count + 1]; int fromIdx = 0; // JoinAsOuter property doesn't doesn't affect 1st table. orderedFromTables[fromIdx] = new TableWithJoinMode(firstTable, false); fromIdx++; DbRelation nextRelation; TableWithJoinMode nextTable; while (GetNextRelationAndTable(allRelations, allJoinModes, orderedFromTables, out nextRelation, out nextTable)) { orderedRelations[relationIdx++] = nextRelation; orderedFromTables[fromIdx++] = nextTable; } }
internal JoinClause(DbRelation relation, IDbTable joinedTable, bool isLeftOuterJoin) { this.Relation = relation; this.JoinedTable = joinedTable; this.IsLeftOuterJoin = isLeftOuterJoin; }
/// <summary> /// Initializes a new instance of the RelationBucket class. /// </summary> /// <param name="firstRelation">First/initial relation added to the bucket using <see cref="Add(DbRelation)"/> method.</param> public RelationBucket(DbRelation firstRelation) { Add(firstRelation); }
/// <summary> /// Checks whether the two relations use the same child FK fields. /// </summary> /// <param name="otherRelation">Other relation.</param> /// <returns>True if all FK fields have been matched; false otherwise.</returns> public bool HasEqualForeignKeyFieldsAs(DbRelation otherRelation) { // Check if both relations use the same table as a child object. // Check only TableNames. Aliases may differ. if (this.child.TableName != otherRelation.child.TableName) return false; // FK count must be the same. if (this.childFK.Length != otherRelation.childFK.Length) return false; // Field order may be different, so all combinations must be tested. int matchedFkFieldsCount = 0; for (int i=0; i<this.childFK.Length; i++) { for (int j=0; j<otherRelation.childFK.Length; j++) { if (this.childFK[i].ColumnName == otherRelation.childFK[j].ColumnName) { matchedFkFieldsCount++; break; } } } bool allFkFieldsAreMatched = (matchedFkFieldsCount == this.childFK.Length); return allFkFieldsAreMatched; }