e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
547 lines
23 KiB
C#
547 lines
23 KiB
C#
//---------------------------------------------------------------------
|
|
// <copyright file="InitializerFacet.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//
|
|
// @owner [....]
|
|
//---------------------------------------------------------------------
|
|
|
|
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,
|
|
}
|
|
}
|