|
//---------------------------------------------------------------------
// <copyright file="ViewSimplifier.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner Microsoft, Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
using System.Data.Common.CommandTrees;
using System.Collections.Generic;
using System.Data.Metadata.Edm;
using System.Diagnostics;
using System.Data.Common.Utils;
using System.Linq;
using System.Globalization;
using System.Data.Common.CommandTrees.ExpressionBuilder;
using System.Data.Common.CommandTrees.Internal;
namespace System.Data.Common.CommandTrees.Internal
{
/// <summary>
/// Utility class that walks a mapping view and returns a simplified expression with projection
/// nodes collapsed. Specifically recognizes the following common pattern in mapping views:
///
/// outerProject(outerBinding(innerProject(innerBinding, innerNew)), outerProjection)
///
/// Recognizes simple disciminator patterns of the form:
///
/// select
/// case when Disc = value1 then value Type1(...)
/// case when Disc = value2 then value Type2(...)
/// ...
///
/// Recognizes redundant case statement of the form:
///
/// select
/// case when (case when Predicate1 then true else false) ...
///
/// </summary>
internal class ViewSimplifier
{
internal static DbQueryCommandTree SimplifyView(EntitySetBase extent, DbQueryCommandTree view)
{
ViewSimplifier vs = new ViewSimplifier(view.MetadataWorkspace, extent);
view = vs.Simplify(view);
return view;
}
private readonly MetadataWorkspace metadata;
private readonly EntitySetBase extent;
private ViewSimplifier(MetadataWorkspace mws, EntitySetBase viewTarget)
{
this.metadata = mws;
this.extent = viewTarget;
}
private DbQueryCommandTree Simplify(DbQueryCommandTree view)
{
var simplifier = PatternMatchRuleProcessor.Create(
// determines if an expression is of the form outerProject(outerProjection(innerProject(innerNew)))
PatternMatchRule.Create(Pattern_CollapseNestedProjection, ViewSimplifier.CollapseNestedProjection),
// A case statement can potentially be simplified
PatternMatchRule.Create(Pattern_Case, ViewSimplifier.SimplifyCaseStatement),
// Nested TPH discriminator pattern can be converted to the expected TPH discriminator pattern
PatternMatchRule.Create(Pattern_NestedTphDiscriminator, ViewSimplifier.SimplifyNestedTphDiscriminator),
// Entity constructors may be augmented with FK-based related entity refs
PatternMatchRule.Create(Pattern_EntityConstructor, this.AddFkRelatedEntityRefs)
);
DbExpression queryExpression = view.Query;
queryExpression = simplifier(queryExpression);
view = DbQueryCommandTree.FromValidExpression(view.MetadataWorkspace, view.DataSpace, queryExpression);
return view;
}
#region Navigation simplification support by adding FK-based related entity refs
private static readonly Func<DbExpression, bool> Pattern_EntityConstructor =
Patterns.MatchProject(
Patterns.AnyExpression,
Patterns.And(
Patterns.MatchEntityType,
Patterns.Or
(
Patterns.MatchNewInstance(),
Patterns.MatchCase(Patterns.AnyExpressions, Patterns.MatchForAll(Patterns.MatchNewInstance()), Patterns.MatchNewInstance())
)
)
);
private bool doNotProcess;
private DbExpression AddFkRelatedEntityRefs(DbExpression viewConstructor)
{
// If the extent being simplified is not a C-Space entity set, or if it has already
// been processed by the simplifier, then keep the original expression by returning
// null.
//
if (this.doNotProcess)
{
return null;
}
if(this.extent.BuiltInTypeKind != BuiltInTypeKind.EntitySet ||
this.extent.EntityContainer.DataSpace != DataSpace.CSpace)
{
this.doNotProcess = true;
return null;
}
// Get a reference to the entity set being simplified, and find all the foreign key
// (foreign key) associations for which the association set references that entity set,
// with either association end.
//
EntitySet targetSet = (EntitySet)this.extent;
var relSets =
targetSet.EntityContainer.BaseEntitySets
.Where(es => es.BuiltInTypeKind == BuiltInTypeKind.AssociationSet)
.Cast<AssociationSet>()
.Where(assocSet =>
assocSet.ElementType.IsForeignKey &&
assocSet.AssociationSetEnds.Any(se => se.EntitySet == targetSet)
)
.ToList();
// If no foreign key association sets that reference the entity set are present, then
// no further processing is necessary, because FK-based related entity references cannot
// be computed and added to the entities constructed for the entity set.
if (relSets.Count == 0)
{
this.doNotProcess = true;
return null;
}
// For every relationship set that references this entity set, the relationship type and
// foreign key constraint are used to determine if the entity set is the dependent set.
// If it is the dependent set, then it is possible to augment the view definition with a
// related entity ref that represents the navigation of the relationship set's relationship
// from the dependent end (this entity set) to the the principal end (the entity set that
// is referenced by the other association set end of the relationship set).
//
var principalSetsAndDependentTypes = new HashSet<Tuple<EntityType, AssociationSetEnd, ReferentialConstraint>>();
foreach (AssociationSet relSet in relSets)
{
// Retrieve the single referential constraint from the foreign key association, and
// use it to determine whether the association set end that represents the dependent
// end of the association references this entity set.
//
var fkConstraint = relSet.ElementType.ReferentialConstraints[0];
var dependentSetEnd = relSet.AssociationSetEnds[fkConstraint.ToRole.Name];
if (dependentSetEnd.EntitySet == targetSet)
{
EntityType requiredSourceNavType = (EntityType)TypeHelpers.GetEdmType<RefType>(dependentSetEnd.CorrespondingAssociationEndMember.TypeUsage).ElementType;
var principalSetEnd = relSet.AssociationSetEnds[fkConstraint.FromRole.Name];
// Record the entity type that an element of this dependent entity set must have in order
// to be a valid navigation source for the relationship set's relationship, along with the
// association set end for the destination (principal) end of the navigation and the FK
// constraint that is associated with the relationship type. This information may be used
// later to construct a related entity ref for any entity constructor expression in the view
// that produces an entity of the required source type or a subtype.
//
principalSetsAndDependentTypes.Add(Tuple.Create(requiredSourceNavType, principalSetEnd, fkConstraint));
}
}
// If no foreign key association sets that use the entity set as the dependent set are present,
// then no further processing is possible, since FK-based related entity refs can only be added
// to the view definition for navigations from the dependent end of the relationship to the principal.
//
if (principalSetsAndDependentTypes.Count == 0)
{
this.doNotProcess = true;
return null;
}
// This rule supports a view that is capped with a projection of the form
// (input).Project(x => new Entity())
// or
// (input).Project(x => CASE WHEN (condition1) THEN new Entity1() ELSE WHEN (condition2) THEN new Entity2()... ELSE new EntityN())
// where every new instance expression Entity1()...EntityN() constructs an entity of a type
// that is compatible with the entity set's element type.
// Here, the list of all DbNewInstanceExpressions contained in the projection is remembered,
// along with any CASE statement conditions, if present. These expressions will be updated
// if necessary and used to build a new capping projection if any of the entity constructors
// are augmented with FK-based related entity references.
//
DbProjectExpression entityProject = (DbProjectExpression)viewConstructor;
List<DbNewInstanceExpression> constructors = new List<DbNewInstanceExpression>();
List<DbExpression> conditions = null;
if (entityProject.Projection.ExpressionKind == DbExpressionKind.Case)
{
// If the projection is a DbCaseExpression, then every result must be a DbNewInstanceExpression
DbCaseExpression discriminatedConstructor = (DbCaseExpression)entityProject.Projection;
conditions = new List<DbExpression>(discriminatedConstructor.When.Count);
for (int idx = 0; idx < discriminatedConstructor.When.Count; idx++)
{
conditions.Add(discriminatedConstructor.When[idx]);
constructors.Add((DbNewInstanceExpression)discriminatedConstructor.Then[idx]);
}
constructors.Add((DbNewInstanceExpression)discriminatedConstructor.Else);
}
else
{
// Otherwise, the projection must be a single DbNewInstanceExpression
constructors.Add((DbNewInstanceExpression)entityProject.Projection);
}
bool rebuildView = false;
for (int idx = 0; idx < constructors.Count; idx++)
{
DbNewInstanceExpression entityConstructor = constructors[idx];
EntityType constructedEntityType = TypeHelpers.GetEdmType<EntityType>(entityConstructor.ResultType);
List<DbRelatedEntityRef> relatedRefs =
principalSetsAndDependentTypes
.Where(psdt => constructedEntityType == psdt.Item1 || constructedEntityType.IsSubtypeOf(psdt.Item1))
.Select(psdt => RelatedEntityRefFromAssociationSetEnd(constructedEntityType, entityConstructor, psdt.Item2, psdt.Item3)).ToList();
if (relatedRefs.Count > 0)
{
if (entityConstructor.HasRelatedEntityReferences)
{
relatedRefs = entityConstructor.RelatedEntityReferences.Concat(relatedRefs).ToList();
}
entityConstructor = DbExpressionBuilder.CreateNewEntityWithRelationshipsExpression(constructedEntityType, entityConstructor.Arguments, relatedRefs);
constructors[idx] = entityConstructor;
rebuildView = true;
}
}
// Default to returning null to indicate that this rule did not produce a modified expression
//
DbExpression result = null;
if (rebuildView)
{
// rebuildView is true, so entity constructing DbNewInstanceExpression(s) were encountered
// and updated with additional related entity refs. The DbProjectExpression that caps the
// view definition therefore needs to be rebuilt and returned as the result of this rule.
//
if (conditions != null)
{
// The original view definition projection was a DbCaseExpression.
// The new expression is also a DbCaseExpression that uses the conditions from the
// original expression together with the updated result expressions to produce the
// new capping projection.
//
List<DbExpression> whens = new List<DbExpression>(conditions.Count);
List<DbExpression> thens = new List<DbExpression>(conditions.Count);
for (int idx = 0; idx < conditions.Count; idx++)
{
whens.Add(conditions[idx]);
thens.Add(constructors[idx]);
}
result = entityProject.Input.Project(DbExpressionBuilder.Case(whens, thens, constructors[conditions.Count]));
}
else
{
// Otherwise, the capping projection consists entirely of the updated DbNewInstanceExpression.
//
result = entityProject.Input.Project(constructors[0]);
}
}
// Regardless of whether or not the view was updated, this rule should not be applied again during rule processing
this.doNotProcess = true;
return result;
}
private static DbRelatedEntityRef RelatedEntityRefFromAssociationSetEnd(EntityType constructedEntityType, DbNewInstanceExpression entityConstructor, AssociationSetEnd principalSetEnd, ReferentialConstraint fkConstraint)
{
EntityType principalEntityType = (EntityType)TypeHelpers.GetEdmType<RefType>(fkConstraint.FromRole.TypeUsage).ElementType;
IList<DbExpression> principalKeyValues = null;
// Create Entity Property/DbExpression value pairs from the entity constructor DbExpression,
// then join these with the principal/dependent property pairs from the FK constraint
// to produce principal property name/DbExpression value pairs from which to create the principal ref.
//
// Ideally the code would be as below, but anonymous types break asmmeta:
//var keyPropAndValue =
// from pv in constructedEntityType.Properties.Select((p, idx) => new { DependentProperty = p, Value = entityConstructor.Arguments[idx] })
// join ft in fkConstraint.FromProperties.Select((fp, idx) => new { PrincipalProperty = fp, DependentProperty = fkConstraint.ToProperties[idx] })
// on pv.DependentProperty equals ft.DependentProperty
// select new { PrincipalProperty = ft.PrincipalProperty.Name, Value = pv.Value };
//
var keyPropAndValue =
from pv in constructedEntityType.Properties.Select((p, idx) => Tuple.Create(p, entityConstructor.Arguments[idx])) // new { DependentProperty = p, Value = entityConstructor.Arguments[idx] })
join ft in fkConstraint.FromProperties.Select((fp, idx) => Tuple.Create(fp, fkConstraint.ToProperties[idx])) //new { PrincipalProperty = fp, DependentProperty = fkConstraint.ToProperties[idx] })
on pv.Item1 equals ft.Item2 //pv.DependentProperty equals ft.DependentProperty
select Tuple.Create(ft.Item1.Name, pv.Item2); // new { PrincipalProperty = ft.PrincipalProperty.Name, Value = pv.Value };
// If there is only a single property in the principal's key, then there is no ordering concern.
// Otherwise, create a dictionary of principal key property name to DbExpression value so that
// when used as the arguments to the ref expression, the dependent property values - used here
// as principal key property values - are in the correct order, which is the same order as the
// key members themselves.
//
if (fkConstraint.FromProperties.Count == 1)
{
var singleKeyNameAndValue = keyPropAndValue.Single();
Debug.Assert(singleKeyNameAndValue.Item1 == fkConstraint.FromProperties[0].Name, "Unexpected single key property name");
principalKeyValues = new[] { singleKeyNameAndValue.Item2 };
}
else
{
var keyValueMap = keyPropAndValue.ToDictionary(pav => pav.Item1, pav => pav.Item2, StringComparer.Ordinal);
principalKeyValues = principalEntityType.KeyMemberNames.Select(memberName => keyValueMap[memberName]).ToList();
}
// Create the ref to the principal entity based on the (now correctly ordered) key value expressions.
//
DbRefExpression principalRef = principalSetEnd.EntitySet.CreateRef(principalEntityType, principalKeyValues);
DbRelatedEntityRef result = DbExpressionBuilder.CreateRelatedEntityRef(fkConstraint.ToRole, fkConstraint.FromRole, principalRef);
return result;
}
#endregion
#region Nested TPH Discriminator simplification
/// <summary>
/// Matches the nested TPH discriminator pattern produced by view generation
/// </summary>
private static readonly Func<DbExpression, bool> Pattern_NestedTphDiscriminator =
Patterns.MatchProject(
Patterns.MatchFilter(
Patterns.MatchProject(
Patterns.MatchFilter(
Patterns.AnyExpression,
Patterns.Or(
Patterns.MatchKind(DbExpressionKind.Equals),
Patterns.MatchKind(DbExpressionKind.Or)
)
),
Patterns.And(
Patterns.MatchRowType,
Patterns.MatchNewInstance(
Patterns.MatchForAll(
Patterns.Or(
Patterns.And(
Patterns.MatchNewInstance(),
Patterns.MatchComplexType
),
Patterns.MatchKind(DbExpressionKind.Property),
Patterns.MatchKind(DbExpressionKind.Case)
)
)
)
)
),
Patterns.Or(
Patterns.MatchKind(DbExpressionKind.Property),
Patterns.MatchKind(DbExpressionKind.Or)
)
),
Patterns.And(
Patterns.MatchEntityType,
Patterns.MatchCase(
Patterns.MatchForAll(Patterns.MatchKind(DbExpressionKind.Property)),
Patterns.MatchForAll(Patterns.MatchKind(DbExpressionKind.NewInstance)),
Patterns.MatchKind(DbExpressionKind.NewInstance)
)
)
);
/// <summary>
/// Converts the DbExpression equivalent of:
///
/// SELECT CASE
/// WHEN a._from0 THEN SUBTYPE1()
/// ...
/// WHEN a._from[n-2] THEN SUBTYPE_n-1()
/// ELSE SUBTYPE_n
/// FROM
/// SELECT
/// b.C1..., b.Cn
/// CASE WHEN b.Discriminator = SUBTYPE1_Value THEN true ELSE false AS _from0
/// ...
/// CASE WHEN b.Discriminator = SUBTYPE_n_Value THEN true ELSE false AS _from[n-1]
/// FROM TSet AS b
/// WHERE b.Discriminator = SUBTYPE1_Value... OR x.Discriminator = SUBTYPE_n_Value
/// AS a
/// WHERE a._from0... OR a._from[n-1]
///
/// into the DbExpression equivalent of the following, which is matched as a TPH discriminator
/// by the <see cref="System.Data.Mapping.ViewGeneration.GeneratedView"/> class and so allows a <see cref="System.Data.Mapping.ViewGeneration.DiscriminatorMap"/>
/// to be produced for the view, which would not otherwise be possible. Note that C1 through Cn
/// are only allowed to be scalars or complex type constructors based on direct property references
/// to the store entity set's scalar properties.
///
/// SELECT CASE
/// WHEN y.Discriminator = SUBTTYPE1_Value THEN SUBTYPE1()
/// ...
/// WHEN y.Discriminator = SUBTYPE_n-1_Value THEN SUBTYPE_n-1()
/// ELSE SUBTYPE_n()
/// FROM
/// SELECT x.C1..., x.Cn, Discriminator FROM TSet AS x
/// WHERE x.Discriminator = SUBTYPE1_Value... OR x.Discriminator = SUBTYPE_n_Value
/// AS y
///
/// </summary>
private static DbExpression SimplifyNestedTphDiscriminator(DbExpression expression)
{
DbProjectExpression entityProjection = (DbProjectExpression)expression;
DbFilterExpression booleanColumnFilter = (DbFilterExpression)entityProjection.Input.Expression;
DbProjectExpression rowProjection = (DbProjectExpression)booleanColumnFilter.Input.Expression;
DbFilterExpression discriminatorFilter = (DbFilterExpression)rowProjection.Input.Expression;
List<DbExpression> predicates = FlattenOr(booleanColumnFilter.Predicate).ToList();
List<DbPropertyExpression> propertyPredicates =
predicates.OfType<DbPropertyExpression>()
.Where(px => px.Instance.ExpressionKind == DbExpressionKind.VariableReference &&
((DbVariableReferenceExpression)px.Instance).VariableName == booleanColumnFilter.Input.VariableName).ToList();
if (predicates.Count != propertyPredicates.Count)
{
return null;
}
List<string> predicateColumnNames = propertyPredicates.Select(px => px.Property.Name).ToList();
Dictionary<object, DbComparisonExpression> discriminatorPredicates = new Dictionary<object, DbComparisonExpression>();
if (!TypeSemantics.IsEntityType(discriminatorFilter.Input.VariableType) ||
!TryMatchDiscriminatorPredicate(discriminatorFilter, (compEx, discValue) => discriminatorPredicates.Add(discValue, compEx)))
{
return null;
}
EdmProperty discriminatorProp = (EdmProperty)((DbPropertyExpression)((DbComparisonExpression)discriminatorPredicates.First().Value).Left).Property;
DbNewInstanceExpression rowConstructor = (DbNewInstanceExpression)rowProjection.Projection;
RowType resultRow = TypeHelpers.GetEdmType<RowType>(rowConstructor.ResultType);
Dictionary<string, DbComparisonExpression> inputPredicateMap = new Dictionary<string, DbComparisonExpression>();
Dictionary<string, DbComparisonExpression> selectorPredicateMap = new Dictionary<string, DbComparisonExpression>();
Dictionary<string, DbExpression> columnValues = new Dictionary<string, DbExpression>(rowConstructor.Arguments.Count);
for (int idx = 0; idx < rowConstructor.Arguments.Count; idx++)
{
string propName = resultRow.Properties[idx].Name;
DbExpression columnVal = rowConstructor.Arguments[idx];
if (predicateColumnNames.Contains(propName))
{
if(columnVal.ExpressionKind != DbExpressionKind.Case)
{
return null;
}
DbCaseExpression casePredicate = (DbCaseExpression)columnVal;
if(casePredicate.When.Count != 1 ||
!TypeSemantics.IsBooleanType(casePredicate.Then[0].ResultType) || !TypeSemantics.IsBooleanType(casePredicate.Else.ResultType) ||
casePredicate.Then[0].ExpressionKind != DbExpressionKind.Constant || casePredicate.Else.ExpressionKind != DbExpressionKind.Constant ||
(bool)((DbConstantExpression)casePredicate.Then[0]).Value != true || (bool)((DbConstantExpression)casePredicate.Else).Value != false)
{
return null;
}
DbPropertyExpression comparedProp;
object constValue;
if(!TryMatchPropertyEqualsValue(casePredicate.When[0], rowProjection.Input.VariableName, out comparedProp, out constValue) ||
comparedProp.Property != discriminatorProp ||
!discriminatorPredicates.ContainsKey(constValue))
{
return null;
}
inputPredicateMap.Add(propName, discriminatorPredicates[constValue]);
selectorPredicateMap.Add(propName, (DbComparisonExpression)casePredicate.When[0]);
}
else
{
columnValues.Add(propName, columnVal);
}
}
// Build a new discriminator-based filter that only includes the same rows allowed by the higher '_from0' column-based filter
DbExpression newDiscriminatorPredicate = Helpers.BuildBalancedTreeInPlace<DbExpression>(new List<DbExpression>(inputPredicateMap.Values), (left, right) => DbExpressionBuilder.Or(left, right));
discriminatorFilter = discriminatorFilter.Input.Filter(newDiscriminatorPredicate);
DbCaseExpression entitySelector = (DbCaseExpression)entityProjection.Projection;
List<DbExpression> newWhens = new List<DbExpression>(entitySelector.When.Count);
List<DbExpression> newThens = new List<DbExpression>(entitySelector.Then.Count);
for (int idx = 0; idx < entitySelector.When.Count; idx++)
{
DbPropertyExpression propWhen = (DbPropertyExpression)entitySelector.When[idx];
DbNewInstanceExpression entityThen = (DbNewInstanceExpression)entitySelector.Then[idx];
DbComparisonExpression discriminatorWhen;
if (!selectorPredicateMap.TryGetValue(propWhen.Property.Name, out discriminatorWhen))
{
return null;
}
newWhens.Add(discriminatorWhen);
DbExpression inputBoundEntityConstructor = ValueSubstituter.Substitute(entityThen, entityProjection.Input.VariableName, columnValues);
newThens.Add(inputBoundEntityConstructor);
}
DbExpression newElse = ValueSubstituter.Substitute(entitySelector.Else, entityProjection.Input.VariableName, columnValues);
DbCaseExpression newEntitySelector = DbExpressionBuilder.Case(newWhens, newThens, newElse);
DbExpression result = discriminatorFilter.BindAs(rowProjection.Input.VariableName).Project(newEntitySelector);
return result;
}
private class ValueSubstituter : DefaultExpressionVisitor
{
internal static DbExpression Substitute(DbExpression original, string referencedVariable, Dictionary<string, DbExpression> propertyValues)
{
Debug.Assert(original != null, "Original expression cannot be null");
ValueSubstituter visitor = new ValueSubstituter(referencedVariable, propertyValues);
return visitor.VisitExpression(original);
}
private readonly string variableName;
private readonly Dictionary<string, DbExpression> replacements;
private ValueSubstituter(string varName, Dictionary<string, DbExpression> replValues)
{
Debug.Assert(varName != null, "Variable name cannot be null");
Debug.Assert(replValues != null, "Replacement values cannot be null");
this.variableName = varName;
this.replacements = replValues;
}
public override DbExpression Visit(DbPropertyExpression expression)
{
DbExpression result = null;
DbExpression replacementValue;
if (expression.Instance.ExpressionKind == DbExpressionKind.VariableReference &&
(((DbVariableReferenceExpression)expression.Instance).VariableName == this.variableName) &&
this.replacements.TryGetValue(expression.Property.Name, out replacementValue))
{
result = replacementValue;
}
else
{
result = base.Visit(expression);
}
return result;
}
}
#endregion
#region Case Statement Simplification
/// <summary>
/// Matches any Case expression
/// </summary>
private static readonly Func<DbExpression, bool> Pattern_Case = Patterns.MatchKind(DbExpressionKind.Case);
private static DbExpression SimplifyCaseStatement(DbExpression expression)
{
DbCaseExpression caseExpression = (DbCaseExpression)expression;
// try simplifying predicates
bool predicateSimplified = false;
List<DbExpression> rewrittenPredicates = new List<DbExpression>(caseExpression.When.Count);
foreach (var when in caseExpression.When)
{
DbExpression simplifiedPredicate;
if (TrySimplifyPredicate(when, out simplifiedPredicate))
{
rewrittenPredicates.Add(simplifiedPredicate);
predicateSimplified = true;
}
else
{
rewrittenPredicates.Add(when);
}
}
if (!predicateSimplified) { return null; }
caseExpression = DbExpressionBuilder.Case(rewrittenPredicates, caseExpression.Then, caseExpression.Else);
return caseExpression;
}
private static bool TrySimplifyPredicate(DbExpression predicate, out DbExpression simplified)
{
simplified = null;
if (predicate.ExpressionKind != DbExpressionKind.Case) { return false; }
var caseExpression = (DbCaseExpression)predicate;
if (caseExpression.Then.Count != 1 && caseExpression.Then[0].ExpressionKind == DbExpressionKind.Constant)
{
return false;
}
var then = (DbConstantExpression)caseExpression.Then[0];
if (!true.Equals(then.Value)) { return false; }
if (caseExpression.Else != null)
{
if (caseExpression.Else.ExpressionKind != DbExpressionKind.Constant) { return false; }
var when = (DbConstantExpression)caseExpression.Else;
if (!false.Equals(when.Value)) { return false; }
}
simplified = caseExpression.When[0];
return true;
}
#endregion
#region Nested Projection Collapsing
/// <summary>
/// Determines if an expression is of the form outerProject(outerProjection(innerProject(innerNew)))
/// </summary>
private static readonly Func<DbExpression, bool> Pattern_CollapseNestedProjection =
Patterns.MatchProject(
Patterns.MatchProject(
Patterns.AnyExpression,
Patterns.MatchKind(DbExpressionKind.NewInstance)
),
Patterns.AnyExpression
);
/// <summary>
/// Collapses outerProject(outerProjection(innerProject(innerNew)))
/// </summary>
private static DbExpression CollapseNestedProjection(DbExpression expression)
{
DbProjectExpression outerProject = (DbProjectExpression)expression;
DbExpression outerProjection = outerProject.Projection;
DbProjectExpression innerProject = (DbProjectExpression)outerProject.Input.Expression;
DbNewInstanceExpression innerNew = (DbNewInstanceExpression)innerProject.Projection;
// get membername -> expression bindings for the inner select so that we know how map property
// references to the inner projection
Dictionary<string, DbExpression> bindings = new Dictionary<string, DbExpression>(innerNew.Arguments.Count);
TypeUsage innerResultTypeUsage = innerNew.ResultType;
RowType innerResultType = (RowType)innerResultTypeUsage.EdmType;
for (int ordinal = 0; ordinal < innerResultType.Members.Count; ordinal++)
{
bindings[innerResultType.Members[ordinal].Name] = innerNew.Arguments[ordinal];
}
// initialize an expression visitor that knows how to map arguments to the outer projection
// to the inner projection source
ProjectionCollapser collapser = new ProjectionCollapser(bindings, outerProject.Input);
// replace all property references to the inner projection
var replacementOuterProjection = collapser.CollapseProjection(outerProjection);
// make sure the collapsing was successful; if not, give up on simplification
if (collapser.IsDoomed) { return null; }
// set replacement value so that the expression replacer infrastructure can substitute
// the collapsed projection in the expression tree
// continue collapsing projection until the pattern no longer matches
DbProjectExpression replacementOuterProject = innerProject.Input.Project(replacementOuterProjection);
return replacementOuterProject;
}
/// <summary>
/// This expression visitor supports collapsing a nested projection matching the pattern described above.
///
/// For instance:
///
/// select T.a as x, T.b as y, true as z from (select E.a as x, E.b as y from Extent E)
///
/// resolves to:
///
/// select E.a, E.b, true as z from Extent E
///
/// In general,
///
/// outerProject(
/// outerBinding(
/// innerProject(innerBinding, innerNew)
/// ),
/// outerNew)
///
/// resolves to:
///
/// replacementOuterProject(
/// innerBinding,
/// replacementOuterNew)
///
/// The outer projection is bound to the inner input source (outerBinding -> innerBinding) and
/// the outer new instance expression has its properties remapped to the inner new instance
/// expression member expressions.
///
/// This replacer is used to simplify argument value in a new instance expression OuterNew
/// from an expression of the form:
///
/// outerProject(outerBinding(innerProject(innerBinding, innerNew)), outerProjection)
///
/// The replacer collapses the outer project terms to point at the innerNew expression.
/// Where possible, VarRef_outer.Property_outer is collapsed to VarRef_inner.Property.
/// </summary>
private class ProjectionCollapser : DefaultExpressionVisitor
{
// the replacer context keeps track of member bindings for var refs and the expression
// binding for the outer projection being remapped
private Dictionary<string, DbExpression> m_varRefMemberBindings;
private DbExpressionBinding m_outerBinding;
private bool m_doomed;
internal ProjectionCollapser(Dictionary<string, DbExpression> varRefMemberBindings,
DbExpressionBinding outerBinding)
: base()
{
m_varRefMemberBindings = varRefMemberBindings;
m_outerBinding = outerBinding;
}
// Visit and identify the Property(VarRef "Outer binding") pattern,
// remapping the property to the appropriate inner projection member
internal DbExpression CollapseProjection(DbExpression expression)
{
return this.VisitExpression(expression);
}
public override DbExpression Visit(DbPropertyExpression property)
{
// check for a property of the outer projection binding (that can be remapped)
if (property.Instance.ExpressionKind == DbExpressionKind.VariableReference &&
IsOuterBindingVarRef((DbVariableReferenceExpression)property.Instance))
{
return m_varRefMemberBindings[property.Property.Name];
}
return base.Visit(property);
}
public override DbExpression Visit(DbVariableReferenceExpression varRef)
{
// if we encounter an unsubstitutued var ref, give up...
if (IsOuterBindingVarRef(varRef))
{
m_doomed = true;
}
return base.Visit(varRef);
}
/// <summary>
/// Heuristic check to make sure the var ref is the one we're supposed to be replacing.
/// </summary>
private bool IsOuterBindingVarRef(DbVariableReferenceExpression varRef)
{
return varRef.VariableName == m_outerBinding.VariableName;
}
/// <summary>
/// Returns a value indicating that the transformation has failed.
/// </summary>
internal bool IsDoomed
{
get { return m_doomed; }
}
}
#endregion
#region Utility Methods
internal static IEnumerable<DbExpression> FlattenOr(DbExpression expression)
{
return Helpers.GetLeafNodes(expression,
exp => (exp.ExpressionKind != DbExpressionKind.Or),
exp => { DbOrExpression orExp = (DbOrExpression)exp; return new[] { orExp.Left, orExp.Right }; });
}
internal static bool TryMatchDiscriminatorPredicate(DbFilterExpression filter, Action<DbComparisonExpression, object> onMatchedComparison)
{
EdmProperty discriminatorProperty = null;
// check each assignment in predicate
foreach (var term in FlattenOr(filter.Predicate))
{
DbPropertyExpression currentDiscriminator;
object discriminatorValue;
if (!TryMatchPropertyEqualsValue(term, filter.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;
}
onMatchedComparison((DbComparisonExpression)term, discriminatorValue);
}
return true;
}
internal 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;
}
#endregion
}
}
|