//--------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner [....] //--------------------------------------------------------------------- namespace System.Data.Objects.ELinq { using System.Collections; using System.Collections.Generic; using System.Data.Common; using System.Data.Common.CommandTrees; using System.Data.Common.CommandTrees.ExpressionBuilder; using System.Data.Common.EntitySql; using System.Data.Common.Utils; using System.Data.Entity; using System.Data.Metadata.Edm; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Text; /// /// Class supporting conversion of LINQ expressions to EDM CQT expressions. /// internal sealed partial class ExpressionConverter { #region Fields private readonly Funcletizer _funcletizer; private readonly Perspective _perspective; private readonly Expression _expression; private readonly BindingContext _bindingContext; private Func _recompileRequired; private List> _parameters; private Dictionary _spanMappings; private MergeOption? _mergeOption; private Dictionary _initializers; private Span _span; private HashSet _inlineEntitySqlQueries; private int _ignoreInclude; private readonly AliasGenerator _aliasGenerator = new AliasGenerator("LQ", 0); private readonly OrderByLifter _orderByLifter; #region Consts private const string s_visualBasicAssemblyFullName = "Microsoft.VisualBasic, Version=10.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"; #endregion #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 Trim = "Trim"; private const string LTrim = "LTrim"; private const string RTrim = "RTrim"; private const string Reverse = "Reverse"; 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 #region Additional Entity function names private const string AsUnicode = "AsUnicode"; private const string AsNonUnicode = "AsNonUnicode"; #endregion #endregion #region Constructors and static initializors internal ExpressionConverter(Funcletizer funcletizer, Expression expression) { EntityUtil.CheckArgumentNull(funcletizer, "funcletizer"); EntityUtil.CheckArgumentNull(expression, "expression"); // Funcletize the expression (identify subexpressions that should be evaluated // locally) _funcletizer = funcletizer; expression = funcletizer.Funcletize(expression, out _recompileRequired); // Normalize the expression (replace obfuscated parts of the tree with simpler nodes) LinqExpressionNormalizer normalizer = new LinqExpressionNormalizer(); _expression = normalizer.Visit(expression); _perspective = funcletizer.RootContext.Perspective; _bindingContext = new BindingContext(); _ignoreInclude = 0; _orderByLifter = new OrderByLifter(_aliasGenerator); } // 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 ExtensionTranslator(); yield return new NewArrayInitTranslator(); yield return new ListInitTranslator(); yield return new NotSupportedTranslator( ExpressionType.LeftShift, ExpressionType.RightShift, ExpressionType.ArrayLength, ExpressionType.ArrayIndex, ExpressionType.Invoke, ExpressionType.Lambda, ExpressionType.NewArrayBounds, ExpressionType.Power); } #endregion #region Properties private EdmItemCollection EdmItemCollection { get { return (EdmItemCollection)_funcletizer.RootContext.MetadataWorkspace.GetItemCollection(DataSpace.CSpace, true); } } internal DbProviderManifest ProviderManifest { get { return ((StoreItemCollection)_funcletizer.RootContext.MetadataWorkspace.GetItemCollection(DataSpace.SSpace)).StoreProviderManifest; } } internal System.Collections.ObjectModel.ReadOnlyCollection> GetParameters() { if (null != _parameters) { return _parameters.AsReadOnly(); } return null; } internal MergeOption? PropagatedMergeOption { get { return _mergeOption; } } internal Span PropagatedSpan { get { return _span; } } internal Func RecompileRequired { get { return _recompileRequired; } } internal int IgnoreInclude { get { return _ignoreInclude; } set { _ignoreInclude = value; } } internal AliasGenerator AliasGenerator { get { return _aliasGenerator; } } #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 DbExpression Convert() { DbExpression result = this.TranslateExpression(_expression); if (!TryGetSpan(result, out _span)) { _span = null; } return result; } internal static bool CanFuncletizePropertyInfo(PropertyInfo propertyInfo) { return MemberAccessTranslator.CanFuncletizePropertyInfo(propertyInfo); } internal bool CanIncludeSpanInfo() { return (_ignoreInclude == 0); } #endregion #region Private Methods private void NotifyMergeOption(MergeOption mergeOption) { if (!this._mergeOption.HasValue) { this._mergeOption = mergeOption; } } // 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. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2301", Justification = "metadata.ClrType is not expected to be an Embedded Interop Type.")] 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(QueryParameterExpression queryParameter) { if (null == _parameters) { _parameters = new List>(); } if (!_parameters.Select(p => p.Value).Contains(queryParameter)) { ObjectParameter parameter = new ObjectParameter(queryParameter.ParameterReference.ParameterName, queryParameter.Type); _parameters.Add(new KeyValuePair(parameter, queryParameter)); } } private bool IsQueryRoot(Expression Expression) { // // An expression is the query root if it was the expression used // when constructing this converter. // return object.ReferenceEquals(_expression, Expression); } #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 DbExpression AddSpanMapping(DbExpression expression, Span span) { if (span != null && this.CanIncludeSpanInfo()) { if (null == _spanMappings) { _spanMappings = new Dictionary(); } Span storedSpan = null; if (_spanMappings.TryGetValue(expression, out storedSpan)) { foreach (Span.SpanPath sp in span.SpanList) { storedSpan.AddSpanPath(sp); } _spanMappings[expression] = storedSpan; } else { _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(DbExpression 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(DbExpression from, DbExpression 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(DbExpression left, DbExpression right, DbExpression 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(DbExpression argument) { DbDistinctExpression retExpr = argument.Distinct(); ApplySpanMapping(argument, retExpr); return retExpr; } private DbExceptExpression Except(DbExpression left, DbExpression right) { DbExceptExpression retExpr = left.Except(right); ApplySpanMapping(left, retExpr); return retExpr; } private DbExpression Filter(DbExpressionBinding input, DbExpression predicate) { DbExpression retExpr = _orderByLifter.Filter(input, predicate); ApplySpanMapping(input.Expression, retExpr); return retExpr; } private DbIntersectExpression Intersect(DbExpression left, DbExpression right) { DbIntersectExpression retExpr = left.Intersect(right); UnifySpanMappings(left, right, retExpr); return retExpr; } private DbExpression Limit(DbExpression argument, DbExpression limit) { DbExpression retExpr = _orderByLifter.Limit(argument, limit); ApplySpanMapping(argument, retExpr); return retExpr; } private DbExpression OfType(DbExpression argument, TypeUsage ofType) { DbExpression retExpr = _orderByLifter.OfType(argument, ofType); ApplySpanMapping(argument, retExpr); return retExpr; } private DbExpression Project(DbExpressionBinding input, DbExpression projection) { DbExpression retExpr = _orderByLifter.Project(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 = input.Sort(keys); ApplySpanMapping(input.Expression, retExpr); return retExpr; } private DbExpression Skip(DbExpressionBinding input, DbExpression skipCount) { DbExpression retExpr = _orderByLifter.Skip(input, skipCount); ApplySpanMapping(input.Expression, retExpr); return retExpr; } private DbUnionAllExpression UnionAll(DbExpression left, DbExpression right) { DbUnionAllExpression retExpr = left.UnionAll(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) { // An inlined ObjectQuery or an IOrderedQueryable expression being cast to IQueryable for use in a sequence method is a no-op. if(fromClrType != null && fromClrType.IsGenericType && toClrType.IsGenericType && (fromClrType.GetGenericTypeDefinition() == typeof(ObjectQuery<>) || fromClrType.GetGenericTypeDefinition() == typeof(IOrderedQueryable<>)) && (toClrType.GetGenericTypeDefinition() == typeof(IQueryable<>) || toClrType.GetGenericTypeDefinition() == typeof(IOrderedQueryable<>)) && fromClrType.GetGenericArguments()[0] == toClrType.GetGenericArguments()[0]) { return null; } // 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.IsScalarType(toType) || !TypeSemantics.IsScalarType(fromType)) { throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnsupportedCast(DescribeClrType(fromClrType), DescribeClrType(toClrType))); } PrimitiveTypeKind fromTypeKind = Helper.AsPrimitive(fromType.EdmType).PrimitiveTypeKind; PrimitiveTypeKind toTypeKind = Helper.AsPrimitive(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 and inlineQuery is Entity-SQL query // 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 DbExpression TranslateInlineQueryOfT(ObjectQuery inlineQuery) { if (!object.ReferenceEquals(_funcletizer.RootContext, inlineQuery.QueryState.ObjectContext)) { throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnsupportedDifferentContexts); } // Check if the inline query has been encountered so far. If so, we don't need to // include its parameters again. We do however need to translate it to a new // DbExpression instance since the expressions may be tagged with span information // and we don't want to mistakenly apply the directive to the wrong part of the query. if (null == _inlineEntitySqlQueries) { _inlineEntitySqlQueries = new HashSet(); } bool isNewInlineQuery = _inlineEntitySqlQueries.Add(inlineQuery); // The ObjectQuery should be Entity-SQL-based at this point. All other query types are currently // inlined. 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 (!_funcletizer.IsCompiledQuery || objectParameters == null || objectParameters.Count == 0) { // Add parameters if they exist and we haven't yet encountered this inline query. if (isNewInlineQuery && objectParameters != null) { // Copy the parameters into the aggregated parameter collection - this will result // in an exception if any duplicate parameter names are encountered. if (this._parameters == null) { this._parameters = new List>(); } foreach (ObjectParameter prm in inlineQuery.QueryState.Parameters) { this._parameters.Add(new KeyValuePair(prm.ShallowCopy(), null)); } } resultExpression = esqlState.Parse(); } 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. resultExpression = esqlState.Parse(); resultExpression = ParameterReferenceRemover.RemoveParameterReferences(resultExpression, objectParameters); } return resultExpression; } private class ParameterReferenceRemover : DefaultExpressionVisitor { internal static DbExpression RemoveParameterReferences(DbExpression expression, ObjectParameterCollection availableParameters) { ParameterReferenceRemover remover = new ParameterReferenceRemover(availableParameters); return remover.VisitExpression(expression); } private readonly ObjectParameterCollection objectParameters; private ParameterReferenceRemover(ObjectParameterCollection availableParams) : base() { Debug.Assert(availableParams != null, "Parameter collection cannot be null"); this.objectParameters = availableParams; } public override DbExpression Visit(DbParameterReferenceExpression expression) { if (this.objectParameters.Contains(expression.ParameterName)) { // A DbNullExpression is required for null values; DbConstantExpression otherwise. ObjectParameter objParam = objectParameters[expression.ParameterName]; if (null == objParam.Value) { return DbExpressionBuilder.Null(expression.ResultType); } else { // This will throw if the value is incompatible with the result type. return DbExpressionBuilder.Constant(expression.ResultType, objParam.Value); } } return expression; } } // creates a CQT cast expression given the source and target CLR type private DbExpression CreateCastExpression(DbExpression source, Type toClrType, Type fromClrType) { // see if the source can be normalized as a set DbExpression 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 source.CastTo(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 DbExpression TranslateLambda(LambdaExpression lambda, DbExpression input, out DbExpressionBinding binding) { input = NormalizeSetSource(input); // create binding context for this lambda expression binding = input.BindAs(_aliasGenerator.Next()); return TranslateLambda(lambda, binding.Variable); } // 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 DbExpression TranslateLambda(LambdaExpression lambda, DbExpression input, string bindingName, out DbExpressionBinding binding) { input = NormalizeSetSource(input); // create binding context for this lambda expression binding = input.BindAs(bindingName); return TranslateLambda(lambda, binding.Variable); } // 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 DbExpression TranslateLambda(LambdaExpression lambda, DbExpression input, out DbGroupExpressionBinding binding) { input = NormalizeSetSource(input); // create binding context for this lambda expression string alias = _aliasGenerator.Next(); binding = input.GroupBindAs(alias, string.Format(CultureInfo.InvariantCulture, "Group{0}", alias)); return TranslateLambda(lambda, binding.Variable); } // Utility translator method for lambda expressions. Given a lambda expression and its translated // inputs, translates the lambda expression private DbExpression TranslateLambda(LambdaExpression lambda, DbExpression input) { Binding scopeBinding = new Binding(lambda.Parameters[0], input); // push the binding scope _bindingContext.PushBindingScope(scopeBinding); // translate expression within this binding scope #if DEBUG int preValue = _ignoreInclude; #endif _ignoreInclude++; DbExpression result = TranslateExpression(lambda.Body); _ignoreInclude--; #if DEBUG Debug.Assert(preValue == _ignoreInclude); #endif // 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 DbExpression NormalizeSetSource(DbExpression input) { Debug.Assert(null != input); // If input looks like "select x from (...) as x", rewrite it as "(...)". // If input has span information attached to to it then leave it as is, otherwise // span info will be lost. Span span; if (input.ExpressionKind == DbExpressionKind.Project && !TryGetSpan(input, out span)) { var project = (DbProjectExpression)input; if (project.Projection == project.Input.Variable) { input = project.Input.Expression; } } // 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 = input.Property(ExpressionConverter.GroupColumnName); } else if (initializerMetadata.Kind == InitializerMetadataKind.EntityCollection) { // for entity collection, redirect the binding to the children input = input.Property(ExpressionConverter.EntityCollectionElementsColumnName); } } 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) { Expression argument = callExpression.Arguments[argumentOrdinal]; return (LambdaExpression)GetLambdaExpression(argument); } private Expression GetLambdaExpression(Expression argument) { if (ExpressionType.Lambda == argument.NodeType) { return argument; } else if (ExpressionType.Quote == argument.NodeType) { return GetLambdaExpression(((UnaryExpression)argument).Operand); } throw EntityUtil.InternalError(EntityUtil.InternalErrorCode.UnexpectedLinqLambdaExpressionFormat); } // Translate a LINQ expression acting as a set input to a CQT expression private DbExpression TranslateSet(Expression linq) { return NormalizeSetSource(TranslateExpression(linq)); } // Translate a LINQ expression to a CQT expression. private DbExpression TranslateExpression(Expression linq) { Debug.Assert(null != linq); DbExpression result; if (!_bindingContext.TryGetBoundExpression(linq, out result)) { // translate to a CQT expression Translator translator; if (s_translators.TryGetValue(linq.NodeType, out translator)) { result = translator.Translate(this, linq); } else { throw EntityUtil.InternalError(EntityUtil.InternalErrorCode.UnknownLinqNodeType, -1, linq.NodeType.ToString()); } } return result; } // Cast expression to align types between CQT and eLINQ private DbExpression AlignTypes(DbExpression cqt, Type toClrType) { Type fromClrType = null; // not used in this code path TypeUsage toType = GetCastTargetType(cqt.ResultType, toClrType, fromClrType, false); if (null != toType) { return cqt.CastTo(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 (_funcletizer.RootContext.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) { // Remove nullable Type nonNullableType = TypeSystem.GetNonNullableType(linqType); // Enum types are only supported for EDM V3 and higher, do not force loading // enum types for previous versions of EDM if (nonNullableType.IsEnum && this.EdmItemCollection.EdmVersion < XmlConstants.EdmVersionForV3) { nonNullableType = nonNullableType.GetEnumUnderlyingType(); } // See if this is a primitive type PrimitiveTypeKind primitiveTypeKind; if (ClrProviderManifest.Instance.TryGetPrimitiveTypeKind(nonNullableType, out primitiveTypeKind)) { type = EdmProviderManifest.Instance.GetCanonicalModelTypeUsage(primitiveTypeKind); return true; } // 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; } } // Ensure the metadata for this object type is loaded _perspective.MetadataWorkspace.ImplicitLoadAssemblyForType(linqType, null); if(!_perspective.TryGetTypeByName(nonNullableType.FullName, false, out type)) { // If the user is casting to a type that is not a model type or a primitive type it can be a cast to an enum that // is not in the model. In that case we use the underlying enum type. // Note that if the underlying type is not any of the EF primitive types we will fail with and InvalidCastException. // This is consistent with what we would do when seeing a cast to a primitive type that is not a EF valid primitive // type (e.g. ulong). if(nonNullableType.IsEnum && ClrProviderManifest.Instance.TryGetPrimitiveTypeKind(nonNullableType.GetEnumUnderlyingType(), out primitiveTypeKind)) { type = EdmProviderManifest.Instance.GetCanonicalModelTypeUsage(primitiveTypeKind); } } return type != null; } /// /// 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.EnumType: case BuiltInTypeKind.EntityType: case BuiltInTypeKind.RefType: 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))); } 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())); } } 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 DbExpression CreateIsNullExpression(DbExpression operand, Type operandClrType) { VerifyTypeSupportedForComparison(operandClrType, operand.ResultType, null); return operand.IsNull(); } /// /// Creates an implementation of equals using the given pattern. Throws exception when argument types /// are not supported for equals comparison. /// private DbExpression CreateEqualsExpression(DbExpression left, DbExpression right, EqualsPattern pattern, Type leftClrType, Type rightClrType) { VerifyTypeSupportedForComparison(leftClrType, left.ResultType, null); VerifyTypeSupportedForComparison(rightClrType, right.ResultType, null); //For Ref Type comparison, check whether they refer to compatible Entity Types. TypeUsage leftType = left.ResultType; TypeUsage rightType = right.ResultType; if (leftType.EdmType.BuiltInTypeKind == BuiltInTypeKind.RefType && rightType.EdmType.BuiltInTypeKind == BuiltInTypeKind.RefType) { TypeUsage commonType; if (!TypeSemantics.TryGetCommonType(leftType, rightType, out commonType)) { RefType leftRefType = left.ResultType.EdmType as RefType; RefType rightRefType = right.ResultType.EdmType as RefType; throw EntityUtil.NotSupported(Strings.ELinq_UnsupportedRefComparison(leftRefType.ElementType.FullName, rightRefType.ElementType.FullName)); } } return RecursivelyRewriteEqualsExpression(left, right, pattern); } private DbExpression RecursivelyRewriteEqualsExpression(DbExpression left, DbExpression 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)) { DbExpression shreddedEquals = null; // if the types are the same, use struct equivalence semantics foreach (EdmProperty property in leftType.Properties) { DbPropertyExpression leftElement = left.Property(property); DbPropertyExpression rightElement = right.Property(property); DbExpression elementsEquals = RecursivelyRewriteEqualsExpression( leftElement, rightElement, pattern); // build up and expression if (null == shreddedEquals) { shreddedEquals = elementsEquals; } else { shreddedEquals = shreddedEquals.And(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 DbExpressionBuilder.False; } } 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 DbExpression ImplementEquality(DbExpression left, DbExpression right, EqualsPattern pattern) { switch (left.ExpressionKind) { case DbExpressionKind.Constant: switch (right.ExpressionKind) { case DbExpressionKind.Constant: // constant EQ constant return left.Equal(right); case DbExpressionKind.Null: // null EQ constant --> false return DbExpressionBuilder.False; 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 DbExpressionBuilder.False; case DbExpressionKind.Null: // null EQ null --> true return DbExpressionBuilder.True; default: // null EQ right --> right IS NULL return right.IsNull(); } 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 left.IsNull(); default: return ImplementEqualityUnknownArguments(left, right, pattern); } } } // Generate an equality expression with one unknown operator and private DbExpression ImplementEqualityConstantAndUnknown( System.Data.Common.CommandTrees.DbConstantExpression constant, DbExpression unknown, EqualsPattern pattern) { switch (pattern) { case EqualsPattern.Store: case EqualsPattern.PositiveNullEqualityNonComposable: // for Joins return constant.Equal(unknown); // either both are non-null, or one is null and the predicate result is undefined case EqualsPattern.PositiveNullEqualityComposable: if (!_funcletizer.RootContext.ContextOptions.UseCSharpNullComparisonBehavior) { return constant.Equal(unknown); // same as EqualsPattern.PositiveNullEqualityNonComposable } return constant.Equal(unknown).And(unknown.IsNull().Not()); // add more logic to avoid undefined result for true clr semantics default: Debug.Fail("unknown pattern"); return null; } } // Generate an equality expression where the values of the left and right operands are completely unknown private DbExpression ImplementEqualityUnknownArguments(DbExpression left, DbExpression right, EqualsPattern pattern) { switch (pattern) { case EqualsPattern.Store: // left EQ right return left.Equal(right); case EqualsPattern.PositiveNullEqualityNonComposable: // for Joins return left.Equal(right).Or(left.IsNull().And(right.IsNull())); case EqualsPattern.PositiveNullEqualityComposable: { var bothNotNull = left.Equal(right); var bothNull = left.IsNull().And(right.IsNull()); if (!_funcletizer.RootContext.ContextOptions.UseCSharpNullComparisonBehavior) { return bothNotNull.Or(bothNull); // same as EqualsPattern.PositiveNullEqualityNonComposable } // add more logic to avoid undefined result for true clr semantics, ensuring composability // (left EQ right AND NOT (left IS NULL OR right IS NULL)) OR (left IS NULL AND right IS NULL) var anyOneIsNull = left.IsNull().Or(right.IsNull()); return (bothNotNull.And(anyOneIsNull.Not())).Or(bothNull); } default: Debug.Fail("unexpected pattern"); return null; } } #endregion #region Helper Methods Shared by Translators /// /// Helper method for String.StartsWith, String.EndsWith and String.Contains /// /// object.Method(argument), where Method is one of String.StartsWith, String.EndsWith or /// String.Contains is translated into: /// 1) If argument is a constant or parameter and the provider supports escaping: /// object like ("%") + argument1 + ("%"), where argument1 is argument escaped by the provider /// and ("%") are appended on the begining/end depending on whether /// insertPercentAtStart/insertPercentAtEnd are specified /// 2) Otherwise: /// object.Method(argument) -> defaultTranslator /// /// /// Should '%' be inserted at the begining of the pattern /// Should '%' be inserted at the end of the pattern /// The delegate that provides the default translation /// The translation private DbExpression TranslateFunctionIntoLike(MethodCallExpression call, bool insertPercentAtStart, bool insertPercentAtEnd, Func defaultTranslator) { char escapeChar; bool providerSupportsEscapingLikeArgument = this.ProviderManifest.SupportsEscapingLikeArgument(out escapeChar); bool useLikeTranslation = false; bool specifyEscape = true; Expression patternExpression = call.Arguments[0]; Expression inputExpression = call.Object; QueryParameterExpression queryParameterExpression = patternExpression as QueryParameterExpression; if (providerSupportsEscapingLikeArgument && (queryParameterExpression != null)) { useLikeTranslation = true; bool specifyEscapeDummy; patternExpression = queryParameterExpression.EscapeParameterForLike(input => PreparePattern(input, insertPercentAtStart, insertPercentAtEnd, out specifyEscapeDummy)); } DbExpression translatedPatternExpression = this.TranslateExpression(patternExpression); DbExpression translatedInputExpression = this.TranslateExpression(inputExpression); if (providerSupportsEscapingLikeArgument && translatedPatternExpression.ExpressionKind == DbExpressionKind.Constant) { useLikeTranslation = true; DbConstantExpression constantExpression = (DbConstantExpression)translatedPatternExpression; string preparedValue = PreparePattern((string)constantExpression.Value, insertPercentAtStart, insertPercentAtEnd, out specifyEscape); Debug.Assert(preparedValue != null, "The prepared value should not be null when the input is non-null"); //Note: the result type needs to be taken from the original expression, as the user may have specified Unicode/Non-Unicode translatedPatternExpression = DbExpressionBuilder.Constant(constantExpression.ResultType, preparedValue); } DbExpression result; if (useLikeTranslation) { if (specifyEscape) { //DevDiv #326720: The constant expression for the escape character should not have unicode set by default var escapeExpression = DbExpressionBuilder.Constant(EdmProviderManifest.Instance.GetCanonicalModelTypeUsage(PrimitiveTypeKind.String), new String(new char[] { escapeChar })); result = DbExpressionBuilder.Like(translatedInputExpression, translatedPatternExpression, escapeExpression); } else { result = DbExpressionBuilder.Like(translatedInputExpression, translatedPatternExpression); } } else { result = defaultTranslator(this, call, translatedPatternExpression, translatedInputExpression); } return result; } /// /// Prepare the given input patternValue into a pattern to be used in a LIKE expression by /// first escaping it by the provider and then appending "%" and the beginging/end depending /// on whether insertPercentAtStart/insertPercentAtEnd is specified. /// private string PreparePattern(string patternValue, bool insertPercentAtStart, bool insertPercentAtEnd, out bool specifyEscape) { // Dev10 #800466: The pattern value if originating from a parameter value could be null if (patternValue == null) { specifyEscape = false; return null; } string escapedPatternValue = this.ProviderManifest.EscapeLikeArgument(patternValue); if (escapedPatternValue == null) { throw EntityUtil.ProviderIncompatible(System.Data.Entity.Strings.ProviderEscapeLikeArgumentReturnedNull); } specifyEscape = patternValue != escapedPatternValue; System.Text.StringBuilder patternBuilder = new System.Text.StringBuilder(); if (insertPercentAtStart) { patternBuilder.Append("%"); } patternBuilder.Append(escapedPatternValue); if (insertPercentAtEnd) { patternBuilder.Append("%"); } return patternBuilder.ToString(); } /// /// Translates the arguments into DbExpressions /// 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, Expression Expression, params Expression[] linqArguments) { DbExpression[] translatedArguments = new DbExpression[linqArguments.Length]; for (int i = 0; i < linqArguments.Length; i++) { translatedArguments[i] = TranslateExpression(linqArguments[i]); } return CreateCanonicalFunction(functionName, Expression, 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, Expression Expression, params DbExpression[] translatedArguments) { List translatedArgumentTypes = new List(translatedArguments.Length); foreach (DbExpression translatedArgument in translatedArguments) { translatedArgumentTypes.Add(translatedArgument.ResultType); } EdmFunction function = FindCanonicalFunction(functionName, translatedArgumentTypes, false /* isGroupAggregateFunction */, Expression); return function.Invoke(translatedArguments); } /// /// Finds a canonical function with the given functionName and argumentTypes /// /// /// /// /// /// private EdmFunction FindCanonicalFunction(string functionName, IList argumentTypes, bool isGroupAggregateFunction, Expression Expression) { return FindFunction(EdmNamespaceName, functionName, argumentTypes, isGroupAggregateFunction, Expression); } /// /// Finds a function with the given namespaceName, functionName and argumentTypes /// /// /// /// /// /// /// private EdmFunction FindFunction(string namespaceName, string functionName, IList argumentTypes, bool isGroupAggregateFunction, Expression Expression) { // find the function IList candidateFunctions; if (!_perspective.TryGetFunctionByName(namespaceName, functionName, false /* ignore case */, out candidateFunctions)) { ThrowUnresolvableFunction(Expression); } Debug.Assert(null != candidateFunctions && candidateFunctions.Count > 0, "provider functions must not be null or empty"); bool isAmbiguous; EdmFunction function = FunctionOverloadResolver.ResolveFunctionOverloads(candidateFunctions, argumentTypes, isGroupAggregateFunction, out isAmbiguous); if (isAmbiguous || null == function) { ThrowUnresolvableFunctionOverload(Expression, isAmbiguous); } return function; } /// /// Helper method for FindFunction /// /// private static void ThrowUnresolvableFunction(Expression Expression) { if (Expression.NodeType == ExpressionType.Call) { MethodInfo methodInfo = ((MethodCallExpression)Expression).Method; throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnresolvableFunctionForMethod(methodInfo, methodInfo.DeclaringType)); } else if (Expression.NodeType == ExpressionType.MemberAccess) { string memberName; Type memberType; MemberInfo memberInfo = TypeSystem.PropertyOrField(((MemberExpression)Expression).Member, out memberName, out memberType); throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnresolvableFunctionForMember(memberInfo, memberInfo.DeclaringType)); } throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnresolvableFunctionForExpression(Expression.NodeType)); } /// /// Helper method for FindCanonicalFunction /// /// private static void ThrowUnresolvableFunctionOverload(Expression Expression, bool isAmbiguous) { if (Expression.NodeType == ExpressionType.Call) { MethodInfo methodInfo = ((MethodCallExpression)Expression).Method; if (isAmbiguous) { throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnresolvableFunctionForMethodAmbiguousMatch(methodInfo, methodInfo.DeclaringType)); } else { throw EntityUtil.NotSupported(System.Data.Entity.Strings.ELinq_UnresolvableFunctionForMethodNotFound(methodInfo, methodInfo.DeclaringType)); } } else if (Expression.NodeType == ExpressionType.MemberAccess) { string memberName; Type memberType; MemberInfo memberInfo = TypeSystem.PropertyOrField(((MemberExpression)Expression).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(Expression.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 typeUsage.New(propertyValues); } #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: // // (1) (left EQ right) => left and right are equal and not null, so return true. // (2) (left IS NULL AND right IS NULL) => Both left and right are null, so return true. // (3) NOT (left IS NULL OR right IS NULL) => // If only one of left or right is null, (1) evaluates to "unknown" and (2) evaluates to false. So we get "unknown" from DB which is null in C#. // This is not desired as it does not help in composability. Hence, (3) is used to return false instead of "unknown" when only one of the operands is null. // // Store: (1) // PositiveNullEqualityNonComposable: (1) OR (2) - suitable only for Join operators, as they are not composable // PositiveNullEqualityComposable: (1) OR (2) AND (3) // // 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 PositiveNullEqualityNonComposable, // simulate C# semantics in store, return "null" if left or right is null, but not both. Suitable for joins. PositiveNullEqualityComposable, // simulate C# semantics in store, always return true or false } #endregion } }