2016-08-03 10:59:49 +00:00
//---------------------------------------------------------------------
// <copyright file="ExpressionConverter.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
2017-08-21 15:34:15 +00:00
// @owner Microsoft
2016-08-03 10:59:49 +00:00
//---------------------------------------------------------------------
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 ;
/// <summary>
/// Class supporting conversion of LINQ expressions to EDM CQT expressions.
/// </summary>
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 < bool > _recompileRequired ;
private List < KeyValuePair < ObjectParameter , QueryParameterExpression > > _parameters ;
private Dictionary < DbExpression , Span > _spanMappings ;
private MergeOption ? _mergeOption ;
private Dictionary < Type , InitializerMetadata > _initializers ;
private Span _span ;
private HashSet < ObjectQuery > _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 < ExpressionType , Translator > s_translators = InitializeTranslators ( ) ;
internal const string s_entityCollectionCountPropertyName = "Count" ;
internal const string s_nullableHasValuePropertyName = "HasValue" ;
internal const string s_nullableValuePropertyName = "Value" ;
/// <summary>
/// Gets the name of the key column appearing in ELinq GroupBy projections
/// </summary>
internal const string KeyColumnName = "Key" ;
/// <summary>
/// Gets the name of the group column appearing in ELinq CQTs (used in GroupBy expressions)
/// </summary>
internal const string GroupColumnName = "Group" ;
/// <summary>
/// Gets the name of the parent column appearing in ELinq EntityCollection projections
/// </summary>
internal const string EntityCollectionOwnerColumnName = "Owner" ;
/// <summary>
/// Gets the name of the children column appearing in ELinq EntityCollection projections
/// </summary>
internal const string EntityCollectionElementsColumnName = "Elements" ;
/// <summary>
/// The Edm namespace name, used for canonical functions
/// </summary>
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 < ExpressionType , Translator > InitializeTranslators ( )
{
Dictionary < ExpressionType , Translator > translators = new Dictionary < ExpressionType , Translator > ( ) ;
foreach ( Translator translator in GetTranslators ( ) )
{
foreach ( ExpressionType nodeType in translator . NodeTypes )
{
translators . Add ( nodeType , translator ) ;
}
}
return translators ;
}
private static IEnumerable < Translator > 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 < KeyValuePair < ObjectParameter , QueryParameterExpression > > GetParameters ( )
{
if ( null ! = _parameters ) { return _parameters . AsReadOnly ( ) ; }
return null ;
}
internal MergeOption ? PropagatedMergeOption { get { return _mergeOption ; } }
internal Span PropagatedSpan { get { return _span ; } }
internal Func < bool > 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 < Type , InitializerMetadata > ( ) ;
}
_initializers . Add ( metadata . ClrType , metadata ) ;
}
}
private void AddParameter ( QueryParameterExpression queryParameter )
{
if ( null = = _parameters )
{
_parameters = new List < KeyValuePair < ObjectParameter , QueryParameterExpression > > ( ) ;
}
if ( ! _parameters . Select ( p = > p . Value ) . Contains ( queryParameter ) )
{
ObjectParameter parameter = new ObjectParameter ( queryParameter . ParameterReference . ParameterName , queryParameter . Type ) ;
_parameters . Add ( new KeyValuePair < ObjectParameter , QueryParameterExpression > ( 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
/// <summary>
/// Adds a new mapping from DbExpression => Span information for the specified expression,
/// after first ensuring that the mapping dictionary has been instantiated.
/// </summary>
/// <param name="expression">The expression for which Span information should be added</param>
/// <param name="span">
/// The Span information, which may be <c>null</c>.
/// If <c>null</c>, no attempt is made to update the dictionary of span mappings.
/// </param>
/// <returns>The original <paramref name="expression"/> argument, to allow <c>return AddSpanMapping(expression, span)</c> scenarios</returns>
private DbExpression AddSpanMapping ( DbExpression expression , Span span )
{
if ( span ! = null & & this . CanIncludeSpanInfo ( ) )
{
if ( null = = _spanMappings )
{
_spanMappings = new Dictionary < DbExpression , Span > ( ) ;
}
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 ;
}
/// <summary>
/// Attempts to retrieve Span information for the specified DbExpression.
/// </summary>
/// <param name="expression">The expression for which Span information should be retrieved.</param>
/// <param name="span">Will contain the Span information for the specified expression if it is present in the Span mapping dictionary.</param>
/// <returns><c>true</c> if Span information was retrieved for the specified expression and <paramref name="span"/> now contains this information; otherwise <c>false</c>.</returns>
private bool TryGetSpan ( DbExpression expression , out Span span )
{
if ( _spanMappings ! = null )
{
return _spanMappings . TryGetValue ( expression , out span ) ;
}
span = null ;
return false ;
}
/// <summary>
/// Removes the Span mapping entry for the specified <paramref name="from"/> expression,
/// and creates a new entry for the specified <paramref name="to"/> expression that maps
/// to the <paramref name="from"/> expression's original Span information. If no Span
/// information is present for the specified <paramref name="from"/> expression then no
/// changes are made to the Span mapping dictionary.
/// </summary>
/// <param name="from">The expression from which to take Span information</param>
/// <param name="to">The expression to which the Span information should be applied</param>
private void ApplySpanMapping ( DbExpression from , DbExpression to )
{
Span argumentSpan ;
if ( TryGetSpan ( from , out argumentSpan ) )
{
AddSpanMapping ( to , argumentSpan ) ;
}
}
/// <summary>
/// Unifies the Span information from the specified <paramref name="left"/> and <paramref name="right"/>
/// expressions, and applies it to the specified <paramref name="to"/> expression. Unification proceeds
/// as follows:
/// - If neither <paramref name="left"/> nor <paramref name="right"/> have Span information, no changes are made
/// - If one of <paramref name="left"/> or <paramref name="right"/> 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 <paramref name="to"/>
/// expression to the Span information.
/// - If both <paramref name="left"/> and <paramref name="right"/> 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 <paramref name="to"/> expression to this new Span.
/// </summary>
/// <param name="left">The first expression argument</param>
/// <param name="right">The second expression argument</param>
/// <param name="to">The result expression</param>
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 < DbSortClause > 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 ;
}
/// <summary>
/// Gets the target type for a CQT cast operation.
/// </summary>
/// <returns>Appropriate type usage, or null if this is a "no-op"</returns>
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 ;
}
/// <summary>
/// Check that the given cast specification is supported and if necessary adjust target type (for instance
/// add precision and scale for Integral -> Decimal casts)
/// </summary>
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 ;
}
/// <summary>
/// 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.
/// </summary>
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 ) ;
}
/// <summary>
/// Gets the target type for an Is or As expression.
/// </summary>
/// <param name="fromType">Input type in model metadata.</param>
/// <param name="toClrType">Test or return type.</param>
/// <param name="operationType">Type of operation; used in error reporting.</param>
/// <param name="fromClrType">Input type in CLR metadata.</param>
/// <returns>Appropriate target type usage.</returns>
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 < ObjectQuery > ( ) ;
}
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 < KeyValuePair < ObjectParameter , QueryParameterExpression > > ( ) ;
}
foreach ( ObjectParameter prm in inlineQuery . QueryState . Parameters )
{
this . _parameters . Add ( new KeyValuePair < ObjectParameter , QueryParameterExpression > ( 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 ;
}
/// <summary>
/// Utility method validating type for comparison ops (isNull, equals, etc.).
/// Only primitive types, entity types, and simple row types (no IGrouping/EntityCollection) are
/// supported.
/// </summary>
private static void VerifyTypeSupportedForComparison ( Type clrType , TypeUsage edmType , Stack < EdmMember > 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 < EdmMember > memberPath )
{
foreach ( EdmMember member in rowType . Properties )
{
if ( null = = memberPath )
{
memberPath = new Stack < EdmMember > ( ) ;
}
memberPath . Push ( member ) ;
VerifyTypeSupportedForComparison ( clrType , member . TypeUsage , memberPath ) ;
memberPath . Pop ( ) ;
}
}
/// <summary>
/// Describe type for exception message.
/// </summary>
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 ) ;
}
/// <summary>
/// Creates an implementation of IsNull. Throws exception when operand type is not supported.
/// </summary>
private DbExpression CreateIsNullExpression ( DbExpression operand , Type operandClrType )
{
VerifyTypeSupportedForComparison ( operandClrType , operand . ResultType , null ) ;
return operand . IsNull ( ) ;
}
/// <summary>
/// Creates an implementation of equals using the given pattern. Throws exception when argument types
/// are not supported for equals comparison.
/// </summary>
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
/// <summary>
/// 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
/// </summary>
/// <param name="call"></param>
/// <param name="insertPercentAtStart">Should '%' be inserted at the begining of the pattern</param>
/// <param name="insertPercentAtEnd">Should '%' be inserted at the end of the pattern</param>
/// <param name="defaultTranslator">The delegate that provides the default translation</param>
/// <returns>The translation</returns>
private DbExpression TranslateFunctionIntoLike ( MethodCallExpression call , bool insertPercentAtStart , bool insertPercentAtEnd , Func < ExpressionConverter , MethodCallExpression , DbExpression , DbExpression , DbExpression > 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 ;
}
/// <summary>
/// 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.
/// </summary>
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 ( ) ;
}
/// <summary>
/// Translates the arguments into DbExpressions
/// and creates a canonical function with the given functionName and these arguments
/// </summary>
/// <param name="functionName">Should represent a non-aggregate canonical function</param>
/// <param name="Expression">Passed only for error handling purposes</param>
/// <param name="linqArguments"></param>
/// <returns></returns>
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 ) ;
}
/// <summary>
/// Creates a canonical function with the given name and the given arguments
/// </summary>
/// <param name="functionName">Should represent a non-aggregate canonical function</param>
/// <param name="Expression">Passed only for error handling purposes</param>
/// <param name="translatedArguments"></param>
/// <returns></returns>
private DbFunctionExpression CreateCanonicalFunction ( string functionName , Expression Expression , params DbExpression [ ] translatedArguments )
{
List < TypeUsage > translatedArgumentTypes = new List < TypeUsage > ( translatedArguments . Length ) ;
foreach ( DbExpression translatedArgument in translatedArguments )
{
translatedArgumentTypes . Add ( translatedArgument . ResultType ) ;
}
EdmFunction function = FindCanonicalFunction ( functionName , translatedArgumentTypes , false /* isGroupAggregateFunction */ , Expression ) ;
return function . Invoke ( translatedArguments ) ;
}
/// <summary>
/// Finds a canonical function with the given functionName and argumentTypes
/// </summary>
/// <param name="functionName"></param>
/// <param name="argumentTypes"></param>
/// <param name="isGroupAggregateFunction"></param>
/// <param name="Expression"></param>
/// <returns></returns>
private EdmFunction FindCanonicalFunction ( string functionName , IList < TypeUsage > argumentTypes , bool isGroupAggregateFunction , Expression Expression )
{
return FindFunction ( EdmNamespaceName , functionName , argumentTypes , isGroupAggregateFunction , Expression ) ;
}
/// <summary>
/// Finds a function with the given namespaceName, functionName and argumentTypes
/// </summary>
/// <param name="namespaceName"></param>
/// <param name="functionName"></param>
/// <param name="argumentTypes"></param>
/// <param name="isGroupAggregateFunction"></param>
/// <param name="Expression"></param>
/// <returns></returns>
private EdmFunction FindFunction ( string namespaceName , string functionName , IList < TypeUsage > argumentTypes , bool isGroupAggregateFunction , Expression Expression )
{
// find the function
IList < EdmFunction > 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 ;
}
/// <summary>
/// Helper method for FindFunction
/// </summary>
/// <param name="Expression"></param>
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 ) ) ;
}
/// <summary>
/// Helper method for FindCanonicalFunction
/// </summary>
/// <param name="Expression"></param>
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 < KeyValuePair < string , DbExpression > > columns , InitializerMetadata initializerMetadata )
{
List < DbExpression > propertyValues = new List < DbExpression > ( columns . Count ) ;
List < EdmProperty > properties = new List < EdmProperty > ( 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
}
}