1 //---------------------------------------------------------------------
2 // <copyright file="DiscriminatorMap.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
10 using System.Data.Common.CommandTrees;
11 using System.Data.Common.CommandTrees.Internal;
12 using System.Collections.Generic;
13 using System.Data.Metadata.Edm;
15 using System.Globalization;
16 using System.Diagnostics;
17 using System.Data.Common.Utils;
19 namespace System.Data.Mapping.ViewGeneration
22 /// Describes top-level query mapping view projection of the form:
25 /// WHEN Discriminator = DiscriminatorValue1 THEN EntityType1(...)
26 /// WHEN Discriminator = DiscriminatorValue2 THEN EntityType2(...)
29 /// Supports optimizing queries to leverage user supplied discriminator values
30 /// in TPH mappings rather than introducing our own. This avoids the need
31 /// to introduce a CASE statement in the store.
33 internal class DiscriminatorMap
36 /// Expression retrieving discriminator value from projection input.
38 internal readonly DbPropertyExpression Discriminator;
40 /// Map from discriminator values to implied entity type.
42 internal readonly System.Collections.ObjectModel.ReadOnlyCollection<KeyValuePair<object, EntityType>> TypeMap;
44 /// Map from entity property to expression generating value for that property. Note that
45 /// the expression must be the same for all types in discriminator map.
47 internal readonly System.Collections.ObjectModel.ReadOnlyCollection<KeyValuePair<EdmProperty, DbExpression>> PropertyMap;
49 /// Map from entity relproperty to expression generating value for that property. Note that
50 /// the expression must be the same for all types in discriminator map.
52 internal readonly System.Collections.ObjectModel.ReadOnlyCollection<KeyValuePair<Query.InternalTrees.RelProperty, DbExpression>> RelPropertyMap;
55 /// EntitySet to which the map applies.
57 internal readonly EntitySet EntitySet;
59 private DiscriminatorMap(DbPropertyExpression discriminator,
60 List<KeyValuePair<object, EntityType>> typeMap,
61 Dictionary<EdmProperty, DbExpression> propertyMap,
62 Dictionary<Query.InternalTrees.RelProperty, DbExpression> relPropertyMap,
65 this.Discriminator = discriminator;
66 this.TypeMap = typeMap.AsReadOnly();
67 this.PropertyMap = propertyMap.ToList().AsReadOnly();
68 this.RelPropertyMap = relPropertyMap.ToList().AsReadOnly();
69 this.EntitySet = entitySet;
73 /// Determines whether the given query view matches the discriminator map pattern.
75 internal static bool TryCreateDiscriminatorMap(EntitySet entitySet, DbExpression queryView, out DiscriminatorMap discriminatorMap)
77 discriminatorMap = null;
79 if (queryView.ExpressionKind != DbExpressionKind.Project) { return false; }
80 var project = (DbProjectExpression)queryView;
82 if (project.Projection.ExpressionKind != DbExpressionKind.Case) { return false; }
83 var caseExpression = (DbCaseExpression)project.Projection;
84 if (project.Projection.ResultType.EdmType.BuiltInTypeKind != BuiltInTypeKind.EntityType) { return false; }
86 // determine value domain by walking filter
87 if (project.Input.Expression.ExpressionKind != DbExpressionKind.Filter) { return false; }
88 var filterExpression = (DbFilterExpression)project.Input.Expression;
90 HashSet<object> discriminatorDomain = new HashSet<object>();
91 if (!ViewSimplifier.TryMatchDiscriminatorPredicate(filterExpression, (equalsExp, discriminatorValue) => discriminatorDomain.Add(discriminatorValue)))
96 var typeMap = new List<KeyValuePair<object, EntityType>>();
97 var propertyMap = new Dictionary<EdmProperty, DbExpression>();
98 var relPropertyMap = new Dictionary<Query.InternalTrees.RelProperty, DbExpression>();
99 var typeToRelPropertyMap = new Dictionary<EntityType, List<Query.InternalTrees.RelProperty>>();
100 DbPropertyExpression discriminator = null;
102 EdmProperty discriminatorProperty = null;
103 for (int i = 0; i < caseExpression.When.Count; i++)
105 var when = caseExpression.When[i];
106 var then = caseExpression.Then[i];
108 var projectionVariableName = project.Input.VariableName;
110 DbPropertyExpression currentDiscriminator;
111 object discriminatorValue;
112 if (!ViewSimplifier.TryMatchPropertyEqualsValue(when, projectionVariableName, out currentDiscriminator, out discriminatorValue)) { return false; }
114 // must be the same discriminator in every case
115 if (null == discriminatorProperty) { discriminatorProperty = (EdmProperty)currentDiscriminator.Property; }
116 else if (discriminatorProperty != currentDiscriminator.Property) { return false; }
117 discriminator = currentDiscriminator;
119 // right hand side must be entity type constructor
120 EntityType currentType;
121 if (!TryMatchEntityTypeConstructor(then, propertyMap, relPropertyMap, typeToRelPropertyMap, out currentType)) { return false; }
123 // remember type + discriminator value
124 typeMap.Add(new KeyValuePair<object, EntityType>(discriminatorValue, currentType));
126 // remove discriminator value from domain
127 discriminatorDomain.Remove(discriminatorValue);
130 // make sure only one member of discriminator domain remains...
131 if (1 != discriminatorDomain.Count) { return false; }
133 // check default case
135 if (null == caseExpression.Else ||
136 !TryMatchEntityTypeConstructor(caseExpression.Else, propertyMap, relPropertyMap, typeToRelPropertyMap, out elseType)) { return false; }
137 typeMap.Add(new KeyValuePair<object, EntityType>(discriminatorDomain.Single(), elseType));
139 // Account for cases where some type in the hierarchy specifies a rel-property, but another
140 // type in the hierarchy does not
141 if (!CheckForMissingRelProperties(relPropertyMap, typeToRelPropertyMap))
146 // since the store may right-pad strings, ensure discriminator values are unique in their trimmed
148 var discriminatorValues = typeMap.Select(map => map.Key);
149 int uniqueValueCount = discriminatorValues.Distinct(TrailingSpaceComparer.Instance).Count();
150 int valueCount = typeMap.Count;
151 if (uniqueValueCount != valueCount) { return false; }
153 discriminatorMap = new DiscriminatorMap(discriminator, typeMap, propertyMap, relPropertyMap, entitySet);
157 private static bool CheckForMissingRelProperties(
158 Dictionary<Query.InternalTrees.RelProperty, DbExpression> relPropertyMap,
159 Dictionary<EntityType, List<Query.InternalTrees.RelProperty>> typeToRelPropertyMap)
161 // Easily the lousiest implementation of this search.
162 // Check to see that for each relProperty that we see in the relPropertyMap
163 // (presumably because some type constructor specified it), every type for
164 // which that rel-property is specified *must* also have specified it.
165 // We don't need to check for equivalence here - because that's already been
167 foreach (Query.InternalTrees.RelProperty relProperty in relPropertyMap.Keys)
169 foreach (KeyValuePair<EntityType, List<Query.InternalTrees.RelProperty>> kv in typeToRelPropertyMap)
171 if (kv.Key.IsSubtypeOf(relProperty.FromEnd.TypeUsage.EdmType))
173 if (!kv.Value.Contains(relProperty))
183 private static bool TryMatchEntityTypeConstructor(DbExpression then,
184 Dictionary<EdmProperty, DbExpression> propertyMap,
185 Dictionary<Query.InternalTrees.RelProperty, DbExpression> relPropertyMap,
186 Dictionary<EntityType, List<Query.InternalTrees.RelProperty>> typeToRelPropertyMap,
187 out EntityType entityType)
189 if (then.ExpressionKind != DbExpressionKind.NewInstance)
194 var constructor = (DbNewInstanceExpression)then;
195 entityType = (EntityType)constructor.ResultType.EdmType;
197 // process arguments to constructor (must be aligned across all case statements)
198 Debug.Assert(entityType.Properties.Count == constructor.Arguments.Count, "invalid new instance");
199 for (int j = 0; j < entityType.Properties.Count; j++)
201 var property = entityType.Properties[j];
202 var assignment = constructor.Arguments[j];
203 DbExpression existingAssignment;
204 if (propertyMap.TryGetValue(property, out existingAssignment))
206 if (!ExpressionsCompatible(assignment, existingAssignment)) { return false; }
210 propertyMap.Add(property, assignment);
214 // Now handle the rel properties
215 if (constructor.HasRelatedEntityReferences)
217 List<Query.InternalTrees.RelProperty> relPropertyList;
218 if (!typeToRelPropertyMap.TryGetValue(entityType, out relPropertyList))
220 relPropertyList = new List<System.Data.Query.InternalTrees.RelProperty>();
221 typeToRelPropertyMap[entityType] = relPropertyList;
223 foreach (DbRelatedEntityRef relatedRef in constructor.RelatedEntityReferences)
225 Query.InternalTrees.RelProperty relProperty = new System.Data.Query.InternalTrees.RelProperty((RelationshipType)relatedRef.TargetEnd.DeclaringType,
226 relatedRef.SourceEnd, relatedRef.TargetEnd);
227 DbExpression assignment = relatedRef.TargetEntityReference;
228 DbExpression existingAssignment;
229 if (relPropertyMap.TryGetValue(relProperty, out existingAssignment))
231 if (!ExpressionsCompatible(assignment, existingAssignment)) { return false; }
235 relPropertyMap.Add(relProperty, assignment);
237 relPropertyList.Add(relProperty);
244 /// Utility method determining whether two expressions appearing within the same scope
245 /// are equivalent. May return false negatives, but no false positives. In other words,
247 /// x != y --> !ExpressionsCompatible(x, y)
249 /// but does not guarantee
251 /// x == y --> ExpressionsCompatible(x, y)
253 private static bool ExpressionsCompatible(DbExpression x, DbExpression y)
255 if (x.ExpressionKind != y.ExpressionKind) { return false; }
256 switch (x.ExpressionKind)
258 case DbExpressionKind.Property:
260 var prop1 = (DbPropertyExpression)x;
261 var prop2 = (DbPropertyExpression)y;
262 return prop1.Property == prop2.Property &&
263 ExpressionsCompatible(prop1.Instance, prop2.Instance);
265 case DbExpressionKind.VariableReference:
266 return ((DbVariableReferenceExpression)x).VariableName ==
267 ((DbVariableReferenceExpression)y).VariableName;
268 case DbExpressionKind.NewInstance:
270 var newX = (DbNewInstanceExpression)x;
271 var newY = (DbNewInstanceExpression)y;
272 if (!newX.ResultType.EdmType.EdmEquals(newY.ResultType.EdmType)) { return false; }
273 for (int i = 0; i < newX.Arguments.Count; i++)
275 if (!ExpressionsCompatible(newX.Arguments[i], newY.Arguments[i]))
282 case DbExpressionKind.Ref:
284 DbRefExpression refX = (DbRefExpression)x;
285 DbRefExpression refY = (DbRefExpression)y;
286 return (refX.EntitySet.EdmEquals(refY.EntitySet) &&
287 ExpressionsCompatible(refX.Argument, refY.Argument));
290 // here come the false negatives...