1 //---------------------------------------------------------------------
2 // <copyright file="ConstraintManager.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
11 using System.Collections.Generic;
12 using System.Globalization;
13 using System.Data.Common;
14 using System.Data.Query.InternalTrees;
15 using md=System.Data.Metadata.Edm;
16 //using System.Diagnostics; // Please use PlanCompiler.Assert instead of Debug.Assert in this class...
18 // It is fine to use Debug.Assert in cases where you assert an obvious thing that is supposed
19 // to prevent from simple mistakes during development (e.g. method argument validation
20 // in cases where it was you who created the variables or the variables had already been validated or
21 // in "else" clauses where due to code changes (e.g. adding a new value to an enum type) the default
22 // "else" block is chosen why the new condition should be treated separately). This kind of asserts are
23 // (can be) helpful when developing new code to avoid simple mistakes but have no or little value in
24 // the shipped product.
25 // PlanCompiler.Assert *MUST* be used to verify conditions in the trees. These would be assumptions
26 // about how the tree was built etc. - in these cases we probably want to throw an exception (this is
27 // what PlanCompiler.Assert does when the condition is not met) if either the assumption is not correct
28 // or the tree was built/rewritten not the way we thought it was.
29 // Use your judgment - if you rather remove an assert than ship it use Debug.Assert otherwise use
30 // PlanCompiler.Assert.
33 // The ConstraintManager module manages foreign key constraints for a query. It reshapes
34 // referential constraints supplied by metadata into a more useful form.
36 namespace System.Data.Query.PlanCompiler
39 /// A simple class that represents a pair of extents
41 internal class ExtentPair
43 #region public surface
45 /// Return the left component of the pair
47 internal md.EntitySetBase Left { get { return m_left; } }
50 /// Return the right component of the pair
52 internal md.EntitySetBase Right { get { return m_right; } }
57 /// <param name="obj"></param>
58 /// <returns></returns>
59 public override bool Equals(object obj)
61 ExtentPair other = obj as ExtentPair;
62 return (other != null) && other.Left.Equals(this.Left) && other.Right.Equals(this.Right);
68 /// <returns></returns>
69 public override int GetHashCode()
71 return (this.Left.GetHashCode() << 4) ^ this.Right.GetHashCode();
76 internal ExtentPair(md.EntitySetBase left, md.EntitySetBase right)
84 private md.EntitySetBase m_left;
85 private md.EntitySetBase m_right;
90 /// Information about a foreign-key constraint
92 internal class ForeignKeyConstraint
94 #region public surface
97 /// Parent key properties
99 internal List<string> ParentKeys { get { return m_parentKeys; } }
101 /// Child key properties
103 internal List<string> ChildKeys { get { return m_childKeys; } }
106 /// Get the parent-child pair
108 internal ExtentPair Pair { get { return m_extentPair; } }
111 /// Return the child rowcount
113 internal md.RelationshipMultiplicity ChildMultiplicity { get { return m_constraint.ToRole.RelationshipMultiplicity; } }
116 /// Get the corresponding parent (key) property, for a specific child (foreign key) property
118 /// <param name="childPropertyName">child (foreign key) property name</param>
119 /// <param name="parentPropertyName">corresponding parent property name</param>
120 /// <returns>true, if the parent property was found</returns>
121 internal bool GetParentProperty(string childPropertyName, out string parentPropertyName)
124 return m_keyMap.TryGetValue(childPropertyName, out parentPropertyName);
129 internal ForeignKeyConstraint(md.RelationshipType relType, md.RelationshipSet relationshipSet, md.ReferentialConstraint constraint)
131 md.AssociationSet assocSet = relationshipSet as md.AssociationSet;
132 md.AssociationEndMember fromEnd = constraint.FromRole as md.AssociationEndMember;
133 md.AssociationEndMember toEnd = constraint.ToRole as md.AssociationEndMember;
135 // Currently only Associations are supported
136 if (null == assocSet || null == fromEnd || null == toEnd)
138 throw EntityUtil.NotSupported();
141 m_constraint = constraint;
142 md.EntitySet parent = System.Data.Common.Utils.MetadataHelper.GetEntitySetAtEnd(assocSet, fromEnd);// relationshipSet.GetRelationshipEndExtent(constraint.FromRole);
143 md.EntitySet child = System.Data.Common.Utils.MetadataHelper.GetEntitySetAtEnd(assocSet, toEnd);// relationshipSet.GetRelationshipEndExtent(constraint.ToRole);
144 m_extentPair = new ExtentPair(parent, child);
145 m_childKeys = new List<string>();
146 foreach (md.EdmProperty prop in constraint.ToProperties)
148 m_childKeys.Add(prop.Name);
151 m_parentKeys = new List<string>();
152 foreach (md.EdmProperty prop in constraint.FromProperties)
154 m_parentKeys.Add(prop.Name);
157 PlanCompiler.Assert((md.RelationshipMultiplicity.ZeroOrOne == fromEnd.RelationshipMultiplicity || md.RelationshipMultiplicity.One == fromEnd.RelationshipMultiplicity), "from-end of relationship constraint cannot have multiplicity greater than 1");
161 #region private state
162 private ExtentPair m_extentPair;
163 private List<string> m_parentKeys;
164 private List<string> m_childKeys;
165 private md.ReferentialConstraint m_constraint;
166 private Dictionary<string, string> m_keyMap;
169 #region private methods
172 /// Build up an equivalence map of primary keys and foreign keys (ie) for each
173 /// foreign key column, identify the corresponding primary key property
175 private void BuildKeyMap()
177 if (m_keyMap != null)
182 m_keyMap = new Dictionary<string, string>();
183 IEnumerator<md.EdmProperty> parentProps = m_constraint.FromProperties.GetEnumerator();
184 IEnumerator<md.EdmProperty> childProps = m_constraint.ToProperties.GetEnumerator();
187 bool parentOver = !parentProps.MoveNext();
188 bool childOver = !childProps.MoveNext();
189 PlanCompiler.Assert(parentOver == childOver, "key count mismatch");
194 m_keyMap[childProps.Current.Name] = parentProps.Current.Name;
201 /// Keeps track of all foreign key relationships
203 internal class ConstraintManager
205 #region public methods
207 /// Is there a parent child relationship between table1 and table2 ?
209 /// <param name="table1">parent table ?</param>
210 /// <param name="table2">child table ?</param>
211 /// <param name="constraints">list of constraints ?</param>
212 /// <returns>true if there is at least one constraint</returns>
213 internal bool IsParentChildRelationship(md.EntitySetBase table1, md.EntitySetBase table2,
214 out List<ForeignKeyConstraint> constraints)
216 LoadRelationships(table1.EntityContainer);
217 LoadRelationships(table2.EntityContainer);
219 ExtentPair extentPair = new ExtentPair(table1, table2);
220 return m_parentChildRelationships.TryGetValue(extentPair, out constraints);
224 /// Load all relationships in this entity container
226 /// <param name="entityContainer"></param>
227 internal void LoadRelationships(md.EntityContainer entityContainer)
229 // Check to see if I've already loaded information for this entity container
230 if (m_entityContainerMap.ContainsKey(entityContainer))
235 // Load all relationships from this entitycontainer
236 foreach (md.EntitySetBase e in entityContainer.BaseEntitySets)
238 md.RelationshipSet relationshipSet = e as md.RelationshipSet;
239 if (relationshipSet == null)
244 // Relationship sets can only contain relationships
245 md.RelationshipType relationshipType = (md.RelationshipType)relationshipSet.ElementType;
246 md.AssociationType assocType = relationshipType as md.AssociationType;
249 // Handle only binary Association relationships for now
251 if (null == assocType || !IsBinary(relationshipType))
256 foreach (md.ReferentialConstraint constraint in assocType.ReferentialConstraints)
258 List<ForeignKeyConstraint> fkConstraintList;
259 ForeignKeyConstraint fkConstraint = new ForeignKeyConstraint(relationshipType, relationshipSet, constraint);
260 if (!m_parentChildRelationships.TryGetValue(fkConstraint.Pair, out fkConstraintList))
262 fkConstraintList = new List<ForeignKeyConstraint>();
263 m_parentChildRelationships[fkConstraint.Pair] = fkConstraintList;
266 // Theoretically, we can have more than one fk constraint between
267 // the 2 tables (though, it is unlikely)
269 fkConstraintList.Add(fkConstraint);
273 // Mark this entity container as already loaded
274 m_entityContainerMap[entityContainer] = entityContainer;
279 internal ConstraintManager()
281 m_entityContainerMap = new Dictionary<md.EntityContainer, md.EntityContainer>();
282 m_parentChildRelationships = new Dictionary<ExtentPair, List<ForeignKeyConstraint>>();
286 #region private state
287 private Dictionary<md.EntityContainer, md.EntityContainer> m_entityContainerMap;
288 private Dictionary<ExtentPair, List<ForeignKeyConstraint>> m_parentChildRelationships;
291 #region private methods
294 /// Is this relationship a binary relationship (ie) does it have exactly 2 end points?
296 /// This should ideally be a method supported by RelationType itself
298 /// <param name="relationshipType"></param>
299 /// <returns>true, if this is a binary relationship</returns>
300 private static bool IsBinary(md.RelationshipType relationshipType)
303 foreach(md.EdmMember member in relationshipType.Members)
305 if (member is md.RelationshipEndMember)
314 return (endCount == 2);