Code:
/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / ndp / fx / src / DataEntity / System / Data / Objects / ELinq / ExpressionConverter.cs / 5 / ExpressionConverter.cs
//----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// @owner [....]
//---------------------------------------------------------------------
using CqtExpression = System.Data.Common.CommandTrees.DbExpression;
using LinqExpression = System.Linq.Expressions.Expression;
using System.Linq.Expressions;
using System.Collections.ObjectModel;
using System.Linq;
using System.Collections.Generic;
using System.Data.Common.CommandTrees;
using System.Data.Metadata.Edm;
using System.Reflection;
using System.Data.Common.EntitySql;
using System.Diagnostics;
using System.Data.Common;
using System.Globalization;
using System.Data.Common.Utils;
using System.Data.Objects.DataClasses;
using System.Data.Objects.Internal;
using System.Collections;
using System.Data.Entity;
using System.Text;
using System.Data.Common.CommandTrees.Internal;
namespace System.Data.Objects.ELinq
{
///
/// Class supporting conversion of LINQ expressions to EDM CQT expressions.
///
internal sealed partial class ExpressionConverter
{
#region Fields
private readonly ObjectContext _context;
private readonly DbCommandTree _commandTree;
private readonly TypeResolver _typeResolver;
private readonly BindingContext _bindingContext;
private readonly Dictionary _groupByDefaultToOptimizedTranslationMap;
private readonly Dictionary _variableNameToInputExpression;
private readonly Dictionary> _aggregateDefaultTranslationToOptimizedTranslationInfoMap;
private readonly Expression _expression;
private readonly Expression _innerExpression;
private readonly HashSet _closureCandidates = new HashSet();
private readonly Stack _linqExpressionStack = new Stack();
private Dictionary _spanMappings;
private MergeOption? _mergeOption;
private List _closureBindings;
private Dictionary _initializers;
private ObjectParameterCollection _parameters;
private Span _span;
private const string s_visualBasicAssemblyFullName =
"Microsoft.VisualBasic, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a";
private static readonly Dictionary s_translators = InitializeTranslators();
internal const string s_entityCollectionCountPropertyName = "Count";
internal const string s_nullableHasValuePropertyName = "HasValue";
internal const string s_nullableValuePropertyName = "Value";
///
/// Gets the name of the key column appearing in ELinq GroupBy projections
///
internal const string KeyColumnName = "Key";
///
/// Gets the name of the group column appearing in ELinq CQTs (used in GroupBy expressions)
///
internal const string GroupColumnName = "Group";
///
/// Gets the name of the parent column appearing in ELinq EntityCollection projections
///
internal const string EntityCollectionOwnerColumnName = "Owner";
///
/// Gets the name of the children column appearing in ELinq EntityCollection projections
///
internal const string EntityCollectionElementsColumnName = "Elements";
///
/// The Edm namespace name, used for canonical functions
///
internal const string EdmNamespaceName = "Edm";
#region Canonical Function Names
private const string Concat = "Concat";
private const string IndexOf = "IndexOf";
private const string Length = "Length";
private const string Right = "Right";
private const string Substring = "Substring";
private const string ToUpper = "ToUpper";
private const string ToLower = "ToLower";
private const string LTrim = "LTrim";
private const string RTrim = "RTrim";
private const string BitwiseAnd = "BitwiseAnd";
private const string BitwiseOr = "BitwiseOr";
private const string BitwiseNot = "BitwiseNot";
private const string BitwiseXor = "BitwiseXor";
private const string CurrentUtcDateTime = "CurrentUtcDateTime";
private const string CurrentDateTimeOffset = "CurrentDateTimeOffset";
private const string CurrentDateTime = "CurrentDateTime";
private const string Year = "Year";
private const string Month = "Month";
private const string Day = "Day";
private const string Hour = "Hour";
private const string Minute = "Minute";
private const string Second = "Second";
private const string Millisecond = "Millisecond";
#endregion
#endregion
#region Constructors and static initializors
internal ExpressionConverter(ObjectContext objectContext, BindingContext bindingContext, DbCommandTree commandTree, Expression toConvert, ObjectParameterCollection sourceParams)
{
Debug.Assert(objectContext != null, "A binding context is required");
Debug.Assert(bindingContext != null, "A binding context is required");
Debug.Assert(commandTree != null, "A command tree is required");
Debug.Assert(toConvert != null, "An expression is required");
_bindingContext = bindingContext;
_context = objectContext;
_commandTree = commandTree;
if (sourceParams != null)
{
foreach (ObjectParameter sourceParam in sourceParams)
{
this.AddParameter(sourceParam);
}
}
// Normalize the expression
_expression = NormalizeExpression(toConvert);
if (_expression.NodeType == ExpressionType.Lambda)
{
_innerExpression = ((LambdaExpression)_expression).Body;
}
else
{
_innerExpression = _expression;
}
_typeResolver = new TypeResolver(_context.Perspective, StringComparer.Ordinal);
_groupByDefaultToOptimizedTranslationMap = new Dictionary();
_aggregateDefaultTranslationToOptimizedTranslationInfoMap = new Dictionary>();
_variableNameToInputExpression = new Dictionary();
}
// initialize translator dictionary (which support identification of translators
// for LINQ expression node types)
private static Dictionary InitializeTranslators()
{
Dictionary translators = new Dictionary();
foreach (Translator translator in GetTranslators())
{
foreach (ExpressionType nodeType in translator.NodeTypes)
{
translators.Add(nodeType, translator);
}
}
return translators;
}
private static IEnumerable GetTranslators()
{
yield return new AndAlsoTranslator();
yield return new OrElseTranslator();
yield return new LessThanTranslator();
yield return new LessThanOrEqualsTranslator();
yield return new GreaterThanTranslator();
yield return new GreaterThanOrEqualsTranslator();
yield return new EqualsTranslator();
yield return new NotEqualsTranslator();
yield return new ConvertTranslator();
yield return new ConstantTranslator();
yield return new NotTranslator();
yield return new MemberAccessTranslator();
yield return new ParameterTranslator();
yield return new MemberInitTranslator();
yield return new NewTranslator();
yield return new AddTranslator();
yield return new ConditionalTranslator();
yield return new DivideTranslator();
yield return new ModuloTranslator();
yield return new SubtractTranslator();
yield return new MultiplyTranslator();
yield return new NegateTranslator();
yield return new UnaryPlusTranslator();
yield return new MethodCallTranslator();
yield return new CoalesceTranslator();
yield return new AsTranslator();
yield return new IsTranslator();
yield return new QuoteTranslator();
yield return new AndTranslator();
yield return new OrTranslator();
yield return new ExclusiveOrTranslator();
yield return new NotSupportedTranslator(
ExpressionType.LeftShift,
ExpressionType.RightShift,
ExpressionType.ArrayLength,
ExpressionType.ArrayIndex,
ExpressionType.Invoke,
ExpressionType.Lambda,
ExpressionType.ListInit,
ExpressionType.NewArrayInit,
ExpressionType.NewArrayBounds,
ExpressionType.Power);
}
#endregion
#region Properties
private EdmItemCollection EdmItemCollection
{
get
{
return (EdmItemCollection)_commandTree.MetadataWorkspace.GetItemCollection(DataSpace.CSpace, true);
}
}
private bool IsCompiledQueryMode { get { return _bindingContext.ObjectContext != null; } }
internal List ClosureBindings { get { return _closureBindings; } }
internal ObjectParameterCollection Parameters { get { return _parameters; } }
internal MergeOption? PropagatedMergeOption { get { return _mergeOption; } }
internal Span PropagatedSpan { get { return _span; } }
#endregion
#region Internal methods
// Convert the LINQ expression to a CQT expression and (optional) Span information.
// Span information will only be present if ObjectQuery instances that specify Spans
// are referenced from the LINQ expression in a manner consistent with the Span combination
// rules, otherwise the Span for the CQT expression will be null.
internal CqtExpression Convert()
{
CqtExpression result = this.TranslateExpression(_expression);
if (!TryGetSpan(result, out _span))
{
_span = null;
}
return result;
}
internal static bool CanTranslatePropertyInfo(PropertyInfo propertyInfo)
{
return MemberAccessTranslator.CanTranslatePropertyInfo(propertyInfo);
}
#endregion
#region Private Methods
private LinqExpression NormalizeExpression(LinqExpression expression)
{
HashSet clientEvaluationCandidates = new HashSet();
LinqMaximalSubtreeNominator.Nominate(expression, clientEvaluationCandidates, ExpressionEvaluator.IsExpressionNodeClientEvaluatable);
expression = LinqTreeNodeEvaluator.Evaluate(expression, clientEvaluationCandidates);
// find all the nodes in the closure branches, storing them in _closureCandidates
LinqMaximalSubtreeNominator.Nominate(expression, _closureCandidates,
e => ExpressionEvaluator.IsExpressionNodeClientEvaluatable(e) || ExpressionEvaluator.IsExpressionNodeAClosure(e));
// normalize the expression (eliminate/rewrite compiler or CLR specific expressions).
LinqExpressionNormalizer normalizer = new LinqExpressionNormalizer();
expression = normalizer.Visit(expression);
return expression;
}
///
/// Adds the specified closure binding, optionally adding the associated
/// ObjectParameter (if any) to the Parameters collection. Parameters
/// have already been copied over if this method is called from
/// ApplyLinqStateTo, which goes through AddClosureBindingWithoutParameter to get here.
/// to copy query state from one Linq ObjectQuery to another, and so
/// ClosureBinding parameters need not be added here in that case.
///
/// The ClosureBinding to add to the set of ClosureBindings
///
/// Specifies whether the ClosureBinding's ObjectParameter (for a ParameterBinding)
/// should be added to the Parameters collection.
///
private void AddClosureBinding(ClosureBinding binding, bool addParam)
{
Debug.Assert(null != binding, "Closure binding cannot be null");
if (_closureBindings == null)
{
_closureBindings = new List();
}
_closureBindings.Add(binding);
if (addParam && null != binding.Parameter)
{
AddParameter(binding.Parameter);
}
}
///
/// Adds a new closure parameter to the query context.
///
///
/// Binding to add to the context.
///
internal void AddClosureBinding(ClosureBinding binding)
{
AddClosureBinding(binding, true);
}
// Requires: metadata must not be null.
//
// Effects: adds initializer metadata to this query context.
//
// Ensures that the given initializer metadata is valid within the current converter context.
// We do not allow two incompatible structures representing the same type within a query, e.g.,
//
// outer.Join(inner, o => new Foo { X = o.ID }, i => new Foo { Y = i.ID }, ...
//
// since this introduces a discrepancy between the CLR (where comparisons between Foo are aware
// of both X and Y) and in ELinq (where comparisons are based on the row structure only), resulting
// in the following join predicates:
//
// Linq: foo1 == foo2 (which presumably amounts to foo1.X == foo2.X && foo1.Y == foo2.Y
// ELinq: foo1.X == foo2.Y
//
// Similar problems occur with set operations such as Union and Concat, where one of the initialization
// patterns may be ignored.
//
// This method performs an overly strict check, requiring that all initializers for a given type
// are structurally equivalent.
internal void ValidateInitializerMetadata(InitializerMetadata metadata)
{
Debug.Assert(null != metadata);
InitializerMetadata existingMetadata;
if (_initializers != null && _initializers.TryGetValue(metadata.ClrType, out existingMetadata))
{
// Verify the initializers are compatible.
if (!metadata.Equals(existingMetadata))
{
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnsupportedHeterogeneousInitializers(
ExpressionConverter.DescribeClrType(metadata.ClrType)));
}
}
else
{
// Register the metadata so that subsequent initializers for this type can be verified.
if (_initializers == null)
{
_initializers = new Dictionary();
}
_initializers.Add(metadata.ClrType, metadata);
}
}
private void AddParameter(ObjectParameter newParam)
{
if (_parameters == null)
{
_parameters = new ObjectParameterCollection(_context.Perspective);
}
_parameters.Add(newParam);
}
private bool IsQueryRoot(Expression linqExpression)
{
//
// An expression is the query root if it was the expression used
// when constructing this converter or, if the root is a LambdaExpression,
// the Body of the expression used when constructing this converter.
//
return (object.ReferenceEquals(_expression, linqExpression) ||
object.ReferenceEquals(_innerExpression, linqExpression));
}
#region Span Mapping maintenance methods
///
/// Adds a new mapping from DbExpression => Span information for the specified expression,
/// after first ensuring that the mapping dictionary has been instantiated.
///
/// The expression for which Span information should be added
///
/// The Span information, which may be null .
/// If null , no attempt is made to update the dictionary of span mappings.
///
/// The original argument, to allow return AddSpanMapping(expression, span) scenarios
private CqtExpression AddSpanMapping(CqtExpression expression, Span span)
{
if (span != null)
{
if (null == _spanMappings)
{
_spanMappings = new Dictionary();
}
_spanMappings[expression] = span;
}
return expression;
}
///
/// Attempts to retrieve Span information for the specified DbExpression.
///
/// The expression for which Span information should be retrieved.
/// Will contain the Span information for the specified expression if it is present in the Span mapping dictionary.
/// true if Span information was retrieved for the specified expression and now contains this information; otherwise false .
private bool TryGetSpan(CqtExpression expression, out Span span)
{
if (_spanMappings != null)
{
return _spanMappings.TryGetValue(expression, out span);
}
span = null;
return false;
}
///
/// Removes the Span mapping entry for the specified expression,
/// and creates a new entry for the specified expression that maps
/// to the expression's original Span information. If no Span
/// information is present for the specified expression then no
/// changes are made to the Span mapping dictionary.
///
/// The expression from which to take Span information
/// The expression to which the Span information should be applied
private void ApplySpanMapping(CqtExpression from, CqtExpression to)
{
Span argumentSpan;
if (TryGetSpan(from, out argumentSpan))
{
AddSpanMapping(to, argumentSpan);
}
}
///
/// Unifies the Span information from the specified and
/// expressions, and applies it to the specified expression. Unification proceeds
/// as follows:
/// - If neither nor have Span information, no changes are made
/// - If one of or has Span information, that single Span information
/// entry is removed from the Span mapping dictionary and used to create a new entry that maps from the
/// expression to the Span information.
/// - If both and have Span information, both entries are removed
/// from the Span mapping dictionary, a new Span is created that contains the union of the original Spans, and
/// a new entry is added to the dictionary that maps from expression to this new Span.
///
/// The first expression argument
/// The second expression argument
/// The result expression
private void UnifySpanMappings(CqtExpression left, CqtExpression right, CqtExpression to)
{
Span leftSpan = null;
Span rightSpan = null;
bool hasLeftSpan = TryGetSpan(left, out leftSpan);
bool hasRightSpan = TryGetSpan(right, out rightSpan);
if (!hasLeftSpan && !hasRightSpan)
{
return;
}
Debug.Assert(leftSpan != null || rightSpan != null, "Span mappings contain null?");
AddSpanMapping(to, Span.CopyUnion(leftSpan, rightSpan));
}
#endregion
// The following methods correspond to query builder methods on ObjectQuery
// and MUST be called by expression translators (instead of calling the equivalent
// CommandTree.CreateXxExpression methods) to ensure that Span information flows
// correctly to the root of the Command Tree as it is constructed by converting
// the LINQ expression tree. Each method correctly maintains a Span mapping (if required)
// for its resulting expression, based on the Span mappings of its argument expression(s).
private DbDistinctExpression Distinct(CqtExpression argument)
{
DbDistinctExpression retExpr = _commandTree.CreateDistinctExpression(argument);
ApplySpanMapping(argument, retExpr);
return retExpr;
}
private DbExceptExpression Except(CqtExpression left, CqtExpression right)
{
DbExceptExpression retExpr = _commandTree.CreateExceptExpression(left, right);
ApplySpanMapping(left, retExpr);
return retExpr;
}
private DbFilterExpression Filter(DbExpressionBinding input, CqtExpression predicate)
{
DbFilterExpression retExpr = _commandTree.CreateFilterExpression(input, predicate);
ApplySpanMapping(input.Expression, retExpr);
return retExpr;
}
private DbIntersectExpression Intersect(CqtExpression left, CqtExpression right)
{
DbIntersectExpression retExpr = _commandTree.CreateIntersectExpression(left, right);
UnifySpanMappings(left, right, retExpr);
return retExpr;
}
private DbLimitExpression Limit(CqtExpression argument, CqtExpression limit)
{
DbLimitExpression retExpr = _commandTree.CreateLimitExpression(argument, limit);
ApplySpanMapping(argument, retExpr);
return retExpr;
}
private DbOfTypeExpression OfType(CqtExpression argument, TypeUsage ofType)
{
DbOfTypeExpression retExpr = _commandTree.CreateOfTypeExpression(argument, ofType);
ApplySpanMapping(argument, retExpr);
return retExpr;
}
private DbProjectExpression Project(DbExpressionBinding input, CqtExpression projection)
{
DbProjectExpression retExpr = _commandTree.CreateProjectExpression(input, projection);
// For identity projection only, the Span is preserved
if (projection.ExpressionKind == DbExpressionKind.VariableReference &&
((DbVariableReferenceExpression)projection).VariableName.Equals(input.VariableName, StringComparison.Ordinal))
{
ApplySpanMapping(input.Expression, retExpr);
}
return retExpr;
}
private DbSortExpression Sort(DbExpressionBinding input, IList keys)
{
DbSortExpression retExpr = _commandTree.CreateSortExpression(input, keys);
ApplySpanMapping(input.Expression, retExpr);
return retExpr;
}
private DbSkipExpression Skip(DbExpressionBinding input, IList keys, CqtExpression skipCount)
{
DbSkipExpression retExpr = _commandTree.CreateSkipExpression(input, keys, skipCount);
ApplySpanMapping(input.Expression, retExpr);
return retExpr;
}
private DbUnionAllExpression UnionAll(CqtExpression left, CqtExpression right)
{
DbUnionAllExpression retExpr = _commandTree.CreateUnionAllExpression(left, right);
UnifySpanMappings(left, right, retExpr);
return retExpr;
}
///
/// Gets the target type for a CQT cast operation.
///
/// Appropriate type usage, or null if this is a "no-op"
private TypeUsage GetCastTargetType(TypeUsage fromType, Type toClrType, Type fromClrType, bool preserveCastForDateTime)
{
// If the types are the same or the fromType is assignable to toType, return null
// (indicating no cast is required)
TypeUsage toType;
if (TryGetValueLayerType(toClrType, out toType) && CanOmitCast(fromType, toType, preserveCastForDateTime))
{
return null;
}
// Check that the cast is supported and adjust the target type as necessary.
toType = ValidateAndAdjustCastTypes(toType, fromType, toClrType, fromClrType);
return toType;
}
///
/// Check that the given cast specification is supported and if necessary adjust target type (for instance
/// add precision and scale for Integral -> Decimal casts)
///
private static TypeUsage ValidateAndAdjustCastTypes(TypeUsage toType, TypeUsage fromType, Type toClrType, Type fromClrType)
{
// only support primitives if real casting is involved
if (toType == null ||
!(TypeSemantics.IsPrimitiveType(toType)) ||
!(TypeSemantics.IsPrimitiveType(fromType)))
{
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnsupportedCast(DescribeClrType(fromClrType), DescribeClrType(toClrType)));
}
PrimitiveTypeKind fromTypeKind = ((PrimitiveType)fromType.EdmType).PrimitiveTypeKind;
PrimitiveTypeKind toTypeKind = ((PrimitiveType)toType.EdmType).PrimitiveTypeKind;
if (toTypeKind == PrimitiveTypeKind.Decimal)
{
// Can't figure out the right precision and scale for decimal, so only accept integer types
switch (fromTypeKind)
{
case PrimitiveTypeKind.Byte:
case PrimitiveTypeKind.Int16:
case PrimitiveTypeKind.Int32:
case PrimitiveTypeKind.Int64:
case PrimitiveTypeKind.SByte:
// adjust precision and scale to ensure sufficient width
toType = TypeUsage.CreateDecimalTypeUsage((PrimitiveType)toType.EdmType, 19, 0);
break;
default:
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnsupportedCastToDecimal);
}
}
return toType;
}
///
/// Determines if an instance of fromType can be assigned to an instance of toType using
/// CLR semantics. in case of primitive type, it must rely on identity since unboxing primitive requires
/// exact match. for nominal types, rely on subtyping.
///
private static bool CanOmitCast(TypeUsage fromType, TypeUsage toType, bool preserveCastForDateTime)
{
bool isPrimitiveType = TypeSemantics.IsPrimitiveType(fromType);
//SQLBUDT #573573: This is to allow for a workaround on Katmai via explicit casting by the user.
// The issue is that SqlServer's type Date maps to Edm.DateTime, same as SqlServer's DateTime and SmallDateTime.
// However the conversion is not possible for all values of Date.
//Note: we could also call here TypeSemantics.IsPrimitiveType(TypeUsage type, PrimitiveTypeKind primitiveTypeKind),
// but that checks again whether the type is primitive
if (isPrimitiveType && preserveCastForDateTime && ((PrimitiveType)fromType.EdmType).PrimitiveTypeKind == PrimitiveTypeKind.DateTime)
{
return false;
}
if (TypeUsageEquals(fromType, toType))
{
return true;
}
if (isPrimitiveType)
{
return fromType.EdmType.EdmEquals(toType.EdmType);
}
return TypeSemantics.IsSubTypeOf(fromType, toType);
}
///
/// Gets the target type for an Is or As expression.
///
/// Input type in model metadata.
/// Test or return type.
/// Type of operation; used in error reporting.
/// Input type in CLR metadata.
/// Appropriate target type usage.
private TypeUsage GetIsOrAsTargetType(TypeUsage fromType, ExpressionType operationType, Type toClrType, Type fromClrType)
{
Debug.Assert(operationType == ExpressionType.TypeAs || operationType == ExpressionType.TypeIs);
// Interpret all type information
TypeUsage toType;
if (!this.TryGetValueLayerType(toClrType, out toType) ||
(!TypeSemantics.IsEntityType(toType) &&
!TypeSemantics.IsComplexType(toType)))
{
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnsupportedIsOrAs(operationType,
DescribeClrType(fromClrType), DescribeClrType(toClrType)));
}
return toType;
}
// requires: inlineQuery is not null
// effects: interprets the given query as an inline query in the current expression and unites
// the current query context with the context for the inline query. If the given query specifies
// span information, then an entry is added to the span mapping dictionary from the CQT expression
// that is the root of the inline query, to the span information that was present in the inline
// query's Span property.
private CqtExpression TranslateInlineQueryOfT(ObjectQuery inlineQuery)
{
if (!object.ReferenceEquals(_context, inlineQuery.QueryState.ObjectContext))
{
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnsupportedDifferentContexts);
}
// if the propagated merge option have not been set, use the MergeOption from this inline query
if (!_mergeOption.HasValue &&
inlineQuery.QueryState.UserSpecifiedMergeOption.HasValue)
{
_mergeOption = inlineQuery.QueryState.UserSpecifiedMergeOption.Value;
}
LinqExpression queryExpression;
if (inlineQuery.QueryState.TryGetExpression(out queryExpression))
{
// This inline query is backed by a LINQ expression, which can be inlined into the translation pass
// (after suitable normalization). Note that no span merging is required between the Span on the
// inline ObjectQuery and the Span produced from the translation of its expression because eLINQ
// ObjectQuery instances cannot have a non-null Span (their Span is modeled as a method call in the
// LINQ expression tree).
queryExpression = NormalizeExpression(queryExpression);
return TranslateExpression(queryExpression);
}
else
{
// The ObjectQuery should be Entity-SQL-based at this point
EntitySqlQueryState esqlState = (EntitySqlQueryState)inlineQuery.QueryState;
// We will produce the translated expression by parsing the Entity-SQL query text.
DbExpression resultExpression = null;
// If we are not converting a compiled query, or the referenced Entity-SQL ObjectQuery
// does not have parameters (and so no parameter references can be in the parsed tree)
// then the Entity-SQL can be parsed directly using the conversion command tree.
ObjectParameterCollection objectParameters = inlineQuery.QueryState.Parameters;
if (!this.IsCompiledQueryMode ||
objectParameters == null ||
objectParameters.Count == 0)
{
// Copy the parameters into the aggregated parameter collection - this will result
// in an exception if any duplicate parameter names are encountered.
if (objectParameters != null)
{
foreach (ObjectParameter prm in inlineQuery.QueryState.Parameters)
{
this.AddParameter(prm.ShallowCopy());
}
}
resultExpression = esqlState.Parse(_commandTree);
}
else
{
// We are converting a compiled query and parameters are present on the referenced ObjectQuery.
// The set of parameters available to a compiled query is fixed (so that adding/removing parameters
// to/from a referenced ObjectQuery does not invalidate the compiled query's execution plan), so the
// referenced ObjectQuery will be fully inlined by replacing each parameter reference with a
// DbConstantExpression containing the value of the referenced parameter.
// So that the parameters are never added to the conversion command tree, the referenced ObjectQuery
// is parsed using a different query command tree.
DbQueryCommandTree replacedTree = new DbQueryCommandTree(_commandTree.MetadataWorkspace, _commandTree.DataSpace);
replacedTree.Query = esqlState.Parse(replacedTree);
replacedTree.Replace((ExpressionReplacement replacement) =>
{
// Only parameter references are being replaced
if (replacement.Current.ExpressionKind != DbExpressionKind.ParameterReference)
{
return;
}
DbParameterReferenceExpression paramRef = (DbParameterReferenceExpression)replacement.Current;
if (objectParameters.Contains(paramRef.ParameterName))
{
// A DbNullExpression is required for null values; DbConstantExpression otherwise.
ObjectParameter objParam = objectParameters[paramRef.ParameterName];
if (null == objParam.Value)
{
replacement.Replacement = replacement.Current.CommandTree.CreateNullExpression(replacement.Current.ResultType);
}
else
{
// This will throw if the value is incompatible with the result type.
replacement.Replacement = replacement.Current.CommandTree.CreateConstantExpression(objParam.Value, replacement.Current.ResultType);
}
}
}
);
resultExpression = _commandTree.Import(replacedTree.Query);
}
// The Span produced by the converted DbExpression is any Span that was assigned to the referened Entity-SQL ObjectQuery.
return AddSpanMapping(resultExpression, inlineQuery.QueryState.Span);
}
}
// creates a CQT cast expression given the source and target CLR type
private CqtExpression CreateCastExpression(CqtExpression source, Type toClrType, Type fromClrType)
{
// see if the source can be normalized as a set
CqtExpression setSource = NormalizeSetSource(source);
if (!Object.ReferenceEquals(source, setSource))
{
// if the resulting cast is a no-op (no either kind is supported
// for set sources), yield the source
if (null == GetCastTargetType(setSource.ResultType, toClrType, fromClrType, true))
{
return source;
}
}
// try to find the appropriate target target for the cast
TypeUsage toType = GetCastTargetType(source.ResultType, toClrType, fromClrType, true);
if (null == toType)
{
// null indicates a no-op cast (from the perspective of the model)
return source;
}
return _commandTree.CreateCastExpression(source, toType);
}
// Utility translator method for lambda expressions. Given a lambda expression and its translated
// inputs, translates the lambda expression, assuming the input is a collection
private CqtExpression TranslateLambda(LambdaExpression lambda, CqtExpression input, out DbExpressionBinding binding)
{
input = NormalizeSetSource(input);
// create binding context for this lambda expression
binding = _commandTree.CreateExpressionBinding(input);
// figure out the binding variable
DbVariableReferenceExpression bindingVariable = binding.Variable;
_variableNameToInputExpression.Add(bindingVariable.VariableName, input);
return TranslateLambda(lambda, bindingVariable);
}
// Utility translator method for lambda expressions that are part of group by. Given a lambda expression and its translated
// inputs, translates the lambda expression, assuming the input needs to be used as a grouping input
private CqtExpression TranslateLambda(LambdaExpression lambda, CqtExpression input, out DbGroupExpressionBinding binding)
{
input = NormalizeSetSource(input);
// create binding context for this lambda expression
binding = _commandTree.CreateGroupExpressionBinding(input);
// figure out the binding variable
DbVariableReferenceExpression bindingVariable = binding.Variable;
return TranslateLambda(lambda, bindingVariable);
}
// Utility translator method for lambda expressions. Given a lambda expression and its translated
// inputs, translates the lambda expression
private CqtExpression TranslateLambda(LambdaExpression lambda, CqtExpression input)
{
Binding scopeBinding = new Binding(lambda.Parameters[0], input);
// push the binding scope
_bindingContext.PushBindingScope(scopeBinding);
// translate expression within this binding scope
CqtExpression result = TranslateExpression(lambda.Body);
// pop binding scope
_bindingContext.PopBindingScope();
return result;
}
// effects: unwraps any "structured" set sources such as IGrouping instances
// (which acts as both a set and a structure containing a property)
private CqtExpression NormalizeSetSource(CqtExpression input)
{
Debug.Assert(null != input);
// determine if the lambda input is an IGrouping or EntityCollection that needs to be unwrapped
InitializerMetadata initializerMetadata;
if (InitializerMetadata.TryGetInitializerMetadata(input.ResultType, out initializerMetadata))
{
if (initializerMetadata.Kind == InitializerMetadataKind.Grouping)
{
// for group by, redirect the binding to the group (rather than the property)
input = _commandTree.CreatePropertyExpression(ExpressionConverter.GroupColumnName, input);
}
else if (initializerMetadata.Kind == InitializerMetadataKind.EntityCollection)
{
// for entity collection, redirect the binding to the children
input = _commandTree.CreatePropertyExpression(ExpressionConverter.EntityCollectionElementsColumnName, input);
}
}
return input;
}
// Given a method call expression, returns the given lambda argument (unwrapping quote or closure references where
// necessary)
private LambdaExpression GetLambdaExpression(MethodCallExpression callExpression, int argumentOrdinal)
{
LinqExpression argument = callExpression.Arguments[argumentOrdinal];
return (LambdaExpression)GetLambdaExpression(argument);
}
private LinqExpression GetLambdaExpression(LinqExpression argument)
{
if (ExpressionType.Lambda == argument.NodeType)
{
return argument;
}
else if (ExpressionType.Quote == argument.NodeType)
{
return GetLambdaExpression(((System.Linq.Expressions.UnaryExpression)argument).Operand);
}
// see if this can be computed as a closure binding (resolving to an expression)
ClosureBinding binding;
TypeUsage typeUsage;
bool allowLambda = true;
if (ClosureBinding.TryCreateClosureBinding(argument, _context.Perspective, allowLambda, _closureCandidates, out binding, out typeUsage) &&
null != binding.Expression)
{
AddClosureBinding(binding);
return GetLambdaExpression(binding.Expression);
}
throw EntityUtil.InternalError(EntityUtil.InternalErrorCode.UnexpectedLinqLambdaExpressionFormat);
}
// Translate a LINQ expression acting as a set input to a CQT expression
private CqtExpression TranslateSet(LinqExpression linq)
{
return NormalizeSetSource(TranslateExpression(linq));
}
// If the given input source is a GroupBy, tries to produce an optimized translation
// The default translation is to 'manually' do group by, the optimized is translating into a
// DbGroupByExpression
private bool TryRewrite(CqtExpression source, DbExpressionBinding sourceBinding, CqtExpression lambda, out CqtExpression result)
{
result = null;
DbGroupByTemplate optimizedTranslationTemplate;
if (_groupByDefaultToOptimizedTranslationMap.TryGetValue(source, out optimizedTranslationTemplate))
{
//Create the group by expression
DbGroupByExpression optimizedTranslation = _commandTree.CreateGroupByExpression(optimizedTranslationTemplate.Input, optimizedTranslationTemplate.GroupKeys, optimizedTranslationTemplate.Aggregates);
DbExpressionBinding optimizedSourceBinding = _commandTree.CreateExpressionBinding(optimizedTranslation);
CqtExpression rewrittenLambda;
if (GroupByExpressionRewriter.TryRewrite(this, lambda, sourceBinding.VariableName, optimizedSourceBinding.Variable, optimizedTranslationTemplate, out rewrittenLambda))
{
result = Project(optimizedSourceBinding, rewrittenLambda);
return true;
}
}
return false;
}
// Translate a LINQ expression to a CQT expression.
private CqtExpression TranslateExpression(LinqExpression linq)
{
Debug.Assert(null != linq);
if (_linqExpressionStack.Contains(linq))
{
throw EntityUtil.InvalidOperation(Strings.ELinq_CycleDetected);
}
_linqExpressionStack.Push(linq);
try
{
ClosureBinding binding;
TypeUsage bindingType;
bool allowLambda = false;
CqtExpression result;
if (_bindingContext.TryGetBoundExpression(linq, out result))
{
// see if the LINQ expression has been bound to some CQT expression already
// (e.g. parameter expressions and compiled query parameters)
return result;
}
else if (ClosureBinding.TryCreateClosureBinding(linq, _context.Perspective, allowLambda, _closureCandidates,
out binding, out bindingType))
{
// see if the expression is a closure binding
if (!IsCompiledQueryMode)
{
AddClosureBinding(binding);
}
if (null != binding.Parameter)
{
if (IsCompiledQueryMode)
{
// pretend it was a constant expression that we found
ConstantExpression constant = Expression.Constant(binding.Parameter.Value, binding.Parameter.ParameterType);
return TranslateExpression(constant);
}
else
{
// Note that parameters are independently added to the command tree, not just
// the query context. This is because a CQT expression tree is invalid if
// its parameters are not known, and a valid tree is required (in addition to
// the query context) to construct an ObjectQuery instance.
_commandTree.AddParameter(binding.Parameter.Name, bindingType);
// return parameter taking the closure value
return _commandTree.CreateParameterReferenceExpression(binding.Parameter.Name);
}
}
else
{
Debug.Assert(null != binding.Query, "binding must be either to a query, expression (handled by TranslateLambda), or a parameter");
return TranslateInlineQueryOfT(binding.Query);
}
}
ObjectQuery queryOfT;
if (ExpressionEvaluator.TryEvaluateRootQuery(_bindingContext, linq, out queryOfT))
{
return TranslateInlineQueryOfT(queryOfT);
}
// translate to a CQT expression
Translator translator;
if (s_translators.TryGetValue(linq.NodeType, out translator))
{
return translator.Translate(this, linq);
}
else
{
throw EntityUtil.InternalError(EntityUtil.InternalErrorCode.UnknownLinqNodeType, -1,
linq.NodeType.ToString());
}
}
finally
{
_linqExpressionStack.Pop();
}
}
// Cast expression to align types between CQT and eLINQ
private CqtExpression AlignTypes(CqtExpression cqt, Type toClrType)
{
Type fromClrType = null; // not used in this code path
TypeUsage toType = GetCastTargetType(cqt.ResultType, toClrType, fromClrType, false);
if (null != toType)
{
return _commandTree.CreateCastExpression(cqt, toType);
}
else
{
return cqt;
}
}
// Determines whether the given type is supported for materialization
private void CheckInitializerType(Type type)
{
// nominal types are not supported
TypeUsage typeUsage;
if (_context.Perspective.TryGetType(type, out typeUsage))
{
BuiltInTypeKind typeKind = typeUsage.EdmType.BuiltInTypeKind;
if (BuiltInTypeKind.EntityType == typeKind ||
BuiltInTypeKind.ComplexType == typeKind)
{
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnsupportedNominalType(
typeUsage.EdmType.FullName));
}
}
// types implementing IEnumerable are not supported
if (TypeSystem.IsSequenceType(type))
{
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnsupportedEnumerableType(
DescribeClrType(type)));
}
}
// requires: Left and right are non-null.
// effects: Determines if the given types are equivalent, ignoring facets. In
// the case of primitive types, consider types equivalent if their kinds are
// equivalent.
// comments: This method is useful in cases where the type facets or specific
// store primitive type are not reliably known, e.g. when the EDM type is determined
// from the CLR type
private static bool TypeUsageEquals(TypeUsage left, TypeUsage right)
{
Debug.Assert(null != left);
Debug.Assert(null != right);
if (left.EdmType.EdmEquals(right.EdmType)) { return true; }
// compare element types for collection
if (BuiltInTypeKind.CollectionType == left.EdmType.BuiltInTypeKind &&
BuiltInTypeKind.CollectionType == right.EdmType.BuiltInTypeKind)
{
return TypeUsageEquals(
((CollectionType)left.EdmType).TypeUsage,
((CollectionType)right.EdmType).TypeUsage);
}
// special case for primitive types
if (BuiltInTypeKind.PrimitiveType == left.EdmType.BuiltInTypeKind &&
BuiltInTypeKind.PrimitiveType == right.EdmType.BuiltInTypeKind)
{
// since LINQ expressions cannot indicate model types directly, we must
// consider types equivalent if they match on the given CLR equivalent
// types (consider the Xml and String primitive types)
return ((PrimitiveType)left.EdmType).ClrEquivalentType.Equals(
((PrimitiveType)right.EdmType).ClrEquivalentType);
}
return false;
}
private TypeUsage GetValueLayerType(Type linqType)
{
TypeUsage type;
if (!TryGetValueLayerType(linqType, out type))
{
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnsupportedType(linqType));
}
return type;
}
// Determine C-Space equivalent type for linqType
private bool TryGetValueLayerType(Type linqType, out TypeUsage type)
{
// Ensure the metadata for this object type is loaded
_commandTree.MetadataWorkspace.LoadAssemblyForType(linqType,
System.Reflection.Assembly.GetCallingAssembly());
// Remove nullable
Type nonNullableType = TypeSystem.GetNonNullableType(linqType);
// See if this is a collection type (if so, recursively resolve)
Type elementType = TypeSystem.GetElementType(nonNullableType);
if (elementType != nonNullableType)
{
TypeUsage elementTypeUsage;
if (TryGetValueLayerType(elementType, out elementTypeUsage))
{
type = TypeHelpers.CreateCollectionTypeUsage(elementTypeUsage);
return true;
}
}
// Retrieve type from map
return _context.Perspective.TryGetTypeByName(
nonNullableType.FullName,
false, // ignoreCase
out type);
}
///
/// Utility method validating type for comparison ops (isNull, equals, etc.).
/// Only primitive types, entity types, and simple row types (no IGrouping/EntityCollection) are
/// supported.
///
private static void VerifyTypeSupportedForComparison(Type clrType, TypeUsage edmType, Stack memberPath)
{
// NOTE: due to bug in null handling for complex types, complex types are currently not supported
// for comparisons (see SQL BU 543956)
switch (edmType.EdmType.BuiltInTypeKind)
{
case BuiltInTypeKind.PrimitiveType:
case BuiltInTypeKind.EntityType:
return;
case BuiltInTypeKind.RowType:
{
InitializerMetadata initializerMetadata;
if (!InitializerMetadata.TryGetInitializerMetadata(edmType, out initializerMetadata) ||
initializerMetadata.Kind == InitializerMetadataKind.ProjectionInitializer ||
initializerMetadata.Kind == InitializerMetadataKind.ProjectionNew)
{
VerifyRowTypeSupportedForComparison(clrType, (RowType)edmType.EdmType, memberPath);
return;
}
break;
}
default:
break;
}
if (null == memberPath)
{
throw EntityUtil.NotSupported(Strings.ELinq_UnsupportedComparison(DescribeClrType(clrType), Strings.ELinq_PrimitiveTypesSample));
}
else
{
// build up description of member path
StringBuilder memberPathDescription = new StringBuilder();
foreach (EdmMember member in memberPath)
{
memberPathDescription.Append(Strings.ELinq_UnsupportedRowMemberComparison(member.Name));
}
memberPathDescription.Append(Strings.ELinq_UnsupportedRowTypeComparison(DescribeClrType(clrType)));
throw EntityUtil.NotSupported(Strings.ELinq_UnsupportedRowComparison(memberPathDescription.ToString(), Strings.ELinq_PrimitiveTypesSample));
}
}
private static void VerifyRowTypeSupportedForComparison(Type clrType, RowType rowType, Stack memberPath)
{
foreach (EdmMember member in rowType.Properties)
{
if (null == memberPath)
{
memberPath = new Stack();
}
memberPath.Push(member);
VerifyTypeSupportedForComparison(clrType, member.TypeUsage, memberPath);
memberPath.Pop();
}
}
///
/// Describe type for exception message.
///
internal static string DescribeClrType(Type clrType)
{
string clrTypeName = clrType.Name;
// Yes, this is a heuristic... just a best effort way of getting
// a reasonable exception message
if (IsCSharpGeneratedClass(clrTypeName, "DisplayClass") ||
IsVBGeneratedClass(clrTypeName, "Closure"))
{
return Strings.ELinq_ClosureType;
}
if (IsCSharpGeneratedClass(clrTypeName, "AnonymousType") ||
IsVBGeneratedClass(clrTypeName, "AnonymousType"))
{
return Strings.ELinq_AnonymousType;
}
string returnName = string.Empty;
if (!String.IsNullOrEmpty(clrType.Namespace))
{
returnName += clrType.Namespace + ".";
}
returnName += clrType.Name;
return returnName;
}
private static bool IsCSharpGeneratedClass(string typeName, string pattern)
{
return typeName.Contains("<>") && typeName.Contains("__") && typeName.Contains(pattern);
}
private static bool IsVBGeneratedClass(string typeName, string pattern)
{
return typeName.Contains("_") && typeName.Contains("$") && typeName.Contains(pattern);
}
///
/// Creates an implementation of IsNull. Throws exception when operand type is not supported.
///
private CqtExpression CreateIsNullExpression(CqtExpression operand, Type operandClrType)
{
VerifyTypeSupportedForComparison(operandClrType, operand.ResultType, null);
return _commandTree.CreateIsNullExpression(operand);
}
///
/// Creates an implementation of equals using the given pattern. Throws exception when argument types
/// are not supported for equals comparison.
///
private CqtExpression CreateEqualsExpression(CqtExpression left, CqtExpression right, EqualsPattern pattern, Type leftClrType, Type rightClrType)
{
VerifyTypeSupportedForComparison(leftClrType, left.ResultType, null);
VerifyTypeSupportedForComparison(rightClrType, right.ResultType, null);
return RecursivelyRewriteEqualsExpression(left, right, pattern);
}
private CqtExpression RecursivelyRewriteEqualsExpression(CqtExpression left, CqtExpression right, EqualsPattern pattern)
{
// check if either side is an initializer type
RowType leftType = left.ResultType.EdmType as RowType;
RowType rightType = left.ResultType.EdmType as RowType;
if (null != leftType || null != rightType)
{
if ((null != leftType && null != rightType) && leftType.EdmEquals(rightType))
{
CqtExpression shreddedEquals = null;
// if the types are the same, use struct equivalence semantics
foreach (EdmProperty property in leftType.Properties)
{
DbPropertyExpression leftElement = _commandTree.CreatePropertyExpression(
property, left);
DbPropertyExpression rightElement = _commandTree.CreatePropertyExpression(
property, right);
CqtExpression elementsEquals = RecursivelyRewriteEqualsExpression(
leftElement, rightElement, pattern);
// build up and expression
if (null == shreddedEquals) { shreddedEquals = elementsEquals; }
else { shreddedEquals = _commandTree.CreateAndExpression(shreddedEquals, elementsEquals); }
}
return shreddedEquals;
}
else
{
// if one or both sides is an initializer and the types are not the same,
// "equals" always evaluates to false
return _commandTree.CreateFalseExpression();
}
}
else
{
return ImplementEquality(left, right, pattern);
}
}
// For comparisons, where the left and right side are nullable or not nullable,
// here are the (compositionally safe) null equality predicates:
// -- x NOT NULL, y NULL
// x = y AND NOT (y IS NULL)
// -- x NULL, y NULL
// (x = y AND (NOT (x IS NULL OR y IS NULL))) OR (x IS NULL AND y IS NULL)
// -- x NOT NULL, y NOT NULL
// x = y
// -- x NULL, y NOT NULL
// x = y AND NOT (x IS NULL)
private CqtExpression ImplementEquality(CqtExpression left, CqtExpression right, EqualsPattern pattern)
{
switch (left.ExpressionKind)
{
case DbExpressionKind.Constant:
switch (right.ExpressionKind)
{
case DbExpressionKind.Constant: // constant EQ constant
return _commandTree.CreateEqualsExpression(left, right);
case DbExpressionKind.Null: // null EQ constant --> false
return _commandTree.CreateFalseExpression();
default:
return ImplementEqualityConstantAndUnknown((System.Data.Common.CommandTrees.DbConstantExpression)left, right, pattern);
}
case DbExpressionKind.Null:
switch (right.ExpressionKind)
{
case DbExpressionKind.Constant: // null EQ constant --> false
return _commandTree.CreateFalseExpression();
case DbExpressionKind.Null: // null EQ null --> true
return _commandTree.CreateTrueExpression();
default: // null EQ right --> right IS NULL
return _commandTree.CreateIsNullExpression(right);
}
default: // unknown
switch (right.ExpressionKind)
{
case DbExpressionKind.Constant:
return ImplementEqualityConstantAndUnknown((System.Data.Common.CommandTrees.DbConstantExpression)right, left, pattern);
case DbExpressionKind.Null: // left EQ null --> left IS NULL
return _commandTree.CreateIsNullExpression(left);
default:
return ImplementEqualityUnknownArguments(left, right, pattern);
}
}
}
// Generate an equality expression with one unknown operator and
private CqtExpression ImplementEqualityConstantAndUnknown(
System.Data.Common.CommandTrees.DbConstantExpression constant, CqtExpression unknown, EqualsPattern pattern)
{
switch (pattern)
{
case EqualsPattern.Store:
case EqualsPattern.PositiveNullEquality:
// either both are non-null, or one is null and the predicate result is undefined
return _commandTree.CreateEqualsExpression(constant, unknown);
default:
Debug.Fail("unknown pattern");
return null;
}
}
// Generate an equality expression where the values of the left and right operands are completely unknown
private CqtExpression ImplementEqualityUnknownArguments(CqtExpression left, CqtExpression right, EqualsPattern pattern)
{
switch (pattern)
{
case EqualsPattern.Store: // left EQ right
return _commandTree.CreateEqualsExpression(left, right);
case EqualsPattern.PositiveNullEquality: // left EQ right OR (left IS NULL AND right IS NULL)
{
CqtExpression leftIsNull = _commandTree.CreateIsNullExpression(left);
CqtExpression rightIsNull = _commandTree.CreateIsNullExpression(right);
CqtExpression equals = _commandTree.CreateEqualsExpression(left, right);
return _commandTree.CreateOrExpression(equals,
_commandTree.CreateAndExpression(leftIsNull, rightIsNull));
}
default:
Debug.Fail("unexpected pattern");
return null;
}
}
#endregion
#region Helper Methods Shared by Translators
///
/// Translates the arguments into CqtExpressions
/// and creates a canonical function with the given functionName and these arguments
///
/// Should represent a non-aggregate canonical function
/// Passed only for error handling purposes
///
///
private DbFunctionExpression TranslateIntoCanonicalFunction(string functionName, LinqExpression linqExpression, params LinqExpression[] linqArguments)
{
DbExpression[] translatedArguments = new DbExpression[linqArguments.Length];
for (int i = 0; i < linqArguments.Length; i++)
{
translatedArguments[i] = TranslateExpression(linqArguments[i]);
}
return CreateCanonicalFunction(functionName, linqExpression, translatedArguments);
}
///
/// Creates a canonical function with the given name and the given arguments
///
/// Should represent a non-aggregate canonical function
/// Passed only for error handling purposes
///
///
private DbFunctionExpression CreateCanonicalFunction(string functionName, LinqExpression linqExpression, params CqtExpression[] translatedArguments)
{
List translatedArgumentTypes = new List(translatedArguments.Length);
foreach (CqtExpression translatedArgument in translatedArguments)
{
translatedArgumentTypes.Add(translatedArgument.ResultType);
}
EdmFunction function = FindCanonicalFunction(functionName, translatedArgumentTypes, false /* isGroupAggregateFunction */, linqExpression);
return _commandTree.CreateFunctionExpression(function, translatedArguments);
}
///
/// Finds a canonical function wiht the given functionName and argumentTypes
///
///
///
///
///
///
private EdmFunction FindCanonicalFunction(string functionName, IList argumentTypes, bool isGroupAggregateFunction, LinqExpression linqExpression)
{
// find the function
IList functions;
if (!_typeResolver.TryGetFunctionFromMetadata(functionName, EdmNamespaceName, true /*ignoreCase*/, out functions))
{
ThrowUnresolvableCanonicalFunction(linqExpression);
}
Debug.Assert(null != functions && functions.Count > 0, "provider functions must not be null or empty");
bool isAmbiguous;
EdmFunction function = TypeResolver.ResolveFunctionOverloads(functions, argumentTypes, isGroupAggregateFunction, out isAmbiguous);
if (isAmbiguous || null == function)
{
ThrowUnresolvableStoreFunction(linqExpression);
}
return function;
}
///
/// Helper method for FindCanonicalFunction
///
///
private static void ThrowUnresolvableCanonicalFunction(LinqExpression linqExpression)
{
if (linqExpression.NodeType == ExpressionType.Call)
{
MethodInfo methodInfo = ((MethodCallExpression)linqExpression).Method;
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnresolvableCanonicalFunctionForMethod(methodInfo, methodInfo.DeclaringType));
}
else if (linqExpression.NodeType == ExpressionType.MemberAccess)
{
string memberName;
Type memberType;
MemberInfo memberInfo = TypeSystem.PropertyOrField(((MemberExpression)linqExpression).Member, out memberName, out memberType);
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnresolvableCanonicalFunctionForMember(memberInfo, memberInfo.DeclaringType));
}
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnresolvableCanonicalFunctionForExpression(linqExpression.NodeType));
}
///
/// Helper method for FindCanonicalFunction
///
///
private static void ThrowUnresolvableStoreFunction(LinqExpression linqExpression)
{
if (linqExpression.NodeType == ExpressionType.Call)
{
MethodInfo methodInfo = ((MethodCallExpression)linqExpression).Method;
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnresolvableStoreFunctionForMethod(methodInfo, methodInfo.DeclaringType));
}
else if (linqExpression.NodeType == ExpressionType.MemberAccess)
{
string memberName;
Type memberType;
MemberInfo memberInfo = TypeSystem.PropertyOrField(((MemberExpression)linqExpression).Member, out memberName, out memberType);
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnresolvableStoreFunctionForMember(memberInfo, memberInfo.DeclaringType));
}
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnresolvableStoreFunctionForExpression(linqExpression.NodeType));
}
private DbNewInstanceExpression CreateNewRowExpression(List> columns, InitializerMetadata initializerMetadata)
{
List propertyValues = new List(columns.Count);
List properties = new List(columns.Count);
for (int i = 0; i < columns.Count; i++)
{
var column = columns[i];
propertyValues.Add(column.Value);
properties.Add(new EdmProperty(column.Key, column.Value.ResultType));
}
RowType rowType = new RowType(properties, initializerMetadata);
TypeUsage typeUsage = TypeUsage.Create(rowType);
return _commandTree.CreateNewInstanceExpression(typeUsage, propertyValues);
}
#endregion
#region Helper Structures
// Mirrors the structure of DbGroupByExpression
// It is used for incrementally constructing a DbGroupByExpression
// (the actual DbGroupByExpression cannot be used for this purpose because it is immutable)
private class DbGroupByTemplate
{
private DbGroupExpressionBinding _input;
private List> _groupKeys;
private List> _aggregates;
public DbGroupByTemplate(DbGroupExpressionBinding input)
{
_input = input;
_groupKeys = new List>();
_aggregates = new List>();
}
public List> GroupKeys
{
get { return _groupKeys; }
}
public List> Aggregates
{
get { return _aggregates; }
}
public DbGroupExpressionBinding Input
{
get { return _input; }
}
}
#endregion
#region Private enums
// Describes different implementation pattern for equality comparisons.
// For all patterns, if one side of the expression is a constant null, converts to an IS NULL
// expression (or resolves to 'true' or 'false' if some constraint is known for the other side).
//
// If neither side is a constant null, the semantics differ:
//
// Store: left EQ right
// NullEquality: left EQ right OR (left IS NULL AND right IS NULL)
// PositiveEquality: left EQ right
//
// In the actual implementation (see ImplementEquality), optimizations exist if one or the other
// side is known not to be null.
private enum EqualsPattern
{
Store, // defer to store
PositiveNullEquality, // null == null is 'true', null == (not null) us undefined
}
#endregion
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// @owner [....]
//---------------------------------------------------------------------
using CqtExpression = System.Data.Common.CommandTrees.DbExpression;
using LinqExpression = System.Linq.Expressions.Expression;
using System.Linq.Expressions;
using System.Collections.ObjectModel;
using System.Linq;
using System.Collections.Generic;
using System.Data.Common.CommandTrees;
using System.Data.Metadata.Edm;
using System.Reflection;
using System.Data.Common.EntitySql;
using System.Diagnostics;
using System.Data.Common;
using System.Globalization;
using System.Data.Common.Utils;
using System.Data.Objects.DataClasses;
using System.Data.Objects.Internal;
using System.Collections;
using System.Data.Entity;
using System.Text;
using System.Data.Common.CommandTrees.Internal;
namespace System.Data.Objects.ELinq
{
///
/// Class supporting conversion of LINQ expressions to EDM CQT expressions.
///
internal sealed partial class ExpressionConverter
{
#region Fields
private readonly ObjectContext _context;
private readonly DbCommandTree _commandTree;
private readonly TypeResolver _typeResolver;
private readonly BindingContext _bindingContext;
private readonly Dictionary _groupByDefaultToOptimizedTranslationMap;
private readonly Dictionary _variableNameToInputExpression;
private readonly Dictionary> _aggregateDefaultTranslationToOptimizedTranslationInfoMap;
private readonly Expression _expression;
private readonly Expression _innerExpression;
private readonly HashSet _closureCandidates = new HashSet();
private readonly Stack _linqExpressionStack = new Stack();
private Dictionary _spanMappings;
private MergeOption? _mergeOption;
private List _closureBindings;
private Dictionary _initializers;
private ObjectParameterCollection _parameters;
private Span _span;
private const string s_visualBasicAssemblyFullName =
"Microsoft.VisualBasic, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a";
private static readonly Dictionary s_translators = InitializeTranslators();
internal const string s_entityCollectionCountPropertyName = "Count";
internal const string s_nullableHasValuePropertyName = "HasValue";
internal const string s_nullableValuePropertyName = "Value";
///
/// Gets the name of the key column appearing in ELinq GroupBy projections
///
internal const string KeyColumnName = "Key";
///
/// Gets the name of the group column appearing in ELinq CQTs (used in GroupBy expressions)
///
internal const string GroupColumnName = "Group";
///
/// Gets the name of the parent column appearing in ELinq EntityCollection projections
///
internal const string EntityCollectionOwnerColumnName = "Owner";
///
/// Gets the name of the children column appearing in ELinq EntityCollection projections
///
internal const string EntityCollectionElementsColumnName = "Elements";
///
/// The Edm namespace name, used for canonical functions
///
internal const string EdmNamespaceName = "Edm";
#region Canonical Function Names
private const string Concat = "Concat";
private const string IndexOf = "IndexOf";
private const string Length = "Length";
private const string Right = "Right";
private const string Substring = "Substring";
private const string ToUpper = "ToUpper";
private const string ToLower = "ToLower";
private const string LTrim = "LTrim";
private const string RTrim = "RTrim";
private const string BitwiseAnd = "BitwiseAnd";
private const string BitwiseOr = "BitwiseOr";
private const string BitwiseNot = "BitwiseNot";
private const string BitwiseXor = "BitwiseXor";
private const string CurrentUtcDateTime = "CurrentUtcDateTime";
private const string CurrentDateTimeOffset = "CurrentDateTimeOffset";
private const string CurrentDateTime = "CurrentDateTime";
private const string Year = "Year";
private const string Month = "Month";
private const string Day = "Day";
private const string Hour = "Hour";
private const string Minute = "Minute";
private const string Second = "Second";
private const string Millisecond = "Millisecond";
#endregion
#endregion
#region Constructors and static initializors
internal ExpressionConverter(ObjectContext objectContext, BindingContext bindingContext, DbCommandTree commandTree, Expression toConvert, ObjectParameterCollection sourceParams)
{
Debug.Assert(objectContext != null, "A binding context is required");
Debug.Assert(bindingContext != null, "A binding context is required");
Debug.Assert(commandTree != null, "A command tree is required");
Debug.Assert(toConvert != null, "An expression is required");
_bindingContext = bindingContext;
_context = objectContext;
_commandTree = commandTree;
if (sourceParams != null)
{
foreach (ObjectParameter sourceParam in sourceParams)
{
this.AddParameter(sourceParam);
}
}
// Normalize the expression
_expression = NormalizeExpression(toConvert);
if (_expression.NodeType == ExpressionType.Lambda)
{
_innerExpression = ((LambdaExpression)_expression).Body;
}
else
{
_innerExpression = _expression;
}
_typeResolver = new TypeResolver(_context.Perspective, StringComparer.Ordinal);
_groupByDefaultToOptimizedTranslationMap = new Dictionary();
_aggregateDefaultTranslationToOptimizedTranslationInfoMap = new Dictionary>();
_variableNameToInputExpression = new Dictionary();
}
// initialize translator dictionary (which support identification of translators
// for LINQ expression node types)
private static Dictionary InitializeTranslators()
{
Dictionary translators = new Dictionary();
foreach (Translator translator in GetTranslators())
{
foreach (ExpressionType nodeType in translator.NodeTypes)
{
translators.Add(nodeType, translator);
}
}
return translators;
}
private static IEnumerable GetTranslators()
{
yield return new AndAlsoTranslator();
yield return new OrElseTranslator();
yield return new LessThanTranslator();
yield return new LessThanOrEqualsTranslator();
yield return new GreaterThanTranslator();
yield return new GreaterThanOrEqualsTranslator();
yield return new EqualsTranslator();
yield return new NotEqualsTranslator();
yield return new ConvertTranslator();
yield return new ConstantTranslator();
yield return new NotTranslator();
yield return new MemberAccessTranslator();
yield return new ParameterTranslator();
yield return new MemberInitTranslator();
yield return new NewTranslator();
yield return new AddTranslator();
yield return new ConditionalTranslator();
yield return new DivideTranslator();
yield return new ModuloTranslator();
yield return new SubtractTranslator();
yield return new MultiplyTranslator();
yield return new NegateTranslator();
yield return new UnaryPlusTranslator();
yield return new MethodCallTranslator();
yield return new CoalesceTranslator();
yield return new AsTranslator();
yield return new IsTranslator();
yield return new QuoteTranslator();
yield return new AndTranslator();
yield return new OrTranslator();
yield return new ExclusiveOrTranslator();
yield return new NotSupportedTranslator(
ExpressionType.LeftShift,
ExpressionType.RightShift,
ExpressionType.ArrayLength,
ExpressionType.ArrayIndex,
ExpressionType.Invoke,
ExpressionType.Lambda,
ExpressionType.ListInit,
ExpressionType.NewArrayInit,
ExpressionType.NewArrayBounds,
ExpressionType.Power);
}
#endregion
#region Properties
private EdmItemCollection EdmItemCollection
{
get
{
return (EdmItemCollection)_commandTree.MetadataWorkspace.GetItemCollection(DataSpace.CSpace, true);
}
}
private bool IsCompiledQueryMode { get { return _bindingContext.ObjectContext != null; } }
internal List ClosureBindings { get { return _closureBindings; } }
internal ObjectParameterCollection Parameters { get { return _parameters; } }
internal MergeOption? PropagatedMergeOption { get { return _mergeOption; } }
internal Span PropagatedSpan { get { return _span; } }
#endregion
#region Internal methods
// Convert the LINQ expression to a CQT expression and (optional) Span information.
// Span information will only be present if ObjectQuery instances that specify Spans
// are referenced from the LINQ expression in a manner consistent with the Span combination
// rules, otherwise the Span for the CQT expression will be null.
internal CqtExpression Convert()
{
CqtExpression result = this.TranslateExpression(_expression);
if (!TryGetSpan(result, out _span))
{
_span = null;
}
return result;
}
internal static bool CanTranslatePropertyInfo(PropertyInfo propertyInfo)
{
return MemberAccessTranslator.CanTranslatePropertyInfo(propertyInfo);
}
#endregion
#region Private Methods
private LinqExpression NormalizeExpression(LinqExpression expression)
{
HashSet clientEvaluationCandidates = new HashSet();
LinqMaximalSubtreeNominator.Nominate(expression, clientEvaluationCandidates, ExpressionEvaluator.IsExpressionNodeClientEvaluatable);
expression = LinqTreeNodeEvaluator.Evaluate(expression, clientEvaluationCandidates);
// find all the nodes in the closure branches, storing them in _closureCandidates
LinqMaximalSubtreeNominator.Nominate(expression, _closureCandidates,
e => ExpressionEvaluator.IsExpressionNodeClientEvaluatable(e) || ExpressionEvaluator.IsExpressionNodeAClosure(e));
// normalize the expression (eliminate/rewrite compiler or CLR specific expressions).
LinqExpressionNormalizer normalizer = new LinqExpressionNormalizer();
expression = normalizer.Visit(expression);
return expression;
}
///
/// Adds the specified closure binding, optionally adding the associated
/// ObjectParameter (if any) to the Parameters collection. Parameters
/// have already been copied over if this method is called from
/// ApplyLinqStateTo, which goes through AddClosureBindingWithoutParameter to get here.
/// to copy query state from one Linq ObjectQuery to another, and so
/// ClosureBinding parameters need not be added here in that case.
///
/// The ClosureBinding to add to the set of ClosureBindings
///
/// Specifies whether the ClosureBinding's ObjectParameter (for a ParameterBinding)
/// should be added to the Parameters collection.
///
private void AddClosureBinding(ClosureBinding binding, bool addParam)
{
Debug.Assert(null != binding, "Closure binding cannot be null");
if (_closureBindings == null)
{
_closureBindings = new List();
}
_closureBindings.Add(binding);
if (addParam && null != binding.Parameter)
{
AddParameter(binding.Parameter);
}
}
///
/// Adds a new closure parameter to the query context.
///
///
/// Binding to add to the context.
///
internal void AddClosureBinding(ClosureBinding binding)
{
AddClosureBinding(binding, true);
}
// Requires: metadata must not be null.
//
// Effects: adds initializer metadata to this query context.
//
// Ensures that the given initializer metadata is valid within the current converter context.
// We do not allow two incompatible structures representing the same type within a query, e.g.,
//
// outer.Join(inner, o => new Foo { X = o.ID }, i => new Foo { Y = i.ID }, ...
//
// since this introduces a discrepancy between the CLR (where comparisons between Foo are aware
// of both X and Y) and in ELinq (where comparisons are based on the row structure only), resulting
// in the following join predicates:
//
// Linq: foo1 == foo2 (which presumably amounts to foo1.X == foo2.X && foo1.Y == foo2.Y
// ELinq: foo1.X == foo2.Y
//
// Similar problems occur with set operations such as Union and Concat, where one of the initialization
// patterns may be ignored.
//
// This method performs an overly strict check, requiring that all initializers for a given type
// are structurally equivalent.
internal void ValidateInitializerMetadata(InitializerMetadata metadata)
{
Debug.Assert(null != metadata);
InitializerMetadata existingMetadata;
if (_initializers != null && _initializers.TryGetValue(metadata.ClrType, out existingMetadata))
{
// Verify the initializers are compatible.
if (!metadata.Equals(existingMetadata))
{
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnsupportedHeterogeneousInitializers(
ExpressionConverter.DescribeClrType(metadata.ClrType)));
}
}
else
{
// Register the metadata so that subsequent initializers for this type can be verified.
if (_initializers == null)
{
_initializers = new Dictionary();
}
_initializers.Add(metadata.ClrType, metadata);
}
}
private void AddParameter(ObjectParameter newParam)
{
if (_parameters == null)
{
_parameters = new ObjectParameterCollection(_context.Perspective);
}
_parameters.Add(newParam);
}
private bool IsQueryRoot(Expression linqExpression)
{
//
// An expression is the query root if it was the expression used
// when constructing this converter or, if the root is a LambdaExpression,
// the Body of the expression used when constructing this converter.
//
return (object.ReferenceEquals(_expression, linqExpression) ||
object.ReferenceEquals(_innerExpression, linqExpression));
}
#region Span Mapping maintenance methods
///
/// Adds a new mapping from DbExpression => Span information for the specified expression,
/// after first ensuring that the mapping dictionary has been instantiated.
///
/// The expression for which Span information should be added
///
/// The Span information, which may be null .
/// If null , no attempt is made to update the dictionary of span mappings.
///
/// The original argument, to allow return AddSpanMapping(expression, span) scenarios
private CqtExpression AddSpanMapping(CqtExpression expression, Span span)
{
if (span != null)
{
if (null == _spanMappings)
{
_spanMappings = new Dictionary();
}
_spanMappings[expression] = span;
}
return expression;
}
///
/// Attempts to retrieve Span information for the specified DbExpression.
///
/// The expression for which Span information should be retrieved.
/// Will contain the Span information for the specified expression if it is present in the Span mapping dictionary.
/// true if Span information was retrieved for the specified expression and now contains this information; otherwise false .
private bool TryGetSpan(CqtExpression expression, out Span span)
{
if (_spanMappings != null)
{
return _spanMappings.TryGetValue(expression, out span);
}
span = null;
return false;
}
///
/// Removes the Span mapping entry for the specified expression,
/// and creates a new entry for the specified expression that maps
/// to the expression's original Span information. If no Span
/// information is present for the specified expression then no
/// changes are made to the Span mapping dictionary.
///
/// The expression from which to take Span information
/// The expression to which the Span information should be applied
private void ApplySpanMapping(CqtExpression from, CqtExpression to)
{
Span argumentSpan;
if (TryGetSpan(from, out argumentSpan))
{
AddSpanMapping(to, argumentSpan);
}
}
///
/// Unifies the Span information from the specified and
/// expressions, and applies it to the specified expression. Unification proceeds
/// as follows:
/// - If neither nor have Span information, no changes are made
/// - If one of or has Span information, that single Span information
/// entry is removed from the Span mapping dictionary and used to create a new entry that maps from the
/// expression to the Span information.
/// - If both and have Span information, both entries are removed
/// from the Span mapping dictionary, a new Span is created that contains the union of the original Spans, and
/// a new entry is added to the dictionary that maps from expression to this new Span.
///
/// The first expression argument
/// The second expression argument
/// The result expression
private void UnifySpanMappings(CqtExpression left, CqtExpression right, CqtExpression to)
{
Span leftSpan = null;
Span rightSpan = null;
bool hasLeftSpan = TryGetSpan(left, out leftSpan);
bool hasRightSpan = TryGetSpan(right, out rightSpan);
if (!hasLeftSpan && !hasRightSpan)
{
return;
}
Debug.Assert(leftSpan != null || rightSpan != null, "Span mappings contain null?");
AddSpanMapping(to, Span.CopyUnion(leftSpan, rightSpan));
}
#endregion
// The following methods correspond to query builder methods on ObjectQuery
// and MUST be called by expression translators (instead of calling the equivalent
// CommandTree.CreateXxExpression methods) to ensure that Span information flows
// correctly to the root of the Command Tree as it is constructed by converting
// the LINQ expression tree. Each method correctly maintains a Span mapping (if required)
// for its resulting expression, based on the Span mappings of its argument expression(s).
private DbDistinctExpression Distinct(CqtExpression argument)
{
DbDistinctExpression retExpr = _commandTree.CreateDistinctExpression(argument);
ApplySpanMapping(argument, retExpr);
return retExpr;
}
private DbExceptExpression Except(CqtExpression left, CqtExpression right)
{
DbExceptExpression retExpr = _commandTree.CreateExceptExpression(left, right);
ApplySpanMapping(left, retExpr);
return retExpr;
}
private DbFilterExpression Filter(DbExpressionBinding input, CqtExpression predicate)
{
DbFilterExpression retExpr = _commandTree.CreateFilterExpression(input, predicate);
ApplySpanMapping(input.Expression, retExpr);
return retExpr;
}
private DbIntersectExpression Intersect(CqtExpression left, CqtExpression right)
{
DbIntersectExpression retExpr = _commandTree.CreateIntersectExpression(left, right);
UnifySpanMappings(left, right, retExpr);
return retExpr;
}
private DbLimitExpression Limit(CqtExpression argument, CqtExpression limit)
{
DbLimitExpression retExpr = _commandTree.CreateLimitExpression(argument, limit);
ApplySpanMapping(argument, retExpr);
return retExpr;
}
private DbOfTypeExpression OfType(CqtExpression argument, TypeUsage ofType)
{
DbOfTypeExpression retExpr = _commandTree.CreateOfTypeExpression(argument, ofType);
ApplySpanMapping(argument, retExpr);
return retExpr;
}
private DbProjectExpression Project(DbExpressionBinding input, CqtExpression projection)
{
DbProjectExpression retExpr = _commandTree.CreateProjectExpression(input, projection);
// For identity projection only, the Span is preserved
if (projection.ExpressionKind == DbExpressionKind.VariableReference &&
((DbVariableReferenceExpression)projection).VariableName.Equals(input.VariableName, StringComparison.Ordinal))
{
ApplySpanMapping(input.Expression, retExpr);
}
return retExpr;
}
private DbSortExpression Sort(DbExpressionBinding input, IList keys)
{
DbSortExpression retExpr = _commandTree.CreateSortExpression(input, keys);
ApplySpanMapping(input.Expression, retExpr);
return retExpr;
}
private DbSkipExpression Skip(DbExpressionBinding input, IList keys, CqtExpression skipCount)
{
DbSkipExpression retExpr = _commandTree.CreateSkipExpression(input, keys, skipCount);
ApplySpanMapping(input.Expression, retExpr);
return retExpr;
}
private DbUnionAllExpression UnionAll(CqtExpression left, CqtExpression right)
{
DbUnionAllExpression retExpr = _commandTree.CreateUnionAllExpression(left, right);
UnifySpanMappings(left, right, retExpr);
return retExpr;
}
///
/// Gets the target type for a CQT cast operation.
///
/// Appropriate type usage, or null if this is a "no-op"
private TypeUsage GetCastTargetType(TypeUsage fromType, Type toClrType, Type fromClrType, bool preserveCastForDateTime)
{
// If the types are the same or the fromType is assignable to toType, return null
// (indicating no cast is required)
TypeUsage toType;
if (TryGetValueLayerType(toClrType, out toType) && CanOmitCast(fromType, toType, preserveCastForDateTime))
{
return null;
}
// Check that the cast is supported and adjust the target type as necessary.
toType = ValidateAndAdjustCastTypes(toType, fromType, toClrType, fromClrType);
return toType;
}
///
/// Check that the given cast specification is supported and if necessary adjust target type (for instance
/// add precision and scale for Integral -> Decimal casts)
///
private static TypeUsage ValidateAndAdjustCastTypes(TypeUsage toType, TypeUsage fromType, Type toClrType, Type fromClrType)
{
// only support primitives if real casting is involved
if (toType == null ||
!(TypeSemantics.IsPrimitiveType(toType)) ||
!(TypeSemantics.IsPrimitiveType(fromType)))
{
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnsupportedCast(DescribeClrType(fromClrType), DescribeClrType(toClrType)));
}
PrimitiveTypeKind fromTypeKind = ((PrimitiveType)fromType.EdmType).PrimitiveTypeKind;
PrimitiveTypeKind toTypeKind = ((PrimitiveType)toType.EdmType).PrimitiveTypeKind;
if (toTypeKind == PrimitiveTypeKind.Decimal)
{
// Can't figure out the right precision and scale for decimal, so only accept integer types
switch (fromTypeKind)
{
case PrimitiveTypeKind.Byte:
case PrimitiveTypeKind.Int16:
case PrimitiveTypeKind.Int32:
case PrimitiveTypeKind.Int64:
case PrimitiveTypeKind.SByte:
// adjust precision and scale to ensure sufficient width
toType = TypeUsage.CreateDecimalTypeUsage((PrimitiveType)toType.EdmType, 19, 0);
break;
default:
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnsupportedCastToDecimal);
}
}
return toType;
}
///
/// Determines if an instance of fromType can be assigned to an instance of toType using
/// CLR semantics. in case of primitive type, it must rely on identity since unboxing primitive requires
/// exact match. for nominal types, rely on subtyping.
///
private static bool CanOmitCast(TypeUsage fromType, TypeUsage toType, bool preserveCastForDateTime)
{
bool isPrimitiveType = TypeSemantics.IsPrimitiveType(fromType);
//SQLBUDT #573573: This is to allow for a workaround on Katmai via explicit casting by the user.
// The issue is that SqlServer's type Date maps to Edm.DateTime, same as SqlServer's DateTime and SmallDateTime.
// However the conversion is not possible for all values of Date.
//Note: we could also call here TypeSemantics.IsPrimitiveType(TypeUsage type, PrimitiveTypeKind primitiveTypeKind),
// but that checks again whether the type is primitive
if (isPrimitiveType && preserveCastForDateTime && ((PrimitiveType)fromType.EdmType).PrimitiveTypeKind == PrimitiveTypeKind.DateTime)
{
return false;
}
if (TypeUsageEquals(fromType, toType))
{
return true;
}
if (isPrimitiveType)
{
return fromType.EdmType.EdmEquals(toType.EdmType);
}
return TypeSemantics.IsSubTypeOf(fromType, toType);
}
///
/// Gets the target type for an Is or As expression.
///
/// Input type in model metadata.
/// Test or return type.
/// Type of operation; used in error reporting.
/// Input type in CLR metadata.
/// Appropriate target type usage.
private TypeUsage GetIsOrAsTargetType(TypeUsage fromType, ExpressionType operationType, Type toClrType, Type fromClrType)
{
Debug.Assert(operationType == ExpressionType.TypeAs || operationType == ExpressionType.TypeIs);
// Interpret all type information
TypeUsage toType;
if (!this.TryGetValueLayerType(toClrType, out toType) ||
(!TypeSemantics.IsEntityType(toType) &&
!TypeSemantics.IsComplexType(toType)))
{
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnsupportedIsOrAs(operationType,
DescribeClrType(fromClrType), DescribeClrType(toClrType)));
}
return toType;
}
// requires: inlineQuery is not null
// effects: interprets the given query as an inline query in the current expression and unites
// the current query context with the context for the inline query. If the given query specifies
// span information, then an entry is added to the span mapping dictionary from the CQT expression
// that is the root of the inline query, to the span information that was present in the inline
// query's Span property.
private CqtExpression TranslateInlineQueryOfT(ObjectQuery inlineQuery)
{
if (!object.ReferenceEquals(_context, inlineQuery.QueryState.ObjectContext))
{
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnsupportedDifferentContexts);
}
// if the propagated merge option have not been set, use the MergeOption from this inline query
if (!_mergeOption.HasValue &&
inlineQuery.QueryState.UserSpecifiedMergeOption.HasValue)
{
_mergeOption = inlineQuery.QueryState.UserSpecifiedMergeOption.Value;
}
LinqExpression queryExpression;
if (inlineQuery.QueryState.TryGetExpression(out queryExpression))
{
// This inline query is backed by a LINQ expression, which can be inlined into the translation pass
// (after suitable normalization). Note that no span merging is required between the Span on the
// inline ObjectQuery and the Span produced from the translation of its expression because eLINQ
// ObjectQuery instances cannot have a non-null Span (their Span is modeled as a method call in the
// LINQ expression tree).
queryExpression = NormalizeExpression(queryExpression);
return TranslateExpression(queryExpression);
}
else
{
// The ObjectQuery should be Entity-SQL-based at this point
EntitySqlQueryState esqlState = (EntitySqlQueryState)inlineQuery.QueryState;
// We will produce the translated expression by parsing the Entity-SQL query text.
DbExpression resultExpression = null;
// If we are not converting a compiled query, or the referenced Entity-SQL ObjectQuery
// does not have parameters (and so no parameter references can be in the parsed tree)
// then the Entity-SQL can be parsed directly using the conversion command tree.
ObjectParameterCollection objectParameters = inlineQuery.QueryState.Parameters;
if (!this.IsCompiledQueryMode ||
objectParameters == null ||
objectParameters.Count == 0)
{
// Copy the parameters into the aggregated parameter collection - this will result
// in an exception if any duplicate parameter names are encountered.
if (objectParameters != null)
{
foreach (ObjectParameter prm in inlineQuery.QueryState.Parameters)
{
this.AddParameter(prm.ShallowCopy());
}
}
resultExpression = esqlState.Parse(_commandTree);
}
else
{
// We are converting a compiled query and parameters are present on the referenced ObjectQuery.
// The set of parameters available to a compiled query is fixed (so that adding/removing parameters
// to/from a referenced ObjectQuery does not invalidate the compiled query's execution plan), so the
// referenced ObjectQuery will be fully inlined by replacing each parameter reference with a
// DbConstantExpression containing the value of the referenced parameter.
// So that the parameters are never added to the conversion command tree, the referenced ObjectQuery
// is parsed using a different query command tree.
DbQueryCommandTree replacedTree = new DbQueryCommandTree(_commandTree.MetadataWorkspace, _commandTree.DataSpace);
replacedTree.Query = esqlState.Parse(replacedTree);
replacedTree.Replace((ExpressionReplacement replacement) =>
{
// Only parameter references are being replaced
if (replacement.Current.ExpressionKind != DbExpressionKind.ParameterReference)
{
return;
}
DbParameterReferenceExpression paramRef = (DbParameterReferenceExpression)replacement.Current;
if (objectParameters.Contains(paramRef.ParameterName))
{
// A DbNullExpression is required for null values; DbConstantExpression otherwise.
ObjectParameter objParam = objectParameters[paramRef.ParameterName];
if (null == objParam.Value)
{
replacement.Replacement = replacement.Current.CommandTree.CreateNullExpression(replacement.Current.ResultType);
}
else
{
// This will throw if the value is incompatible with the result type.
replacement.Replacement = replacement.Current.CommandTree.CreateConstantExpression(objParam.Value, replacement.Current.ResultType);
}
}
}
);
resultExpression = _commandTree.Import(replacedTree.Query);
}
// The Span produced by the converted DbExpression is any Span that was assigned to the referened Entity-SQL ObjectQuery.
return AddSpanMapping(resultExpression, inlineQuery.QueryState.Span);
}
}
// creates a CQT cast expression given the source and target CLR type
private CqtExpression CreateCastExpression(CqtExpression source, Type toClrType, Type fromClrType)
{
// see if the source can be normalized as a set
CqtExpression setSource = NormalizeSetSource(source);
if (!Object.ReferenceEquals(source, setSource))
{
// if the resulting cast is a no-op (no either kind is supported
// for set sources), yield the source
if (null == GetCastTargetType(setSource.ResultType, toClrType, fromClrType, true))
{
return source;
}
}
// try to find the appropriate target target for the cast
TypeUsage toType = GetCastTargetType(source.ResultType, toClrType, fromClrType, true);
if (null == toType)
{
// null indicates a no-op cast (from the perspective of the model)
return source;
}
return _commandTree.CreateCastExpression(source, toType);
}
// Utility translator method for lambda expressions. Given a lambda expression and its translated
// inputs, translates the lambda expression, assuming the input is a collection
private CqtExpression TranslateLambda(LambdaExpression lambda, CqtExpression input, out DbExpressionBinding binding)
{
input = NormalizeSetSource(input);
// create binding context for this lambda expression
binding = _commandTree.CreateExpressionBinding(input);
// figure out the binding variable
DbVariableReferenceExpression bindingVariable = binding.Variable;
_variableNameToInputExpression.Add(bindingVariable.VariableName, input);
return TranslateLambda(lambda, bindingVariable);
}
// Utility translator method for lambda expressions that are part of group by. Given a lambda expression and its translated
// inputs, translates the lambda expression, assuming the input needs to be used as a grouping input
private CqtExpression TranslateLambda(LambdaExpression lambda, CqtExpression input, out DbGroupExpressionBinding binding)
{
input = NormalizeSetSource(input);
// create binding context for this lambda expression
binding = _commandTree.CreateGroupExpressionBinding(input);
// figure out the binding variable
DbVariableReferenceExpression bindingVariable = binding.Variable;
return TranslateLambda(lambda, bindingVariable);
}
// Utility translator method for lambda expressions. Given a lambda expression and its translated
// inputs, translates the lambda expression
private CqtExpression TranslateLambda(LambdaExpression lambda, CqtExpression input)
{
Binding scopeBinding = new Binding(lambda.Parameters[0], input);
// push the binding scope
_bindingContext.PushBindingScope(scopeBinding);
// translate expression within this binding scope
CqtExpression result = TranslateExpression(lambda.Body);
// pop binding scope
_bindingContext.PopBindingScope();
return result;
}
// effects: unwraps any "structured" set sources such as IGrouping instances
// (which acts as both a set and a structure containing a property)
private CqtExpression NormalizeSetSource(CqtExpression input)
{
Debug.Assert(null != input);
// determine if the lambda input is an IGrouping or EntityCollection that needs to be unwrapped
InitializerMetadata initializerMetadata;
if (InitializerMetadata.TryGetInitializerMetadata(input.ResultType, out initializerMetadata))
{
if (initializerMetadata.Kind == InitializerMetadataKind.Grouping)
{
// for group by, redirect the binding to the group (rather than the property)
input = _commandTree.CreatePropertyExpression(ExpressionConverter.GroupColumnName, input);
}
else if (initializerMetadata.Kind == InitializerMetadataKind.EntityCollection)
{
// for entity collection, redirect the binding to the children
input = _commandTree.CreatePropertyExpression(ExpressionConverter.EntityCollectionElementsColumnName, input);
}
}
return input;
}
// Given a method call expression, returns the given lambda argument (unwrapping quote or closure references where
// necessary)
private LambdaExpression GetLambdaExpression(MethodCallExpression callExpression, int argumentOrdinal)
{
LinqExpression argument = callExpression.Arguments[argumentOrdinal];
return (LambdaExpression)GetLambdaExpression(argument);
}
private LinqExpression GetLambdaExpression(LinqExpression argument)
{
if (ExpressionType.Lambda == argument.NodeType)
{
return argument;
}
else if (ExpressionType.Quote == argument.NodeType)
{
return GetLambdaExpression(((System.Linq.Expressions.UnaryExpression)argument).Operand);
}
// see if this can be computed as a closure binding (resolving to an expression)
ClosureBinding binding;
TypeUsage typeUsage;
bool allowLambda = true;
if (ClosureBinding.TryCreateClosureBinding(argument, _context.Perspective, allowLambda, _closureCandidates, out binding, out typeUsage) &&
null != binding.Expression)
{
AddClosureBinding(binding);
return GetLambdaExpression(binding.Expression);
}
throw EntityUtil.InternalError(EntityUtil.InternalErrorCode.UnexpectedLinqLambdaExpressionFormat);
}
// Translate a LINQ expression acting as a set input to a CQT expression
private CqtExpression TranslateSet(LinqExpression linq)
{
return NormalizeSetSource(TranslateExpression(linq));
}
// If the given input source is a GroupBy, tries to produce an optimized translation
// The default translation is to 'manually' do group by, the optimized is translating into a
// DbGroupByExpression
private bool TryRewrite(CqtExpression source, DbExpressionBinding sourceBinding, CqtExpression lambda, out CqtExpression result)
{
result = null;
DbGroupByTemplate optimizedTranslationTemplate;
if (_groupByDefaultToOptimizedTranslationMap.TryGetValue(source, out optimizedTranslationTemplate))
{
//Create the group by expression
DbGroupByExpression optimizedTranslation = _commandTree.CreateGroupByExpression(optimizedTranslationTemplate.Input, optimizedTranslationTemplate.GroupKeys, optimizedTranslationTemplate.Aggregates);
DbExpressionBinding optimizedSourceBinding = _commandTree.CreateExpressionBinding(optimizedTranslation);
CqtExpression rewrittenLambda;
if (GroupByExpressionRewriter.TryRewrite(this, lambda, sourceBinding.VariableName, optimizedSourceBinding.Variable, optimizedTranslationTemplate, out rewrittenLambda))
{
result = Project(optimizedSourceBinding, rewrittenLambda);
return true;
}
}
return false;
}
// Translate a LINQ expression to a CQT expression.
private CqtExpression TranslateExpression(LinqExpression linq)
{
Debug.Assert(null != linq);
if (_linqExpressionStack.Contains(linq))
{
throw EntityUtil.InvalidOperation(Strings.ELinq_CycleDetected);
}
_linqExpressionStack.Push(linq);
try
{
ClosureBinding binding;
TypeUsage bindingType;
bool allowLambda = false;
CqtExpression result;
if (_bindingContext.TryGetBoundExpression(linq, out result))
{
// see if the LINQ expression has been bound to some CQT expression already
// (e.g. parameter expressions and compiled query parameters)
return result;
}
else if (ClosureBinding.TryCreateClosureBinding(linq, _context.Perspective, allowLambda, _closureCandidates,
out binding, out bindingType))
{
// see if the expression is a closure binding
if (!IsCompiledQueryMode)
{
AddClosureBinding(binding);
}
if (null != binding.Parameter)
{
if (IsCompiledQueryMode)
{
// pretend it was a constant expression that we found
ConstantExpression constant = Expression.Constant(binding.Parameter.Value, binding.Parameter.ParameterType);
return TranslateExpression(constant);
}
else
{
// Note that parameters are independently added to the command tree, not just
// the query context. This is because a CQT expression tree is invalid if
// its parameters are not known, and a valid tree is required (in addition to
// the query context) to construct an ObjectQuery instance.
_commandTree.AddParameter(binding.Parameter.Name, bindingType);
// return parameter taking the closure value
return _commandTree.CreateParameterReferenceExpression(binding.Parameter.Name);
}
}
else
{
Debug.Assert(null != binding.Query, "binding must be either to a query, expression (handled by TranslateLambda), or a parameter");
return TranslateInlineQueryOfT(binding.Query);
}
}
ObjectQuery queryOfT;
if (ExpressionEvaluator.TryEvaluateRootQuery(_bindingContext, linq, out queryOfT))
{
return TranslateInlineQueryOfT(queryOfT);
}
// translate to a CQT expression
Translator translator;
if (s_translators.TryGetValue(linq.NodeType, out translator))
{
return translator.Translate(this, linq);
}
else
{
throw EntityUtil.InternalError(EntityUtil.InternalErrorCode.UnknownLinqNodeType, -1,
linq.NodeType.ToString());
}
}
finally
{
_linqExpressionStack.Pop();
}
}
// Cast expression to align types between CQT and eLINQ
private CqtExpression AlignTypes(CqtExpression cqt, Type toClrType)
{
Type fromClrType = null; // not used in this code path
TypeUsage toType = GetCastTargetType(cqt.ResultType, toClrType, fromClrType, false);
if (null != toType)
{
return _commandTree.CreateCastExpression(cqt, toType);
}
else
{
return cqt;
}
}
// Determines whether the given type is supported for materialization
private void CheckInitializerType(Type type)
{
// nominal types are not supported
TypeUsage typeUsage;
if (_context.Perspective.TryGetType(type, out typeUsage))
{
BuiltInTypeKind typeKind = typeUsage.EdmType.BuiltInTypeKind;
if (BuiltInTypeKind.EntityType == typeKind ||
BuiltInTypeKind.ComplexType == typeKind)
{
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnsupportedNominalType(
typeUsage.EdmType.FullName));
}
}
// types implementing IEnumerable are not supported
if (TypeSystem.IsSequenceType(type))
{
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnsupportedEnumerableType(
DescribeClrType(type)));
}
}
// requires: Left and right are non-null.
// effects: Determines if the given types are equivalent, ignoring facets. In
// the case of primitive types, consider types equivalent if their kinds are
// equivalent.
// comments: This method is useful in cases where the type facets or specific
// store primitive type are not reliably known, e.g. when the EDM type is determined
// from the CLR type
private static bool TypeUsageEquals(TypeUsage left, TypeUsage right)
{
Debug.Assert(null != left);
Debug.Assert(null != right);
if (left.EdmType.EdmEquals(right.EdmType)) { return true; }
// compare element types for collection
if (BuiltInTypeKind.CollectionType == left.EdmType.BuiltInTypeKind &&
BuiltInTypeKind.CollectionType == right.EdmType.BuiltInTypeKind)
{
return TypeUsageEquals(
((CollectionType)left.EdmType).TypeUsage,
((CollectionType)right.EdmType).TypeUsage);
}
// special case for primitive types
if (BuiltInTypeKind.PrimitiveType == left.EdmType.BuiltInTypeKind &&
BuiltInTypeKind.PrimitiveType == right.EdmType.BuiltInTypeKind)
{
// since LINQ expressions cannot indicate model types directly, we must
// consider types equivalent if they match on the given CLR equivalent
// types (consider the Xml and String primitive types)
return ((PrimitiveType)left.EdmType).ClrEquivalentType.Equals(
((PrimitiveType)right.EdmType).ClrEquivalentType);
}
return false;
}
private TypeUsage GetValueLayerType(Type linqType)
{
TypeUsage type;
if (!TryGetValueLayerType(linqType, out type))
{
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnsupportedType(linqType));
}
return type;
}
// Determine C-Space equivalent type for linqType
private bool TryGetValueLayerType(Type linqType, out TypeUsage type)
{
// Ensure the metadata for this object type is loaded
_commandTree.MetadataWorkspace.LoadAssemblyForType(linqType,
System.Reflection.Assembly.GetCallingAssembly());
// Remove nullable
Type nonNullableType = TypeSystem.GetNonNullableType(linqType);
// See if this is a collection type (if so, recursively resolve)
Type elementType = TypeSystem.GetElementType(nonNullableType);
if (elementType != nonNullableType)
{
TypeUsage elementTypeUsage;
if (TryGetValueLayerType(elementType, out elementTypeUsage))
{
type = TypeHelpers.CreateCollectionTypeUsage(elementTypeUsage);
return true;
}
}
// Retrieve type from map
return _context.Perspective.TryGetTypeByName(
nonNullableType.FullName,
false, // ignoreCase
out type);
}
///
/// Utility method validating type for comparison ops (isNull, equals, etc.).
/// Only primitive types, entity types, and simple row types (no IGrouping/EntityCollection) are
/// supported.
///
private static void VerifyTypeSupportedForComparison(Type clrType, TypeUsage edmType, Stack memberPath)
{
// NOTE: due to bug in null handling for complex types, complex types are currently not supported
// for comparisons (see SQL BU 543956)
switch (edmType.EdmType.BuiltInTypeKind)
{
case BuiltInTypeKind.PrimitiveType:
case BuiltInTypeKind.EntityType:
return;
case BuiltInTypeKind.RowType:
{
InitializerMetadata initializerMetadata;
if (!InitializerMetadata.TryGetInitializerMetadata(edmType, out initializerMetadata) ||
initializerMetadata.Kind == InitializerMetadataKind.ProjectionInitializer ||
initializerMetadata.Kind == InitializerMetadataKind.ProjectionNew)
{
VerifyRowTypeSupportedForComparison(clrType, (RowType)edmType.EdmType, memberPath);
return;
}
break;
}
default:
break;
}
if (null == memberPath)
{
throw EntityUtil.NotSupported(Strings.ELinq_UnsupportedComparison(DescribeClrType(clrType), Strings.ELinq_PrimitiveTypesSample));
}
else
{
// build up description of member path
StringBuilder memberPathDescription = new StringBuilder();
foreach (EdmMember member in memberPath)
{
memberPathDescription.Append(Strings.ELinq_UnsupportedRowMemberComparison(member.Name));
}
memberPathDescription.Append(Strings.ELinq_UnsupportedRowTypeComparison(DescribeClrType(clrType)));
throw EntityUtil.NotSupported(Strings.ELinq_UnsupportedRowComparison(memberPathDescription.ToString(), Strings.ELinq_PrimitiveTypesSample));
}
}
private static void VerifyRowTypeSupportedForComparison(Type clrType, RowType rowType, Stack memberPath)
{
foreach (EdmMember member in rowType.Properties)
{
if (null == memberPath)
{
memberPath = new Stack();
}
memberPath.Push(member);
VerifyTypeSupportedForComparison(clrType, member.TypeUsage, memberPath);
memberPath.Pop();
}
}
///
/// Describe type for exception message.
///
internal static string DescribeClrType(Type clrType)
{
string clrTypeName = clrType.Name;
// Yes, this is a heuristic... just a best effort way of getting
// a reasonable exception message
if (IsCSharpGeneratedClass(clrTypeName, "DisplayClass") ||
IsVBGeneratedClass(clrTypeName, "Closure"))
{
return Strings.ELinq_ClosureType;
}
if (IsCSharpGeneratedClass(clrTypeName, "AnonymousType") ||
IsVBGeneratedClass(clrTypeName, "AnonymousType"))
{
return Strings.ELinq_AnonymousType;
}
string returnName = string.Empty;
if (!String.IsNullOrEmpty(clrType.Namespace))
{
returnName += clrType.Namespace + ".";
}
returnName += clrType.Name;
return returnName;
}
private static bool IsCSharpGeneratedClass(string typeName, string pattern)
{
return typeName.Contains("<>") && typeName.Contains("__") && typeName.Contains(pattern);
}
private static bool IsVBGeneratedClass(string typeName, string pattern)
{
return typeName.Contains("_") && typeName.Contains("$") && typeName.Contains(pattern);
}
///
/// Creates an implementation of IsNull. Throws exception when operand type is not supported.
///
private CqtExpression CreateIsNullExpression(CqtExpression operand, Type operandClrType)
{
VerifyTypeSupportedForComparison(operandClrType, operand.ResultType, null);
return _commandTree.CreateIsNullExpression(operand);
}
///
/// Creates an implementation of equals using the given pattern. Throws exception when argument types
/// are not supported for equals comparison.
///
private CqtExpression CreateEqualsExpression(CqtExpression left, CqtExpression right, EqualsPattern pattern, Type leftClrType, Type rightClrType)
{
VerifyTypeSupportedForComparison(leftClrType, left.ResultType, null);
VerifyTypeSupportedForComparison(rightClrType, right.ResultType, null);
return RecursivelyRewriteEqualsExpression(left, right, pattern);
}
private CqtExpression RecursivelyRewriteEqualsExpression(CqtExpression left, CqtExpression right, EqualsPattern pattern)
{
// check if either side is an initializer type
RowType leftType = left.ResultType.EdmType as RowType;
RowType rightType = left.ResultType.EdmType as RowType;
if (null != leftType || null != rightType)
{
if ((null != leftType && null != rightType) && leftType.EdmEquals(rightType))
{
CqtExpression shreddedEquals = null;
// if the types are the same, use struct equivalence semantics
foreach (EdmProperty property in leftType.Properties)
{
DbPropertyExpression leftElement = _commandTree.CreatePropertyExpression(
property, left);
DbPropertyExpression rightElement = _commandTree.CreatePropertyExpression(
property, right);
CqtExpression elementsEquals = RecursivelyRewriteEqualsExpression(
leftElement, rightElement, pattern);
// build up and expression
if (null == shreddedEquals) { shreddedEquals = elementsEquals; }
else { shreddedEquals = _commandTree.CreateAndExpression(shreddedEquals, elementsEquals); }
}
return shreddedEquals;
}
else
{
// if one or both sides is an initializer and the types are not the same,
// "equals" always evaluates to false
return _commandTree.CreateFalseExpression();
}
}
else
{
return ImplementEquality(left, right, pattern);
}
}
// For comparisons, where the left and right side are nullable or not nullable,
// here are the (compositionally safe) null equality predicates:
// -- x NOT NULL, y NULL
// x = y AND NOT (y IS NULL)
// -- x NULL, y NULL
// (x = y AND (NOT (x IS NULL OR y IS NULL))) OR (x IS NULL AND y IS NULL)
// -- x NOT NULL, y NOT NULL
// x = y
// -- x NULL, y NOT NULL
// x = y AND NOT (x IS NULL)
private CqtExpression ImplementEquality(CqtExpression left, CqtExpression right, EqualsPattern pattern)
{
switch (left.ExpressionKind)
{
case DbExpressionKind.Constant:
switch (right.ExpressionKind)
{
case DbExpressionKind.Constant: // constant EQ constant
return _commandTree.CreateEqualsExpression(left, right);
case DbExpressionKind.Null: // null EQ constant --> false
return _commandTree.CreateFalseExpression();
default:
return ImplementEqualityConstantAndUnknown((System.Data.Common.CommandTrees.DbConstantExpression)left, right, pattern);
}
case DbExpressionKind.Null:
switch (right.ExpressionKind)
{
case DbExpressionKind.Constant: // null EQ constant --> false
return _commandTree.CreateFalseExpression();
case DbExpressionKind.Null: // null EQ null --> true
return _commandTree.CreateTrueExpression();
default: // null EQ right --> right IS NULL
return _commandTree.CreateIsNullExpression(right);
}
default: // unknown
switch (right.ExpressionKind)
{
case DbExpressionKind.Constant:
return ImplementEqualityConstantAndUnknown((System.Data.Common.CommandTrees.DbConstantExpression)right, left, pattern);
case DbExpressionKind.Null: // left EQ null --> left IS NULL
return _commandTree.CreateIsNullExpression(left);
default:
return ImplementEqualityUnknownArguments(left, right, pattern);
}
}
}
// Generate an equality expression with one unknown operator and
private CqtExpression ImplementEqualityConstantAndUnknown(
System.Data.Common.CommandTrees.DbConstantExpression constant, CqtExpression unknown, EqualsPattern pattern)
{
switch (pattern)
{
case EqualsPattern.Store:
case EqualsPattern.PositiveNullEquality:
// either both are non-null, or one is null and the predicate result is undefined
return _commandTree.CreateEqualsExpression(constant, unknown);
default:
Debug.Fail("unknown pattern");
return null;
}
}
// Generate an equality expression where the values of the left and right operands are completely unknown
private CqtExpression ImplementEqualityUnknownArguments(CqtExpression left, CqtExpression right, EqualsPattern pattern)
{
switch (pattern)
{
case EqualsPattern.Store: // left EQ right
return _commandTree.CreateEqualsExpression(left, right);
case EqualsPattern.PositiveNullEquality: // left EQ right OR (left IS NULL AND right IS NULL)
{
CqtExpression leftIsNull = _commandTree.CreateIsNullExpression(left);
CqtExpression rightIsNull = _commandTree.CreateIsNullExpression(right);
CqtExpression equals = _commandTree.CreateEqualsExpression(left, right);
return _commandTree.CreateOrExpression(equals,
_commandTree.CreateAndExpression(leftIsNull, rightIsNull));
}
default:
Debug.Fail("unexpected pattern");
return null;
}
}
#endregion
#region Helper Methods Shared by Translators
///
/// Translates the arguments into CqtExpressions
/// and creates a canonical function with the given functionName and these arguments
///
/// Should represent a non-aggregate canonical function
/// Passed only for error handling purposes
///
///
private DbFunctionExpression TranslateIntoCanonicalFunction(string functionName, LinqExpression linqExpression, params LinqExpression[] linqArguments)
{
DbExpression[] translatedArguments = new DbExpression[linqArguments.Length];
for (int i = 0; i < linqArguments.Length; i++)
{
translatedArguments[i] = TranslateExpression(linqArguments[i]);
}
return CreateCanonicalFunction(functionName, linqExpression, translatedArguments);
}
///
/// Creates a canonical function with the given name and the given arguments
///
/// Should represent a non-aggregate canonical function
/// Passed only for error handling purposes
///
///
private DbFunctionExpression CreateCanonicalFunction(string functionName, LinqExpression linqExpression, params CqtExpression[] translatedArguments)
{
List translatedArgumentTypes = new List(translatedArguments.Length);
foreach (CqtExpression translatedArgument in translatedArguments)
{
translatedArgumentTypes.Add(translatedArgument.ResultType);
}
EdmFunction function = FindCanonicalFunction(functionName, translatedArgumentTypes, false /* isGroupAggregateFunction */, linqExpression);
return _commandTree.CreateFunctionExpression(function, translatedArguments);
}
///
/// Finds a canonical function wiht the given functionName and argumentTypes
///
///
///
///
///
///
private EdmFunction FindCanonicalFunction(string functionName, IList argumentTypes, bool isGroupAggregateFunction, LinqExpression linqExpression)
{
// find the function
IList functions;
if (!_typeResolver.TryGetFunctionFromMetadata(functionName, EdmNamespaceName, true /*ignoreCase*/, out functions))
{
ThrowUnresolvableCanonicalFunction(linqExpression);
}
Debug.Assert(null != functions && functions.Count > 0, "provider functions must not be null or empty");
bool isAmbiguous;
EdmFunction function = TypeResolver.ResolveFunctionOverloads(functions, argumentTypes, isGroupAggregateFunction, out isAmbiguous);
if (isAmbiguous || null == function)
{
ThrowUnresolvableStoreFunction(linqExpression);
}
return function;
}
///
/// Helper method for FindCanonicalFunction
///
///
private static void ThrowUnresolvableCanonicalFunction(LinqExpression linqExpression)
{
if (linqExpression.NodeType == ExpressionType.Call)
{
MethodInfo methodInfo = ((MethodCallExpression)linqExpression).Method;
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnresolvableCanonicalFunctionForMethod(methodInfo, methodInfo.DeclaringType));
}
else if (linqExpression.NodeType == ExpressionType.MemberAccess)
{
string memberName;
Type memberType;
MemberInfo memberInfo = TypeSystem.PropertyOrField(((MemberExpression)linqExpression).Member, out memberName, out memberType);
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnresolvableCanonicalFunctionForMember(memberInfo, memberInfo.DeclaringType));
}
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnresolvableCanonicalFunctionForExpression(linqExpression.NodeType));
}
///
/// Helper method for FindCanonicalFunction
///
///
private static void ThrowUnresolvableStoreFunction(LinqExpression linqExpression)
{
if (linqExpression.NodeType == ExpressionType.Call)
{
MethodInfo methodInfo = ((MethodCallExpression)linqExpression).Method;
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnresolvableStoreFunctionForMethod(methodInfo, methodInfo.DeclaringType));
}
else if (linqExpression.NodeType == ExpressionType.MemberAccess)
{
string memberName;
Type memberType;
MemberInfo memberInfo = TypeSystem.PropertyOrField(((MemberExpression)linqExpression).Member, out memberName, out memberType);
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnresolvableStoreFunctionForMember(memberInfo, memberInfo.DeclaringType));
}
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnresolvableStoreFunctionForExpression(linqExpression.NodeType));
}
private DbNewInstanceExpression CreateNewRowExpression(List> columns, InitializerMetadata initializerMetadata)
{
List propertyValues = new List(columns.Count);
List properties = new List(columns.Count);
for (int i = 0; i < columns.Count; i++)
{
var column = columns[i];
propertyValues.Add(column.Value);
properties.Add(new EdmProperty(column.Key, column.Value.ResultType));
}
RowType rowType = new RowType(properties, initializerMetadata);
TypeUsage typeUsage = TypeUsage.Create(rowType);
return _commandTree.CreateNewInstanceExpression(typeUsage, propertyValues);
}
#endregion
#region Helper Structures
// Mirrors the structure of DbGroupByExpression
// It is used for incrementally constructing a DbGroupByExpression
// (the actual DbGroupByExpression cannot be used for this purpose because it is immutable)
private class DbGroupByTemplate
{
private DbGroupExpressionBinding _input;
private List> _groupKeys;
private List> _aggregates;
public DbGroupByTemplate(DbGroupExpressionBinding input)
{
_input = input;
_groupKeys = new List>();
_aggregates = new List>();
}
public List> GroupKeys
{
get { return _groupKeys; }
}
public List> Aggregates
{
get { return _aggregates; }
}
public DbGroupExpressionBinding Input
{
get { return _input; }
}
}
#endregion
#region Private enums
// Describes different implementation pattern for equality comparisons.
// For all patterns, if one side of the expression is a constant null, converts to an IS NULL
// expression (or resolves to 'true' or 'false' if some constraint is known for the other side).
//
// If neither side is a constant null, the semantics differ:
//
// Store: left EQ right
// NullEquality: left EQ right OR (left IS NULL AND right IS NULL)
// PositiveEquality: left EQ right
//
// In the actual implementation (see ImplementEquality), optimizations exist if one or the other
// side is known not to be null.
private enum EqualsPattern
{
Store, // defer to store
PositiveNullEquality, // null == null is 'true', null == (not null) us undefined
}
#endregion
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- ListViewAutomationPeer.cs
- StrongNameIdentityPermission.cs
- HwndKeyboardInputProvider.cs
- sqlser.cs
- GZipStream.cs
- Point3DCollectionConverter.cs
- WebColorConverter.cs
- FrameworkPropertyMetadata.cs
- PriorityQueue.cs
- mda.cs
- StatusBarPanelClickEvent.cs
- ResourceContainer.cs
- SmtpNetworkElement.cs
- FixedDocument.cs
- CookieHandler.cs
- PeerNameRegistration.cs
- DiscoveryClient.cs
- NameValueCollection.cs
- datacache.cs
- CallbackWrapper.cs
- EntityAdapter.cs
- MiniCustomAttributeInfo.cs
- QuerySetOp.cs
- Line.cs
- MembershipValidatePasswordEventArgs.cs
- SQLDateTimeStorage.cs
- CachedBitmap.cs
- DispatchWrapper.cs
- StringUtil.cs
- TableProvider.cs
- ValidatorCollection.cs
- QueryStringHandler.cs
- RegionInfo.cs
- Int32CollectionValueSerializer.cs
- AffineTransform3D.cs
- ListViewItem.cs
- SqlMultiplexer.cs
- RequestSecurityTokenResponse.cs
- LOSFormatter.cs
- TreeNodeBinding.cs
- InheritanceRules.cs
- MissingFieldException.cs
- PtsContext.cs
- FixedTextView.cs
- MergePropertyDescriptor.cs
- LoginAutoFormat.cs
- CapabilitiesUse.cs
- RichTextBox.cs
- TextProperties.cs
- SvcMapFile.cs
- PathFigure.cs
- BufferBuilder.cs
- ExecutionContext.cs
- KeyFrames.cs
- FocusChangedEventArgs.cs
- ScopelessEnumAttribute.cs
- TracePayload.cs
- AuthorizationRule.cs
- ConnectionManagementElementCollection.cs
- ChannelPoolSettings.cs
- InstalledFontCollection.cs
- COMException.cs
- ComponentGlyph.cs
- UnknownExceptionActionHelper.cs
- X509IssuerSerialKeyIdentifierClause.cs
- VisualTarget.cs
- ClientConfigurationSystem.cs
- InvalidPipelineStoreException.cs
- DataServiceEntityAttribute.cs
- FatalException.cs
- WebEncodingValidator.cs
- VirtualStackFrame.cs
- WebEventCodes.cs
- PersianCalendar.cs
- FilterFactory.cs
- CryptoApi.cs
- Hex.cs
- ScrollProviderWrapper.cs
- XmlQuerySequence.cs
- ToolConsole.cs
- QueryExtender.cs
- WebPartsPersonalization.cs
- FaultDescription.cs
- ProxyGenerator.cs
- RotateTransform3D.cs
- CustomLineCap.cs
- PriorityQueue.cs
- __TransparentProxy.cs
- SafeCryptContextHandle.cs
- NotifyIcon.cs
- assemblycache.cs
- Clock.cs
- SafeCertificateStore.cs
- XmlComplianceUtil.cs
- HttpWebRequest.cs
- HandlerWithFactory.cs
- XPathNodeList.cs
- ExpressionNormalizer.cs
- HtmlContainerControl.cs
- LinqDataSourceView.cs