2016-08-03 10:59:49 +00:00
//---------------------------------------------------------------------
// <copyright file="InitializerFacet.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.Generic ;
using System.Data.Common ;
using System.Data.Common.Internal.Materialization ;
using System.Data.Metadata.Edm ;
using System.Data.Objects.DataClasses ;
using System.Data.Objects.Internal ;
using System.Diagnostics ;
using System.Globalization ;
using System.Linq ;
using System.Linq.Expressions ;
using System.Reflection ;
using System.Security ;
using System.Security.Permissions ;
using System.Threading ;
/// <summary>
/// Facet encapsulating information necessary to initialize a LINQ projection
/// result.
/// </summary>
internal abstract class InitializerMetadata : IEquatable < InitializerMetadata >
{
internal readonly Type ClrType ;
internal static readonly MethodInfo UserExpressionMarker = typeof ( InitializerMetadata ) . GetMethod ( "MarkAsUserExpression" , BindingFlags . NonPublic | BindingFlags . Static ) ;
private static long s_identifier ;
internal readonly string Identity ;
private static readonly string s_identifierPrefix = typeof ( InitializerMetadata ) . Name ;
private InitializerMetadata ( Type clrType )
{
Debug . Assert ( null ! = clrType ) ;
ClrType = clrType ;
Identity = s_identifierPrefix + Interlocked . Increment ( ref s_identifier ) . ToString ( CultureInfo . InvariantCulture ) ;
}
// Gets the kind of this initializer (grouping, row, etc.)
internal abstract InitializerMetadataKind Kind { get ; }
// Attempts to retrieve the initializer facet from a type usage
internal static bool TryGetInitializerMetadata ( TypeUsage typeUsage , out InitializerMetadata initializerMetadata )
{
initializerMetadata = null ;
if ( BuiltInTypeKind . RowType = = typeUsage . EdmType . BuiltInTypeKind )
{
initializerMetadata = ( ( RowType ) typeUsage . EdmType ) . InitializerMetadata ;
}
return null ! = initializerMetadata ;
}
// Initializes an initializer for an IGrouping return type
// Requires: resultType is IGrouping<T, K> instance.
internal static InitializerMetadata CreateGroupingInitializer ( EdmItemCollection itemCollection , Type resultType )
{
return itemCollection . GetCanonicalInitializerMetadata ( new GroupingInitializerMetadata ( resultType ) ) ;
}
// Initializes an initializer for a MemberInit expression
internal static InitializerMetadata CreateProjectionInitializer ( EdmItemCollection itemCollection , MemberInitExpression initExpression ,
MemberInfo [ ] members )
{
return itemCollection . GetCanonicalInitializerMetadata ( new ProjectionInitializerMetadata ( initExpression , members ) ) ;
}
// Initializes an initializer for a New expression
internal static InitializerMetadata CreateProjectionInitializer ( EdmItemCollection itemCollection , NewExpression newExpression )
{
return itemCollection . GetCanonicalInitializerMetadata ( new ProjectionNewMetadata ( newExpression ) ) ;
}
// Initializes an initializer for a New expression with no properties
internal static InitializerMetadata CreateEmptyProjectionInitializer ( EdmItemCollection itemCollection , NewExpression newExpression )
{
return itemCollection . GetCanonicalInitializerMetadata ( new EmptyProjectionNewMetadata ( newExpression ) ) ;
}
// Creates metadata for entity collection materialization
internal static InitializerMetadata CreateEntityCollectionInitializer ( EdmItemCollection itemCollection , Type type , NavigationProperty navigationProperty )
{
return itemCollection . GetCanonicalInitializerMetadata ( new EntityCollectionInitializerMetadata ( type , navigationProperty ) ) ;
}
private static T MarkAsUserExpression < T > ( T value )
{
// No op. This is used as a marker inside of an expression tree to indicate
// that the input expression is not trusted.
return value ;
}
internal virtual void AppendColumnMapKey ( ColumnMapKeyBuilder builder )
{
// by default, the type is sufficient (more information is needed for EntityCollection and initializers)
builder . Append ( "CLR-" , this . ClrType ) ;
}
public override bool Equals ( object obj )
{
Debug . Fail ( "use typed Equals method only" ) ;
return Equals ( obj as InitializerMetadata ) ;
}
public bool Equals ( InitializerMetadata other )
{
Debug . Assert ( null ! = other , "must not use a null key" ) ;
if ( object . ReferenceEquals ( this , other ) ) { return true ; }
if ( this . Kind ! = other . Kind ) { return false ; }
if ( ! this . ClrType . Equals ( other . ClrType ) ) { return false ; }
return IsStructurallyEquivalent ( other ) ;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2303", Justification="ClrType is not expected to be an Embedded Interop Type.")]
public override int GetHashCode ( )
{
return ClrType . GetHashCode ( ) ;
}
/// <summary>
/// Requires: other has the same type as this and refers to the same CLR type
/// Determine whether this Metadata is compatible with the other based on record layout.
/// </summary>
protected virtual bool IsStructurallyEquivalent ( InitializerMetadata other )
{
return true ;
}
/// <summary>
/// Produces an expression initializing an instance of ClrType (given emitters for input
/// columns)
/// </summary>
internal abstract Expression Emit ( Translator translator , List < TranslatorResult > propertyTranslatorResults ) ;
/// <summary>
/// Yields expected types for input columns. Null values are returned for children
/// whose type is irrelevant to the initializer.
/// </summary>
internal abstract IEnumerable < Type > GetChildTypes ( ) ;
/// <summary>
/// return a list of propertyReader expressions from an array of translator results.
/// </summary>
/// <param name="propertyTranslatorResults"></param>
/// <returns></returns>
protected static List < Expression > GetPropertyReaders ( List < TranslatorResult > propertyTranslatorResults )
{
List < Expression > propertyReaders = propertyTranslatorResults . Select ( s = > s . UnwrappedExpression ) . ToList ( ) ;
return propertyReaders ;
}
/// <summary>
/// Implementation of IGrouping that can be initialized using the standard
/// initializer pattern supported by ELinq
/// </summary>
/// <typeparam name="K">Type of key</typeparam>
/// <typeparam name="T">Type of record</typeparam>
private class Grouping < K , T > : IGrouping < K , T >
{
public Grouping ( K key , IEnumerable < T > group )
{
_key = key ;
_group = group ;
}
private readonly K _key ;
private readonly IEnumerable < T > _group ;
public K Key
{
get { return _key ; }
}
public IEnumerable < T > Group
{
get { return _group ; }
}
IEnumerator < T > IEnumerable < T > . GetEnumerator ( )
{
if ( null = = _group )
{
yield break ;
}
foreach ( T member in _group )
{
yield return member ;
}
}
System . Collections . IEnumerator System . Collections . IEnumerable . GetEnumerator ( )
{
return ( ( IEnumerable < T > ) this ) . GetEnumerator ( ) ;
}
}
/// <summary>
/// Metadata for grouping initializer.
/// </summary>
private class GroupingInitializerMetadata : InitializerMetadata
{
internal GroupingInitializerMetadata ( Type type )
: base ( type )
{
}
internal override InitializerMetadataKind Kind { get { return InitializerMetadataKind . Grouping ; } }
internal override Expression Emit ( Translator translator , List < TranslatorResult > propertyTranslatorResults )
{
// Create expression of the form:
// new Grouping<K, T>(children[0], children[1])
// Collect information...
Debug . Assert ( ClrType . IsGenericType & &
typeof ( IGrouping < , > ) . Equals ( ClrType . GetGenericTypeDefinition ( ) ) ) ;
Debug . Assert ( propertyTranslatorResults . Count = = 2 ) ;
Type keyType = this . ClrType . GetGenericArguments ( ) [ 0 ] ;
Type groupElementType = this . ClrType . GetGenericArguments ( ) [ 1 ] ;
Type groupType = typeof ( Grouping < , > ) . MakeGenericType ( keyType , groupElementType ) ;
ConstructorInfo constructor = groupType . GetConstructors ( ) . Single ( ) ;
// new Grouping<K, T>(children[0], children[1])
Expression newGrouping = Expression . Convert ( Expression . New ( constructor , GetPropertyReaders ( propertyTranslatorResults ) ) , this . ClrType ) ;
return newGrouping ;
}
internal override IEnumerable < Type > GetChildTypes ( )
{
// Collect information...
Debug . Assert ( ClrType . IsGenericType & &
typeof ( IGrouping < , > ) . Equals ( ClrType . GetGenericTypeDefinition ( ) ) ) ;
Type keyType = this . ClrType . GetGenericArguments ( ) [ 0 ] ;
Type groupElementType = this . ClrType . GetGenericArguments ( ) [ 1 ] ;
// key
yield return keyType ;
// group
yield return typeof ( IEnumerable < > ) . MakeGenericType ( groupElementType ) ;
}
}
/// <summary>
/// Metadata for anonymous type materialization.
/// </summary>
private class ProjectionNewMetadata : InitializerMetadata
{
internal ProjectionNewMetadata ( NewExpression newExpression )
: base ( newExpression . Type )
{
Debug . Assert ( null ! = newExpression ) ;
_newExpression = newExpression ;
}
private readonly NewExpression _newExpression ;
internal override InitializerMetadataKind Kind { get { return InitializerMetadataKind . ProjectionNew ; } }
protected override bool IsStructurallyEquivalent ( InitializerMetadata other )
{
// caller must ensure the type matches
ProjectionNewMetadata otherProjection = ( ProjectionNewMetadata ) other ;
if ( this . _newExpression . Members = = null & & otherProjection . _newExpression . Members = = null )
{
return true ;
}
if ( this . _newExpression . Members = = null | | otherProjection . _newExpression . Members = = null )
{
return false ;
}
if ( this . _newExpression . Members . Count ! = otherProjection . _newExpression . Members . Count )
{
return false ;
}
for ( int i = 0 ; i < this . _newExpression . Members . Count ; i + + )
{
MemberInfo thisMember = this . _newExpression . Members [ i ] ;
MemberInfo otherMember = otherProjection . _newExpression . Members [ i ] ;
if ( ! thisMember . Equals ( otherMember ) )
{
return false ;
}
}
return true ;
}
internal override Expression Emit ( Translator translator , List < TranslatorResult > propertyTranslatorResults )
{
// Create expression of the form:
// _newExpression(children)
// (ClrType)null
Expression nullProjection = Expression . Constant ( null , this . ClrType ) ;
// _newExpression with members rebound
Expression newProjection = Expression . New ( _newExpression . Constructor , GetPropertyReaders ( propertyTranslatorResults ) ) ;
// Indicate that this expression is provided by the user and should not be trusted.
return Expression . Call ( UserExpressionMarker . MakeGenericMethod ( newProjection . Type ) , newProjection ) ;
}
internal override IEnumerable < Type > GetChildTypes ( )
{
// return all argument types
return _newExpression . Arguments . Select ( arg = > arg . Type ) ;
}
internal override void AppendColumnMapKey ( ColumnMapKeyBuilder builder )
{
base . AppendColumnMapKey ( builder ) ;
builder . Append ( _newExpression . Constructor . ToString ( ) ) ;
foreach ( var member in _newExpression . Members ? ? Enumerable . Empty < MemberInfo > ( ) )
{
builder . Append ( "DT" , member . DeclaringType ) ;
builder . Append ( "." + member . Name ) ;
}
}
}
private class EmptyProjectionNewMetadata : ProjectionNewMetadata
{
internal EmptyProjectionNewMetadata ( NewExpression newExpression )
: base ( newExpression )
{
}
internal override Expression Emit ( Translator translator , List < TranslatorResult > propertyReaders )
{
// ignore sentinel column
return base . Emit ( translator , new List < TranslatorResult > ( ) ) ;
}
internal override IEnumerable < Type > GetChildTypes ( )
{
// ignore sentinel column
yield return null ;
}
}
/// <summary>
/// Metadata for standard projection initializers.
/// </summary>
private class ProjectionInitializerMetadata : InitializerMetadata
{
internal ProjectionInitializerMetadata ( MemberInitExpression initExpression , MemberInfo [ ] members )
: base ( initExpression . Type )
{
Debug . Assert ( null ! = initExpression ) ;
Debug . Assert ( null ! = members ) ;
_initExpression = initExpression ;
_members = members ;
}
private readonly MemberInitExpression _initExpression ;
private readonly MemberInfo [ ] _members ;
internal override InitializerMetadataKind Kind { get { return InitializerMetadataKind . ProjectionInitializer ; } }
protected override bool IsStructurallyEquivalent ( InitializerMetadata other )
{
// caller must ensure the type matches
ProjectionInitializerMetadata otherProjection = ( ProjectionInitializerMetadata ) other ;
if ( this . _initExpression . Bindings . Count ! = otherProjection . _initExpression . Bindings . Count )
{
return false ;
}
for ( int i = 0 ; i < this . _initExpression . Bindings . Count ; i + + )
{
MemberBinding thisBinding = this . _initExpression . Bindings [ i ] ;
MemberBinding otherBinding = otherProjection . _initExpression . Bindings [ i ] ;
if ( ! thisBinding . Member . Equals ( otherBinding . Member ) )
{
return false ;
}
}
return true ;
}
internal override Expression Emit ( Translator translator , List < TranslatorResult > propertyReaders )
{
// Create expression of the form:
// _initExpression(children)
// create member bindings (where values are taken from children)
MemberBinding [ ] memberBindings = new MemberBinding [ _initExpression . Bindings . Count ] ;
MemberBinding [ ] constantMemberBindings = new MemberBinding [ memberBindings . Length ] ;
for ( int i = 0 ; i < memberBindings . Length ; i + + )
{
MemberBinding originalBinding = _initExpression . Bindings [ i ] ;
Expression value = propertyReaders [ i ] . UnwrappedExpression ;
MemberBinding newBinding = Expression . Bind ( originalBinding . Member , value ) ;
MemberBinding constantBinding = Expression . Bind ( originalBinding . Member , Expression . Constant (
TypeSystem . GetDefaultValue ( value . Type ) , value . Type ) ) ;
memberBindings [ i ] = newBinding ;
constantMemberBindings [ i ] = constantBinding ;
}
Expression newProjection = Expression . MemberInit ( _initExpression . NewExpression , memberBindings ) ;
// Indicate that this expression is provided by the user and should not be trusted.
return Expression . Call ( UserExpressionMarker . MakeGenericMethod ( newProjection . Type ) , newProjection ) ;
}
internal override IEnumerable < Type > GetChildTypes ( )
{
// return all argument types
foreach ( var binding in _initExpression . Bindings )
{
// determine member type
Type memberType ;
string name ;
TypeSystem . PropertyOrField ( binding . Member , out name , out memberType ) ;
yield return memberType ;
}
}
internal override void AppendColumnMapKey ( ColumnMapKeyBuilder builder )
{
base . AppendColumnMapKey ( builder ) ;
foreach ( var binding in _initExpression . Bindings )
{
builder . Append ( "," , binding . Member . DeclaringType ) ;
builder . Append ( "." + binding . Member . Name ) ;
}
}
}
/// <summary>
/// Metadata for entity collection initializer.
/// </summary>
private class EntityCollectionInitializerMetadata : InitializerMetadata
{
internal EntityCollectionInitializerMetadata ( Type type , NavigationProperty navigationProperty )
: base ( type )
{
Debug . Assert ( null ! = navigationProperty ) ;
_navigationProperty = navigationProperty ;
}
private readonly NavigationProperty _navigationProperty ;
internal override InitializerMetadataKind Kind { get { return InitializerMetadataKind . EntityCollection ; } }
/// <summary>
/// Make sure the other metadata instance generates the same property
/// (otherwise, we get incorrect behavior where multiple nav props return
/// the same type)
/// </summary>
protected override bool IsStructurallyEquivalent ( InitializerMetadata other )
{
// caller must ensure the type matches
EntityCollectionInitializerMetadata otherInitializer = ( EntityCollectionInitializerMetadata ) other ;
return this . _navigationProperty . Equals ( otherInitializer . _navigationProperty ) ;
}
private static readonly MethodInfo s_createEntityCollectionMethod = typeof ( EntityCollectionInitializerMetadata ) . GetMethod ( "CreateEntityCollection" ,
BindingFlags . Static | BindingFlags . Public ) ;
internal override Expression Emit ( Translator translator , List < TranslatorResult > propertyTranslatorResults )
{
Debug . Assert ( propertyTranslatorResults . Count > 1 , "no properties?" ) ;
Debug . Assert ( propertyTranslatorResults [ 1 ] is CollectionTranslatorResult , "not a collection?" ) ;
Type elementType = GetElementType ( ) ;
MethodInfo createEntityCollectionMethod = s_createEntityCollectionMethod . MakeGenericMethod ( elementType ) ;
Expression shaper = Translator . Shaper_Parameter ;
Expression owner = propertyTranslatorResults [ 0 ] . Expression ;
CollectionTranslatorResult collectionResult = propertyTranslatorResults [ 1 ] as CollectionTranslatorResult ;
Expression coordinator = collectionResult . ExpressionToGetCoordinator ;
// CreateEntityCollection(shaper, owner, elements, relationshipName, targetRoleName)
Expression result = Expression . Call ( createEntityCollectionMethod ,
shaper , owner , coordinator , Expression . Constant ( _navigationProperty . RelationshipType . FullName ) , Expression . Constant ( _navigationProperty . ToEndMember . Name ) ) ;
return result ;
}
public static EntityCollection < T > CreateEntityCollection < T > ( Shaper state , IEntityWrapper wrappedOwner , Coordinator < T > coordinator , string relationshipName , string targetRoleName )
where T : class
{
if ( null = = wrappedOwner . Entity )
{
return null ;
}
else
{
EntityCollection < T > result = wrappedOwner . RelationshipManager . GetRelatedCollection < T > ( relationshipName , targetRoleName ) ;
// register a handler for deferred loading (when the nested result has been consumed)
coordinator . RegisterCloseHandler ( ( readerState , elements ) = > result . Load ( elements , readerState . MergeOption ) ) ;
return result ;
}
}
internal override IEnumerable < Type > GetChildTypes ( )
{
Type elementType = GetElementType ( ) ;
yield return null ; // defer in determining entity type...
yield return typeof ( IEnumerable < > ) . MakeGenericType ( elementType ) ;
}
internal override void AppendColumnMapKey ( ColumnMapKeyBuilder builder )
{
base . AppendColumnMapKey ( builder ) ;
builder . Append ( ",NP" + _navigationProperty . Name ) ;
builder . Append ( ",AT" , _navigationProperty . DeclaringType ) ;
}
private Type GetElementType ( )
{
// POCO support requires that we allow ICollection<T> collections. This allows a POCO collection
// to be projected in a LINQ query.
Type elementType ;
if ( ! EntityUtil . TryGetICollectionElementType ( ClrType , out elementType ) )
{
throw EntityUtil . InvalidOperation ( System . Data . Entity . Strings . ELinq_UnexpectedTypeForNavigationProperty (
_navigationProperty ,
typeof ( EntityCollection < > ) , typeof ( ICollection < > ) ,
ClrType ) ) ;
}
return elementType ;
}
}
}
internal enum InitializerMetadataKind
{
Grouping ,
ProjectionNew ,
ProjectionInitializer ,
EntityCollection ,
}
}