Code:
/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / Orcas / NetFXw7 / ndp / fx / src / DataEntity / System / Data / Map / ViewGeneration / DiscriminatorMap.cs / 1 / DiscriminatorMap.cs
//----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Data.Common.CommandTrees;
using System.Data.Common.CommandTrees.Internal;
using System.Collections.Generic;
using System.Data.Metadata.Edm;
using System.Linq;
using System.Globalization;
using System.Diagnostics;
using System.Data.Common.Utils;
namespace System.Data.Mapping.ViewGeneration
{
///
/// Describes top-level query mapping view projection of the form:
///
/// SELECT VALUE CASE
/// WHEN Discriminator = DiscriminatorValue1 THEN EntityType1(...)
/// WHEN Discriminator = DiscriminatorValue2 THEN EntityType2(...)
/// ...
///
/// Supports optimizing queries to leverage user supplied discriminator values
/// in TPH mappings rather than introducing our own. This avoids the need
/// to introduce a CASE statement in the store.
///
internal class DiscriminatorMap
{
///
/// Expression retrieving discriminator value from projection input.
///
internal readonly DbPropertyExpression Discriminator;
///
/// Map from discriminator values to implied entity type.
///
internal readonly System.Collections.ObjectModel.ReadOnlyCollection> TypeMap;
///
/// Map from entity property to expression generating value for that property. Note that
/// the expression must be the same for all types in discriminator map.
///
internal readonly System.Collections.ObjectModel.ReadOnlyCollection> PropertyMap;
///
/// Map from entity relproperty to expression generating value for that property. Note that
/// the expression must be the same for all types in discriminator map.
///
internal readonly System.Collections.ObjectModel.ReadOnlyCollection> RelPropertyMap;
///
/// EntitySet to which the map applies.
///
internal readonly EntitySet EntitySet;
private DiscriminatorMap(DbPropertyExpression discriminator,
List> typeMap,
Dictionary propertyMap,
Dictionary relPropertyMap,
EntitySet entitySet)
{
this.Discriminator = discriminator;
this.TypeMap = typeMap.AsReadOnly();
this.PropertyMap = propertyMap.ToList().AsReadOnly();
this.RelPropertyMap = relPropertyMap.ToList().AsReadOnly();
this.EntitySet = entitySet;
}
///
/// Determines whether the given query view matches the discriminator map pattern.
///
internal static bool TryCreateDiscriminatorMap(EntitySet entitySet, DbExpression queryView, out DiscriminatorMap discriminatorMap)
{
discriminatorMap = null;
if (queryView.ExpressionKind != DbExpressionKind.Project) { return false; }
var project = (DbProjectExpression)queryView;
if (project.Projection.ExpressionKind != DbExpressionKind.Case) { return false; }
var caseExpression = (DbCaseExpression)project.Projection;
if (project.Projection.ResultType.EdmType.BuiltInTypeKind != BuiltInTypeKind.EntityType) { return false; }
EdmProperty discriminatorProperty = null;
// determine value domain by walking filter
HashSet discriminatorDomain = new HashSet();
if (project.Input.Expression.ExpressionKind != DbExpressionKind.Filter) { return false; }
var filterExpression = (DbFilterExpression)project.Input.Expression;
// check each assignment in predicate
foreach (var term in FlattenOr(filterExpression.Predicate))
{
DbPropertyExpression currentDiscriminator;
object discriminatorValue;
if (!TryMatchPropertyEqualsValue(term, filterExpression.Input.VariableName, out currentDiscriminator, out discriminatorValue)) { return false; }
// must be the same discriminator in every case
if (null == discriminatorProperty) { discriminatorProperty = (EdmProperty)currentDiscriminator.Property; }
else if (discriminatorProperty != currentDiscriminator.Property) { return false; }
discriminatorDomain.Add(discriminatorValue);
}
var typeMap = new List>();
var propertyMap = new Dictionary();
var relPropertyMap = new Dictionary();
var typeToRelPropertyMap = new Dictionary>();
DbPropertyExpression discriminator = null;
for (int i = 0; i < caseExpression.When.Count; i++)
{
var when = caseExpression.When[i];
var then = caseExpression.Then[i];
var projectionVariableName = project.Input.VariableName;
DbPropertyExpression currentDiscriminator;
object discriminatorValue;
if (!TryMatchPropertyEqualsValue(when, projectionVariableName, out currentDiscriminator, out discriminatorValue)) { return false; }
// must be the same discriminator in every case
if (null == discriminatorProperty) { discriminatorProperty = (EdmProperty)currentDiscriminator.Property; }
else if (discriminatorProperty != currentDiscriminator.Property) { return false; }
discriminator = currentDiscriminator;
// right hand side must be entity type constructor
EntityType currentType;
if (!TryMatchEntityTypeConstructor(then, propertyMap, relPropertyMap, typeToRelPropertyMap, out currentType)) { return false; }
// remember type + discriminator value
typeMap.Add(new KeyValuePair(discriminatorValue, currentType));
// remove discriminator value from domain
discriminatorDomain.Remove(discriminatorValue);
}
// make sure only one member of discriminator domain remains...
if (1 != discriminatorDomain.Count) { return false; }
// check default case
EntityType elseType;
if (null == caseExpression.Else ||
!TryMatchEntityTypeConstructor(caseExpression.Else, propertyMap, relPropertyMap, typeToRelPropertyMap, out elseType)) { return false; }
typeMap.Add(new KeyValuePair(discriminatorDomain.Single(), elseType));
// Account for cases where some type in the hierarchy specifies a rel-property, but another
// type in the hierarchy does not
if (!CheckForMissingRelProperties(relPropertyMap, typeToRelPropertyMap))
{
return false;
}
// since the store may right-pad strings, ensure discriminator values are unique in their trimmed
// form
var discriminatorValues = typeMap.Select(map => map.Key);
int uniqueValueCount = discriminatorValues.Distinct(TrailingSpaceComparer.Instance).Count();
int valueCount = typeMap.Count;
if (uniqueValueCount != valueCount) { return false; }
discriminatorMap = new DiscriminatorMap(discriminator, typeMap, propertyMap, relPropertyMap, entitySet);
return true;
}
private static bool CheckForMissingRelProperties(
Dictionary relPropertyMap,
Dictionary> typeToRelPropertyMap)
{
// Easily the lousiest implementation of this search.
// Check to see that for each relProperty that we see in the relPropertyMap
// (presumably because some type constructor specified it), every type for
// which that rel-property is specified *must* also have specified it.
// We don't need to check for equivalence here - because that's already been
// checked
foreach (Query.InternalTrees.RelProperty relProperty in relPropertyMap.Keys)
{
foreach (KeyValuePair> kv in typeToRelPropertyMap)
{
if (kv.Key.IsSubtypeOf(relProperty.FromEnd.TypeUsage.EdmType))
{
if (!kv.Value.Contains(relProperty))
{
return false;
}
}
}
}
return true;
}
private static bool TryMatchPropertyEqualsValue(DbExpression expression, string propertyVariable, out DbPropertyExpression property, out object value)
{
property = null;
value = null;
// make sure when is of the form Discriminator = Constant
if (expression.ExpressionKind != DbExpressionKind.Equals) { return false; }
var equals = (DbBinaryExpression)expression;
if (equals.Left.ExpressionKind != DbExpressionKind.Property) { return false; }
property = (DbPropertyExpression)equals.Left;
if (!TryMatchConstant(equals.Right, out value)) { return false; }
// verify the property is a property of the input variable
if (property.Instance.ExpressionKind != DbExpressionKind.VariableReference ||
((DbVariableReferenceExpression)property.Instance).VariableName != propertyVariable) { return false; }
return true;
}
private static bool TryMatchConstant(DbExpression expression, out object value)
{
if (expression.ExpressionKind == DbExpressionKind.Constant)
{
value = ((DbConstantExpression)expression).Value;
return true;
}
if (expression.ExpressionKind == DbExpressionKind.Cast &&
expression.ResultType.EdmType.BuiltInTypeKind == BuiltInTypeKind.PrimitiveType)
{
var castExpression = (DbCastExpression)expression;
if (TryMatchConstant(castExpression.Argument, out value))
{
// convert the value
var primitiveType = (PrimitiveType)expression.ResultType.EdmType;
// constant literals have already been validated by view gen...
value = Convert.ChangeType(value, primitiveType.ClrEquivalentType, CultureInfo.InvariantCulture);
return true;
}
}
value = null;
return false;
}
private static IEnumerable FlattenOr(DbExpression expression)
{
if (expression.ExpressionKind == DbExpressionKind.Or)
{
return CommandTreeUtils.FlattenAssociativeExpression(expression);
}
return new DbExpression[] { expression };
}
private static bool TryMatchEntityTypeConstructor(DbExpression then,
Dictionary propertyMap,
Dictionary relPropertyMap,
Dictionary> typeToRelPropertyMap,
out EntityType entityType)
{
if (then.ExpressionKind != DbExpressionKind.NewInstance)
{
entityType = null;
return false;
}
var constructor = (DbNewInstanceExpression)then;
entityType = (EntityType)constructor.ResultType.EdmType;
// process arguments to constructor (must be aligned across all case statements)
Debug.Assert(entityType.Properties.Count == constructor.Arguments.Count, "invalid new instance");
for (int j = 0; j < entityType.Properties.Count; j++)
{
var property = entityType.Properties[j];
var assignment = constructor.Arguments[j];
DbExpression existingAssignment;
if (propertyMap.TryGetValue(property, out existingAssignment))
{
if (!ExpressionsCompatible(assignment, existingAssignment)) { return false; }
}
else
{
propertyMap.Add(property, assignment);
}
}
// Now handle the rel properties
if (constructor.HasRelatedEntityReferences)
{
List relPropertyList;
if (!typeToRelPropertyMap.TryGetValue(entityType, out relPropertyList))
{
relPropertyList = new List();
typeToRelPropertyMap[entityType] = relPropertyList;
}
foreach (DbRelatedEntityRef relatedRef in constructor.RelatedEntityReferences)
{
Query.InternalTrees.RelProperty relProperty = new System.Data.Query.InternalTrees.RelProperty((RelationshipType)relatedRef.TargetEnd.DeclaringType,
relatedRef.SourceEnd, relatedRef.TargetEnd);
DbExpression assignment = relatedRef.TargetEntityReference;
DbExpression existingAssignment;
if (relPropertyMap.TryGetValue(relProperty, out existingAssignment))
{
if (!ExpressionsCompatible(assignment, existingAssignment)) { return false; }
}
else
{
relPropertyMap.Add(relProperty, assignment);
}
relPropertyList.Add(relProperty);
}
}
return true;
}
///
/// Utility method determining whether two expressions appearing within the same scope
/// are equivalent. May return false negatives, but no false positives. In other words,
///
/// x != y --> !ExpressionsCompatible(x, y)
///
/// but does not guarantee
///
/// x == y --> ExpressionsCompatible(x, y)
///
private static bool ExpressionsCompatible(DbExpression x, DbExpression y)
{
if (x.ExpressionKind != y.ExpressionKind) { return false; }
switch (x.ExpressionKind)
{
case DbExpressionKind.Property:
{
var prop1 = (DbPropertyExpression)x;
var prop2 = (DbPropertyExpression)y;
return prop1.Property == prop2.Property &&
ExpressionsCompatible(prop1.Instance, prop2.Instance);
}
case DbExpressionKind.VariableReference:
return ((DbVariableReferenceExpression)x).VariableName ==
((DbVariableReferenceExpression)y).VariableName;
case DbExpressionKind.NewInstance:
{
var newX = (DbNewInstanceExpression)x;
var newY = (DbNewInstanceExpression)y;
if (!newX.ResultType.EdmType.EdmEquals(newY.ResultType.EdmType)) { return false; }
for (int i = 0; i < newX.Arguments.Count; i++)
{
if (!ExpressionsCompatible(newX.Arguments[i], newY.Arguments[i]))
{
return false;
}
}
return true;
}
case DbExpressionKind.Ref:
{
DbRefExpression refX = (DbRefExpression)x;
DbRefExpression refY = (DbRefExpression)y;
return (refX.EntitySet.EdmEquals(refY.EntitySet) &&
ExpressionsCompatible(refX.Argument, refY.Argument));
}
default:
// here come the false negatives...
return false;
}
}
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Data.Common.CommandTrees;
using System.Data.Common.CommandTrees.Internal;
using System.Collections.Generic;
using System.Data.Metadata.Edm;
using System.Linq;
using System.Globalization;
using System.Diagnostics;
using System.Data.Common.Utils;
namespace System.Data.Mapping.ViewGeneration
{
///
/// Describes top-level query mapping view projection of the form:
///
/// SELECT VALUE CASE
/// WHEN Discriminator = DiscriminatorValue1 THEN EntityType1(...)
/// WHEN Discriminator = DiscriminatorValue2 THEN EntityType2(...)
/// ...
///
/// Supports optimizing queries to leverage user supplied discriminator values
/// in TPH mappings rather than introducing our own. This avoids the need
/// to introduce a CASE statement in the store.
///
internal class DiscriminatorMap
{
///
/// Expression retrieving discriminator value from projection input.
///
internal readonly DbPropertyExpression Discriminator;
///
/// Map from discriminator values to implied entity type.
///
internal readonly System.Collections.ObjectModel.ReadOnlyCollection> TypeMap;
///
/// Map from entity property to expression generating value for that property. Note that
/// the expression must be the same for all types in discriminator map.
///
internal readonly System.Collections.ObjectModel.ReadOnlyCollection> PropertyMap;
///
/// Map from entity relproperty to expression generating value for that property. Note that
/// the expression must be the same for all types in discriminator map.
///
internal readonly System.Collections.ObjectModel.ReadOnlyCollection> RelPropertyMap;
///
/// EntitySet to which the map applies.
///
internal readonly EntitySet EntitySet;
private DiscriminatorMap(DbPropertyExpression discriminator,
List> typeMap,
Dictionary propertyMap,
Dictionary relPropertyMap,
EntitySet entitySet)
{
this.Discriminator = discriminator;
this.TypeMap = typeMap.AsReadOnly();
this.PropertyMap = propertyMap.ToList().AsReadOnly();
this.RelPropertyMap = relPropertyMap.ToList().AsReadOnly();
this.EntitySet = entitySet;
}
///
/// Determines whether the given query view matches the discriminator map pattern.
///
internal static bool TryCreateDiscriminatorMap(EntitySet entitySet, DbExpression queryView, out DiscriminatorMap discriminatorMap)
{
discriminatorMap = null;
if (queryView.ExpressionKind != DbExpressionKind.Project) { return false; }
var project = (DbProjectExpression)queryView;
if (project.Projection.ExpressionKind != DbExpressionKind.Case) { return false; }
var caseExpression = (DbCaseExpression)project.Projection;
if (project.Projection.ResultType.EdmType.BuiltInTypeKind != BuiltInTypeKind.EntityType) { return false; }
EdmProperty discriminatorProperty = null;
// determine value domain by walking filter
HashSet discriminatorDomain = new HashSet();
if (project.Input.Expression.ExpressionKind != DbExpressionKind.Filter) { return false; }
var filterExpression = (DbFilterExpression)project.Input.Expression;
// check each assignment in predicate
foreach (var term in FlattenOr(filterExpression.Predicate))
{
DbPropertyExpression currentDiscriminator;
object discriminatorValue;
if (!TryMatchPropertyEqualsValue(term, filterExpression.Input.VariableName, out currentDiscriminator, out discriminatorValue)) { return false; }
// must be the same discriminator in every case
if (null == discriminatorProperty) { discriminatorProperty = (EdmProperty)currentDiscriminator.Property; }
else if (discriminatorProperty != currentDiscriminator.Property) { return false; }
discriminatorDomain.Add(discriminatorValue);
}
var typeMap = new List>();
var propertyMap = new Dictionary();
var relPropertyMap = new Dictionary();
var typeToRelPropertyMap = new Dictionary>();
DbPropertyExpression discriminator = null;
for (int i = 0; i < caseExpression.When.Count; i++)
{
var when = caseExpression.When[i];
var then = caseExpression.Then[i];
var projectionVariableName = project.Input.VariableName;
DbPropertyExpression currentDiscriminator;
object discriminatorValue;
if (!TryMatchPropertyEqualsValue(when, projectionVariableName, out currentDiscriminator, out discriminatorValue)) { return false; }
// must be the same discriminator in every case
if (null == discriminatorProperty) { discriminatorProperty = (EdmProperty)currentDiscriminator.Property; }
else if (discriminatorProperty != currentDiscriminator.Property) { return false; }
discriminator = currentDiscriminator;
// right hand side must be entity type constructor
EntityType currentType;
if (!TryMatchEntityTypeConstructor(then, propertyMap, relPropertyMap, typeToRelPropertyMap, out currentType)) { return false; }
// remember type + discriminator value
typeMap.Add(new KeyValuePair(discriminatorValue, currentType));
// remove discriminator value from domain
discriminatorDomain.Remove(discriminatorValue);
}
// make sure only one member of discriminator domain remains...
if (1 != discriminatorDomain.Count) { return false; }
// check default case
EntityType elseType;
if (null == caseExpression.Else ||
!TryMatchEntityTypeConstructor(caseExpression.Else, propertyMap, relPropertyMap, typeToRelPropertyMap, out elseType)) { return false; }
typeMap.Add(new KeyValuePair(discriminatorDomain.Single(), elseType));
// Account for cases where some type in the hierarchy specifies a rel-property, but another
// type in the hierarchy does not
if (!CheckForMissingRelProperties(relPropertyMap, typeToRelPropertyMap))
{
return false;
}
// since the store may right-pad strings, ensure discriminator values are unique in their trimmed
// form
var discriminatorValues = typeMap.Select(map => map.Key);
int uniqueValueCount = discriminatorValues.Distinct(TrailingSpaceComparer.Instance).Count();
int valueCount = typeMap.Count;
if (uniqueValueCount != valueCount) { return false; }
discriminatorMap = new DiscriminatorMap(discriminator, typeMap, propertyMap, relPropertyMap, entitySet);
return true;
}
private static bool CheckForMissingRelProperties(
Dictionary relPropertyMap,
Dictionary> typeToRelPropertyMap)
{
// Easily the lousiest implementation of this search.
// Check to see that for each relProperty that we see in the relPropertyMap
// (presumably because some type constructor specified it), every type for
// which that rel-property is specified *must* also have specified it.
// We don't need to check for equivalence here - because that's already been
// checked
foreach (Query.InternalTrees.RelProperty relProperty in relPropertyMap.Keys)
{
foreach (KeyValuePair> kv in typeToRelPropertyMap)
{
if (kv.Key.IsSubtypeOf(relProperty.FromEnd.TypeUsage.EdmType))
{
if (!kv.Value.Contains(relProperty))
{
return false;
}
}
}
}
return true;
}
private static bool TryMatchPropertyEqualsValue(DbExpression expression, string propertyVariable, out DbPropertyExpression property, out object value)
{
property = null;
value = null;
// make sure when is of the form Discriminator = Constant
if (expression.ExpressionKind != DbExpressionKind.Equals) { return false; }
var equals = (DbBinaryExpression)expression;
if (equals.Left.ExpressionKind != DbExpressionKind.Property) { return false; }
property = (DbPropertyExpression)equals.Left;
if (!TryMatchConstant(equals.Right, out value)) { return false; }
// verify the property is a property of the input variable
if (property.Instance.ExpressionKind != DbExpressionKind.VariableReference ||
((DbVariableReferenceExpression)property.Instance).VariableName != propertyVariable) { return false; }
return true;
}
private static bool TryMatchConstant(DbExpression expression, out object value)
{
if (expression.ExpressionKind == DbExpressionKind.Constant)
{
value = ((DbConstantExpression)expression).Value;
return true;
}
if (expression.ExpressionKind == DbExpressionKind.Cast &&
expression.ResultType.EdmType.BuiltInTypeKind == BuiltInTypeKind.PrimitiveType)
{
var castExpression = (DbCastExpression)expression;
if (TryMatchConstant(castExpression.Argument, out value))
{
// convert the value
var primitiveType = (PrimitiveType)expression.ResultType.EdmType;
// constant literals have already been validated by view gen...
value = Convert.ChangeType(value, primitiveType.ClrEquivalentType, CultureInfo.InvariantCulture);
return true;
}
}
value = null;
return false;
}
private static IEnumerable FlattenOr(DbExpression expression)
{
if (expression.ExpressionKind == DbExpressionKind.Or)
{
return CommandTreeUtils.FlattenAssociativeExpression(expression);
}
return new DbExpression[] { expression };
}
private static bool TryMatchEntityTypeConstructor(DbExpression then,
Dictionary propertyMap,
Dictionary relPropertyMap,
Dictionary> typeToRelPropertyMap,
out EntityType entityType)
{
if (then.ExpressionKind != DbExpressionKind.NewInstance)
{
entityType = null;
return false;
}
var constructor = (DbNewInstanceExpression)then;
entityType = (EntityType)constructor.ResultType.EdmType;
// process arguments to constructor (must be aligned across all case statements)
Debug.Assert(entityType.Properties.Count == constructor.Arguments.Count, "invalid new instance");
for (int j = 0; j < entityType.Properties.Count; j++)
{
var property = entityType.Properties[j];
var assignment = constructor.Arguments[j];
DbExpression existingAssignment;
if (propertyMap.TryGetValue(property, out existingAssignment))
{
if (!ExpressionsCompatible(assignment, existingAssignment)) { return false; }
}
else
{
propertyMap.Add(property, assignment);
}
}
// Now handle the rel properties
if (constructor.HasRelatedEntityReferences)
{
List relPropertyList;
if (!typeToRelPropertyMap.TryGetValue(entityType, out relPropertyList))
{
relPropertyList = new List();
typeToRelPropertyMap[entityType] = relPropertyList;
}
foreach (DbRelatedEntityRef relatedRef in constructor.RelatedEntityReferences)
{
Query.InternalTrees.RelProperty relProperty = new System.Data.Query.InternalTrees.RelProperty((RelationshipType)relatedRef.TargetEnd.DeclaringType,
relatedRef.SourceEnd, relatedRef.TargetEnd);
DbExpression assignment = relatedRef.TargetEntityReference;
DbExpression existingAssignment;
if (relPropertyMap.TryGetValue(relProperty, out existingAssignment))
{
if (!ExpressionsCompatible(assignment, existingAssignment)) { return false; }
}
else
{
relPropertyMap.Add(relProperty, assignment);
}
relPropertyList.Add(relProperty);
}
}
return true;
}
///
/// Utility method determining whether two expressions appearing within the same scope
/// are equivalent. May return false negatives, but no false positives. In other words,
///
/// x != y --> !ExpressionsCompatible(x, y)
///
/// but does not guarantee
///
/// x == y --> ExpressionsCompatible(x, y)
///
private static bool ExpressionsCompatible(DbExpression x, DbExpression y)
{
if (x.ExpressionKind != y.ExpressionKind) { return false; }
switch (x.ExpressionKind)
{
case DbExpressionKind.Property:
{
var prop1 = (DbPropertyExpression)x;
var prop2 = (DbPropertyExpression)y;
return prop1.Property == prop2.Property &&
ExpressionsCompatible(prop1.Instance, prop2.Instance);
}
case DbExpressionKind.VariableReference:
return ((DbVariableReferenceExpression)x).VariableName ==
((DbVariableReferenceExpression)y).VariableName;
case DbExpressionKind.NewInstance:
{
var newX = (DbNewInstanceExpression)x;
var newY = (DbNewInstanceExpression)y;
if (!newX.ResultType.EdmType.EdmEquals(newY.ResultType.EdmType)) { return false; }
for (int i = 0; i < newX.Arguments.Count; i++)
{
if (!ExpressionsCompatible(newX.Arguments[i], newY.Arguments[i]))
{
return false;
}
}
return true;
}
case DbExpressionKind.Ref:
{
DbRefExpression refX = (DbRefExpression)x;
DbRefExpression refY = (DbRefExpression)y;
return (refX.EntitySet.EdmEquals(refY.EntitySet) &&
ExpressionsCompatible(refX.Argument, refY.Argument));
}
default:
// here come the false negatives...
return false;
}
}
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.