/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / DataEntity / System / Data / Common / internal / materialization / translator.cs / 1599186 / translator.cs
//------------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// [....]
// [....]
//-----------------------------------------------------------------------------
using System.Collections.Generic;
using System.Data;
using System.Data.Common.QueryCache;
using System.Data.Common.Utils;
using System.Data.Entity;
using System.Data.Mapping;
using System.Data.Metadata.Edm;
using System.Data.Objects;
using System.Data.Objects.ELinq;
using System.Data.Objects.Internal;
using System.Data.Query.InternalTrees;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Security.Permissions;
using System.Security;
using System.Text;
using System.Data.Objects.DataClasses;
using System.Runtime.CompilerServices;
namespace System.Data.Common.Internal.Materialization
{
///
/// Struct containing the requested type and parent column map used
/// as the arg in the Translator visitor.
///
internal struct TranslatorArg
{
internal readonly Type RequestedType;
internal TranslatorArg(Type requestedType)
{
this.RequestedType = requestedType;
}
}
///
/// Type returned by the Translator visitor; allows us to put the logic
/// to ensure a specific return type in a single place, instead of in
/// each Visit method.
///
internal class TranslatorResult
{
private readonly Expression ReturnedExpression;
private readonly Type RequestedType;
internal TranslatorResult(Expression returnedExpression, Type requestedType)
{
this.RequestedType = requestedType;
this.ReturnedExpression = returnedExpression;
}
///
/// Return the expression; wrapped with the appropriate cast/convert
/// logic to guarantee it's type.
///
internal Expression Expression
{
get
{
Expression result = Translator.Emit_EnsureType(ReturnedExpression, RequestedType);
return result;
}
}
///
/// Return the expression without attempting to cast/convert to the requested type.
///
internal Expression UnconvertedExpression
{
get
{
return ReturnedExpression;
}
}
///
/// Checks if the expression represents an wrapped entity and if so creates an expression
/// that extracts the raw entity from the wrapper.
///
internal Expression UnwrappedExpression
{
get
{
if (!typeof(IEntityWrapper).IsAssignableFrom(ReturnedExpression.Type))
{
return ReturnedExpression;
}
return Translator.Emit_UnwrapAndEnsureType(ReturnedExpression, RequestedType);
}
}
}
///
/// For collection results, we really want to know the expression to
/// get the coordinator from its stateslot as well, so we have an
/// additional one...
///
internal class CollectionTranslatorResult : TranslatorResult
{
internal readonly Expression ExpressionToGetCoordinator;
internal CollectionTranslatorResult(Expression returnedExpression, ColumnMap columnMap, Type requestedType, Expression expressionToGetCoordinator)
: base(returnedExpression, requestedType)
{
this.ExpressionToGetCoordinator = expressionToGetCoordinator;
}
}
///
/// Translates query ColumnMap into ShaperFactory. Basically, we interpret the
/// ColumnMap and compile delegates used to materialize results.
///
internal class Translator : ColumnMapVisitorWithResults
{
#region private state
///
/// Gets the O-Space Metadata workspace.
///
private readonly MetadataWorkspace _workspace;
///
/// Gets structure telling us how to interpret 'span' rows (includes implicit
/// relationship span and explicit full span via ObjectQuery.Include().
///
private readonly SpanIndex _spanIndex;
///
/// Gets the MergeOption for the current query (influences our handling of
/// entities when they are materialized).
///
private readonly MergeOption _mergeOption;
///
/// When true, indicates we're processing for the value layer (BridgeDataReader)
/// and not the ObjectMaterializer
///
private readonly bool IsValueLayer;
///
/// Gets scratchpad for topmost nested reader coordinator.
///
private CoordinatorScratchpad _rootCoordinatorScratchpad;
///
/// Gets scratchpad for the coordinator builder for the nested reader currently
/// being translated or emitted.
///
private CoordinatorScratchpad _currentCoordinatorScratchpad;
///
/// Gets number of 'Shaper.State' slots allocated (used to hold onto intermediate
/// values during materialization)
///
private int _stateSlotCount;
///
/// Set to true if any Entity/Complex type/property for which we're emitting a
/// handler is non-public. Used to determine which security checks are necessary
/// when invoking the delegate.
///
private bool _hasNonPublicMembers;
///
/// Keeps track of all LINQ expressions accepted from the user code.
///
private readonly List>> _userExpressions = new List>>();
///
/// Local cache of ObjectTypeMappings for EdmTypes (to prevent expensive lookups).
///
private readonly Dictionary _objectTypeMappings = new Dictionary();
#endregion
#region constructor
private Translator(MetadataWorkspace workspace, SpanIndex spanIndex, MergeOption mergeOption, bool valueLayer)
{
_workspace = workspace;
_spanIndex = spanIndex;
_mergeOption = mergeOption;
IsValueLayer = valueLayer;
}
#endregion
#region "public" surface area
///
/// The main entry point for the translation process. Given a ColumnMap, returns
/// a ShaperFactory which can be used to materialize results for a query.
///
internal static ShaperFactory TranslateColumnMap(QueryCacheManager queryCacheManager, ColumnMap columnMap, MetadataWorkspace workspace, SpanIndex spanIndex, MergeOption mergeOption, bool valueLayer)
{
Debug.Assert(columnMap is CollectionColumnMap, "root column map must be a collection for a query");
// If the query cache already contains a plan, then we're done
ShaperFactory result;
string columnMapKey = ColumnMapKeyBuilder.GetColumnMapKey(columnMap, spanIndex);
ShaperFactoryQueryCacheKey cacheKey = new ShaperFactoryQueryCacheKey(columnMapKey, mergeOption, valueLayer);
if (queryCacheManager.TryCacheLookup(cacheKey, out result))
{
return result;
}
// Didn't find it in the cache, so we have to do the translation; First create
// the translator visitor that recursively tranforms ColumnMaps into Expressions
// stored on the CoordinatorScratchpads it also constructs. We'll compile those
// expressions into delegates later.
Translator translator = new Translator(workspace, spanIndex, mergeOption, valueLayer);
columnMap.Accept(translator, new TranslatorArg(typeof(IEnumerable<>).MakeGenericType(typeof(TRequestedType))));
Debug.Assert(null != translator._rootCoordinatorScratchpad, "translating the root of the query must populate _rootCoordinatorBuilder"); // how can this happen?
// We're good. Go ahead and recursively compile the CoordinatorScratchpads we
// created in the vistor into CoordinatorFactories which contain compiled
// delegates for the expressions we generated.
CoordinatorFactory coordinatorFactory = (CoordinatorFactory)translator._rootCoordinatorScratchpad.Compile();
// Along the way we constructed a nice delegate to perform runtime permission
// checks (e.g. for LinkDemand and non-public members). We need that now.
Action checkPermissionsDelegate = translator.GetCheckPermissionsDelegate();
// Finally, take everything we've produced, and create the ShaperFactory to
// contain it all, then add it to the query cache so we don't need to do this
// for this query again.
result = new ShaperFactory(translator._stateSlotCount, coordinatorFactory, checkPermissionsDelegate, mergeOption);
QueryCacheEntry cacheEntry = new ShaperFactoryQueryCacheEntry(cacheKey, result);
if (queryCacheManager.TryLookupAndAdd(cacheEntry, out cacheEntry))
{
// Someone beat us to it. Use their result instead.
result = ((ShaperFactoryQueryCacheEntry)cacheEntry).GetTarget();
}
return result;
}
///
/// Compiles a delegate taking a Shaper instance and returning values. Used to compile
/// Expressions produced by the emitter.
///
/// Asserts MemberAccess to skip visbility check.
/// This means that that security checks are skipped. Before calling this
/// method you must ensure that you've done a TestComple on expressions provided
/// by the user to ensure the compilation doesn't violate them.
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2128")]
[System.Security.SecuritySafeCritical]
[ReflectionPermission(SecurityAction.Assert, MemberAccess = true)]
internal static Func Compile(Expression body)
{
var lambda = Expression.Lambda>(body, Shaper_Parameter);
return lambda.Compile();
}
///
/// Non-generic version of Compile (where the result type is passed in as an argument rather
/// than a type parameter)
///
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
internal static object Compile(Type resultType, Expression body)
{
MethodInfo compile = Translator_Compile.MakeGenericMethod(resultType);
return compile.Invoke(null, new object[] { body });
}
///
/// Tell the translator about a user-defined LINQ expression that is being inlined
/// in the materializer delegate. Before we produce the shaper factory we'll ensure
/// that security is not violated by using this expression in our delegates.
///
internal void RegisterUserExpression(Expression expression)
{
Expression> lambda = Expression.Lambda>(Expression.Convert(expression, typeof(object)));
_userExpressions.Add(lambda);
}
#endregion
#region helpers
///
/// Allocates a slot in 'Shaper.State' which can be used as storage for
/// materialization tasks (e.g. remembering key values for a nested collection)
///
private int AllocateStateSlot()
{
return _stateSlotCount++;
}
///
/// Returns a delegate performing necessary permission checks identified
/// by this translator. This delegate must be called every time a row is
/// read from the ObjectResult enumerator, since the enumerator can be
/// passed across security contexts.
///
private Action GetCheckPermissionsDelegate()
{
// Emit an action to check runtime permissions.
Action checkPermissions = null;
if (_hasNonPublicMembers)
{
checkPermissions = DemandMemberAccess;
}
else if (_userExpressions.Count > 0)
{
IEnumerable>> userExpressions = _userExpressions;
checkPermissions = () => VerifyUserExpressions(userExpressions);
}
return checkPermissions;
}
private static void DemandMemberAccess()
{
LightweightCodeGenerator.MemberAccessReflectionPermission.Demand();
}
///
/// Try compiling the user expressions to ensure it would succeed without an
/// assert (user expressions are inlined with references to EF internals which
/// require the assert so we need to check the user expressions separately).
///
/// This method is called every time a new query result is returned to make sure
/// the user expressions can be compiled in the current security context.
///
private static void VerifyUserExpressions(IEnumerable>> userExpressions)
{
// As an optimization, check if we have member access permission. If so,
// we know the compile would succeed and don't need to make the effort.
if (!LightweightCodeGenerator.HasMemberAccessReflectionPermission())
{
// If we don't have MemberAccess, compile the expressions to see if they
// might be satisfied by RestrictedMemberAccess.
foreach (Expression> userExpression in userExpressions)
{
userExpression.Compile();
}
}
}
///
/// Return the CLR type we're supposed to materialize for the TypeUsage
///
private Type DetermineClrType(TypeUsage typeUsage)
{
return DetermineClrType(typeUsage.EdmType);
}
///
/// Return the CLR type we're supposed to materialize for the EdmType
///
private Type DetermineClrType(EdmType edmType)
{
Type result = null;
// Normalize for spandex
edmType = ResolveSpanType(edmType);
switch (edmType.BuiltInTypeKind)
{
case BuiltInTypeKind.EntityType:
case BuiltInTypeKind.ComplexType:
if (IsValueLayer)
{
result = typeof(RecordState);
}
else
{
result = LookupObjectMapping(edmType).ClrType.ClrType;
}
break;
case BuiltInTypeKind.RefType:
result = typeof(EntityKey);
break;
case BuiltInTypeKind.CollectionType:
if (IsValueLayer)
{
result = typeof(Coordinator);
}
else
{
EdmType edmElementType = ((CollectionType)edmType).TypeUsage.EdmType;
result = DetermineClrType(edmElementType);
result = typeof(IEnumerable<>).MakeGenericType(result);
}
break;
case BuiltInTypeKind.PrimitiveType:
result = ((PrimitiveType)edmType).ClrEquivalentType;
if (result.IsValueType)
{
result = typeof(Nullable<>).MakeGenericType(result);
}
break;
case BuiltInTypeKind.RowType:
if (IsValueLayer)
{
result = typeof(RecordState);
}
else
{
// LINQ has anonymous types that aren't going to show up in our
// metadata workspace, and we don't want to hydrate a record when
// we need an anonymous type. ELINQ solves this by annotating the
// edmType with some additional information, which we'll pick up
// here.
InitializerMetadata initializerMetadata = ((RowType)edmType).InitializerMetadata;
if (null != initializerMetadata)
{
result = initializerMetadata.ClrType;
}
else
{
// Otherwise, by default, we'll give DbDataRecord results (the
// user can also cast to IExtendedDataRecord)
result = typeof(DbDataRecord);
}
}
break;
default:
Debug.Fail(string.Format(CultureInfo.CurrentCulture, "The type {0} was not the expected scalar, enumeration, collection, structural, nominal, or reference type.", edmType.GetType()));
break;
}
Debug.Assert(null != result, "no result?"); // just making sure we cover this in the switch statement.
return result;
}
///
/// Get the ConstructorInfo for the type specified, and ensure we keep track
/// of any security requirements that the type has.
///
private ConstructorInfo GetConstructor(Type type)
{
ConstructorInfo result = null;
if (!type.IsAbstract)
{
result = LightweightCodeGenerator.GetConstructorForType(type);
// remember security requirements for this constructor
if (!LightweightCodeGenerator.IsPublic(result))
{
_hasNonPublicMembers = true;
}
}
return result;
}
///
/// Retrieves object mapping metadata for the given type. The first time a type
/// is encountered, we cache the metadata to avoid repeating the work for every
/// row in result.
///
/// Caching at the materializer rather than workspace/metadata cache level optimizes
/// for transient types (including row types produced for span, LINQ initializations,
/// collections and projections).
///
private ObjectTypeMapping LookupObjectMapping(EdmType edmType)
{
Debug.Assert(null != edmType, "no edmType?"); // edmType must not be null.
ObjectTypeMapping result;
EdmType resolvedType = ResolveSpanType(edmType);
if (null == resolvedType)
{
resolvedType = edmType;
}
if (!_objectTypeMappings.TryGetValue(resolvedType, out result))
{
result = Util.GetObjectMapping(resolvedType, _workspace);
_objectTypeMappings.Add(resolvedType, result);
}
return result;
}
///
/// Remove spanned info from the edmType
///
///
///
private EdmType ResolveSpanType(EdmType edmType)
{
EdmType result = edmType;
switch (result.BuiltInTypeKind)
{
case BuiltInTypeKind.CollectionType:
// For collections, we have to edmType from the (potentially) spanned
// element of the collection, then build a new Collection around it.
result = ResolveSpanType(((CollectionType)result).TypeUsage.EdmType);
if (null != result)
{
result = new CollectionType(result);
}
break;
case BuiltInTypeKind.RowType:
// If there is a SpanMap, pick up the EdmType from the first column
// in the record, otherwise it's just the type we already have.
RowType rowType = (RowType)result;
if (null != _spanIndex && _spanIndex.HasSpanMap(rowType))
{
result = rowType.Members[0].TypeUsage.EdmType;
}
break;
}
return result;
}
///
/// Creates an expression representing an inline delegate of type Func<Shaper, body.Type>
///
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
private LambdaExpression CreateInlineDelegate(Expression body)
{
// Note that we call through to a typed method so that we can call Expression.Lambda instead
// of the straightforward Expression.Lambda. The latter requires FullTrust.
Type delegateReturnType = body.Type;
MethodInfo createMethod = Translator_TypedCreateInlineDelegate.MakeGenericMethod(delegateReturnType);
LambdaExpression result = (LambdaExpression)createMethod.Invoke(this, new object[] { body });
return result;
}
private Expression> TypedCreateInlineDelegate(Expression body)
{
Expression> result = Expression.Lambda>(body, Shaper_Parameter);
_currentCoordinatorScratchpad.AddInlineDelegate(result);
return result;
}
#endregion
#region Lightweight CodeGen emitters
#region static Reflection info used in emitters
private static readonly MethodInfo DbDataReader_GetValue = typeof(DbDataReader).GetMethod("GetValue");
private static readonly MethodInfo DbDataReader_GetString = typeof(DbDataReader).GetMethod("GetString");
private static readonly MethodInfo DbDataReader_GetInt16 = typeof(DbDataReader).GetMethod("GetInt16");
private static readonly MethodInfo DbDataReader_GetInt32 = typeof(DbDataReader).GetMethod("GetInt32");
private static readonly MethodInfo DbDataReader_GetInt64 = typeof(DbDataReader).GetMethod("GetInt64");
private static readonly MethodInfo DbDataReader_GetBoolean = typeof(DbDataReader).GetMethod("GetBoolean");
private static readonly MethodInfo DbDataReader_GetDecimal = typeof(DbDataReader).GetMethod("GetDecimal");
private static readonly MethodInfo DbDataReader_GetFloat = typeof(DbDataReader).GetMethod("GetFloat");
private static readonly MethodInfo DbDataReader_GetDouble = typeof(DbDataReader).GetMethod("GetDouble");
private static readonly MethodInfo DbDataReader_GetDateTime = typeof(DbDataReader).GetMethod("GetDateTime");
private static readonly MethodInfo DbDataReader_GetGuid = typeof(DbDataReader).GetMethod("GetGuid");
private static readonly MethodInfo DbDataReader_GetByte = typeof(DbDataReader).GetMethod("GetByte");
private static readonly MethodInfo DbDataReader_IsDBNull = typeof(DbDataReader).GetMethod("IsDBNull");
private static readonly ConstructorInfo EntityKey_ctor_SingleKey = typeof(EntityKey).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(EntitySet), typeof(object) }, null);
private static readonly ConstructorInfo EntityKey_ctor_CompositeKey = typeof(EntityKey).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(EntitySet), typeof(object[]) }, null);
private static readonly MethodInfo IEntityKeyWithKey_EntityKey = typeof(System.Data.Objects.DataClasses.IEntityWithKey).GetProperty("EntityKey").GetSetMethod();
private static readonly MethodInfo IEqualityComparerOfString_Equals = typeof(IEqualityComparer).GetMethod("Equals", new Type[] { typeof(string), typeof(string) });
private static readonly ConstructorInfo MaterializedDataRecord_ctor = typeof(MaterializedDataRecord).GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
null, new Type[] { typeof(MetadataWorkspace), typeof(TypeUsage), typeof(object[]) },
null);
private static readonly MethodInfo RecordState_GatherData = typeof(RecordState).GetMethod("GatherData", BindingFlags.NonPublic | BindingFlags.Instance);
private static readonly MethodInfo RecordState_SetNullRecord = typeof(RecordState).GetMethod("SetNullRecord", BindingFlags.NonPublic | BindingFlags.Instance);
private static readonly MethodInfo Shaper_Discriminate = typeof(Shaper).GetMethod("Discriminate");
private static readonly MethodInfo Shaper_GetPropertyValueWithErrorHandling = typeof(Shaper).GetMethod("GetPropertyValueWithErrorHandling");
private static readonly MethodInfo Shaper_GetColumnValueWithErrorHandling = typeof(Shaper).GetMethod("GetColumnValueWithErrorHandling");
private static readonly MethodInfo Shaper_HandleEntity = typeof(Shaper).GetMethod("HandleEntity");
private static readonly MethodInfo Shaper_HandleEntityAppendOnly = typeof(Shaper).GetMethod("HandleEntityAppendOnly");
private static readonly MethodInfo Shaper_HandleEntityNoTracking = typeof(Shaper).GetMethod("HandleEntityNoTracking");
private static readonly MethodInfo Shaper_HandleFullSpanCollection = typeof(Shaper).GetMethod("HandleFullSpanCollection");
private static readonly MethodInfo Shaper_HandleFullSpanElement = typeof(Shaper).GetMethod("HandleFullSpanElement");
private static readonly MethodInfo Shaper_HandleIEntityWithKey = typeof(Shaper).GetMethod("HandleIEntityWithKey");
private static readonly MethodInfo Shaper_HandleRelationshipSpan = typeof(Shaper).GetMethod("HandleRelationshipSpan");
private static readonly MethodInfo Shaper_SetColumnValue = typeof(Shaper).GetMethod("SetColumnValue");
private static readonly MethodInfo Shaper_SetEntityRecordInfo = typeof(Shaper).GetMethod("SetEntityRecordInfo");
private static readonly MethodInfo Shaper_SetState = typeof(Shaper).GetMethod("SetState");
private static readonly MethodInfo Shaper_SetStatePassthrough = typeof(Shaper).GetMethod("SetStatePassthrough");
private static readonly MethodInfo Translator_BinaryEquals = typeof(Translator).GetMethod("BinaryEquals", BindingFlags.NonPublic | BindingFlags.Static);
private static readonly MethodInfo Translator_CheckedConvert = typeof(Translator).GetMethod("CheckedConvert", BindingFlags.NonPublic | BindingFlags.Static);
private static readonly MethodInfo Translator_Compile = typeof(Translator).GetMethod("Compile", BindingFlags.NonPublic | BindingFlags.Static, null, new Type[] { typeof(Expression) }, null);
private static readonly MethodInfo Translator_MultipleDiscriminatorPolymorphicColumnMapHelper = typeof(Translator).GetMethod("MultipleDiscriminatorPolymorphicColumnMapHelper", BindingFlags.NonPublic | BindingFlags.Instance);
private static readonly MethodInfo Translator_TypedCreateInlineDelegate = typeof(Translator).GetMethod("TypedCreateInlineDelegate", BindingFlags.NonPublic | BindingFlags.Instance);
private static readonly PropertyInfo EntityWrapperFactory_NullWrapper = typeof(EntityWrapperFactory).GetProperty("NullWrapper", BindingFlags.Static | BindingFlags.NonPublic);
private static readonly PropertyInfo IEntityWrapper_Entity = typeof(IEntityWrapper).GetProperty("Entity");
private static readonly MethodInfo EntityProxyTypeInfo_SetEntityWrapper = typeof(EntityProxyTypeInfo).GetMethod("SetEntityWrapper", BindingFlags.NonPublic | BindingFlags.Instance);
private static readonly ConstructorInfo PocoPropertyAccessorStrategy_ctor = typeof(PocoPropertyAccessorStrategy).GetConstructor(new Type[] { typeof(object) });
private static readonly ConstructorInfo EntityWithChangeTrackerStrategy_ctor = typeof(EntityWithChangeTrackerStrategy).GetConstructor(new Type[] { typeof(IEntityWithChangeTracker) });
private static readonly ConstructorInfo EntityWithKeyStrategy_ctor = typeof(EntityWithKeyStrategy).GetConstructor(new Type[] { typeof(IEntityWithKey) });
private static readonly ConstructorInfo PocoEntityKeyStrategy_ctor = typeof(PocoEntityKeyStrategy).GetConstructor(new Type[0]);
private static readonly PropertyInfo SnapshotChangeTrackingStrategy_Instance = typeof(SnapshotChangeTrackingStrategy).GetProperty("Instance", BindingFlags.Static | BindingFlags.Public);
private static readonly MethodInfo EntityWrapperFactory_GetPocoPropertyAccessorStrategyFunc = typeof(EntityWrapperFactory).GetMethod("GetPocoPropertyAccessorStrategyFunc", BindingFlags.NonPublic | BindingFlags.Static);
private static readonly MethodInfo EntityWrapperFactory_GetNullPropertyAccessorStrategyFunc = typeof(EntityWrapperFactory).GetMethod("GetNullPropertyAccessorStrategyFunc", BindingFlags.NonPublic | BindingFlags.Static);
private static readonly MethodInfo EntityWrapperFactory_GetEntityWithChangeTrackerStrategyFunc = typeof(EntityWrapperFactory).GetMethod("GetEntityWithChangeTrackerStrategyFunc", BindingFlags.NonPublic | BindingFlags.Static);
private static readonly MethodInfo EntityWrapperFactory_GetSnapshotChangeTrackingStrategyFunc = typeof(EntityWrapperFactory).GetMethod("GetSnapshotChangeTrackingStrategyFunc", BindingFlags.NonPublic | BindingFlags.Static);
private static readonly MethodInfo EntityWrapperFactory_GetEntityWithKeyStrategyStrategyFunc = typeof(EntityWrapperFactory).GetMethod("GetEntityWithKeyStrategyStrategyFunc", BindingFlags.NonPublic | BindingFlags.Static);
private static readonly MethodInfo EntityWrapperFactory_GetPocoEntityKeyStrategyFunc = typeof(EntityWrapperFactory).GetMethod("GetPocoEntityKeyStrategyFunc", BindingFlags.NonPublic | BindingFlags.Static);
#endregion
#region static expressions used in emitters
private static readonly Expression DBNull_Value = Expression.Constant(DBNull.Value, typeof(object));
internal static readonly ParameterExpression Shaper_Parameter = Expression.Parameter(typeof(Shaper), "shaper");
private static readonly ParameterExpression EntityParameter = Expression.Parameter(typeof(object), "entity");
private static readonly Expression Shaper_Reader = Expression.Field(Shaper_Parameter, typeof(Shaper).GetField("Reader"));
private static readonly Expression Shaper_Workspace = Expression.Field(Shaper_Parameter, typeof(Shaper).GetField("Workspace"));
private static readonly Expression Shaper_State = Expression.Field(Shaper_Parameter, typeof(Shaper).GetField("State"));
private static readonly Expression Shaper_Context = Expression.Field(Shaper_Parameter, typeof(Shaper).GetField("Context"));
private static readonly Expression Shaper_Context_Options = Expression.Property(Shaper_Context, typeof(ObjectContext).GetProperty("ContextOptions"));
private static readonly Expression Shaper_ProxyCreationEnabled = Expression.Property(Shaper_Context_Options, typeof(ObjectContextOptions).GetProperty("ProxyCreationEnabled"));
#endregion
///
/// Create expression to AndAlso the expressions and return the result.
///
private static Expression Emit_AndAlso(IEnumerable operands)
{
Expression result = null;
foreach (Expression operand in operands)
{
if (result == null)
{
result = operand;
}
else
{
result = Expression.AndAlso(result, operand);
}
}
return result;
}
///
/// Create expression to bitwise-or the expressions and return the result.
///
private static Expression Emit_BitwiseOr(IEnumerable operands)
{
Expression result = null;
foreach (Expression operand in operands)
{
if (result == null)
{
result = operand;
}
else
{
result = Expression.Or(result, operand);
}
}
return result;
}
///
/// Creates an expression with null value. If the given type cannot be assigned
/// a null value, we create a value that throws when materializing. We don't throw statically
/// because we consistently defer type checks until materialization.
///
/// See SQL BU 588980.
///
/// Type of null expression.
/// Null expression.
internal static Expression Emit_NullConstant(Type type)
{
Expression nullConstant;
EntityUtil.CheckArgumentNull(type, "type");
// check if null can be assigned to the type
if (type.IsClass || TypeSystem.IsNullableType(type))
{
// create the constant directly if it accepts null
nullConstant = Expression.Constant(null, type);
}
else
{
// create (object)null and then cast to the type
nullConstant = Emit_EnsureType(Expression.Constant(null, typeof(object)), type);
}
return nullConstant;
}
///
/// Emits an expression that represnts a NullEntityWrapper instance.
///
/// The type of the null to be wrapped
/// An expression represnting a wrapped null
internal static Expression Emit_WrappedNullConstant(Type type)
{
return Expression.Property(null, EntityWrapperFactory_NullWrapper);
}
///
/// Create expression that guarantees the input expression is of the specified
/// type; no Convert is added if the expression is already of the same type.
///
/// Internal because it is called from the TranslatorResult.
///
internal static Expression Emit_EnsureType(Expression input, Type type)
{
Expression result = input;
if (input.Type != type && !typeof(IEntityWrapper).IsAssignableFrom(input.Type))
{
if (type.IsAssignableFrom(input.Type))
{
// simple convert, just to make sure static type checks succeed
result = Expression.Convert(input, type);
}
else
{
// user is asking for the 'wrong' type... add exception handling
// in case of failure
MethodInfo checkedConvertMethod = Translator_CheckedConvert.MakeGenericMethod(input.Type, type);
result = Expression.Call(checkedConvertMethod, input);
}
}
return result;
}
///
/// Uses Emit_EnsureType and then wraps the result in an IEntityWrapper instance.
///
/// The expression that creates the entity to be wrapped
/// Expression to read the entity key
/// Expression to read the entity set
/// The type that was actuall requested by the client--may be object
/// The type of the identity type of the entity being materialized--never a proxy type
/// The actual type being materialized--may be a proxy type
/// Either NoTracking or AppendOnly depending on whether the entity is to be tracked
/// If true, then a proxy is being created
/// An expression representing the IEntityWrapper for the new entity
internal static Expression Emit_EnsureTypeAndWrap(Expression input, Expression keyReader, Expression entitySetReader, Type requestedType, Type identityType, Type actualType, MergeOption mergeOption, bool isProxy)
{
Expression result = Emit_EnsureType(input, requestedType); // Needed to ensure appropriate exception is thrown
if (!requestedType.IsClass)
{
result = Emit_EnsureType(input, typeof(object));
}
result = Emit_EnsureType(result, actualType); // Needed to ensure appropriate type for wrapper constructor
return CreateEntityWrapper(result, keyReader, entitySetReader, actualType, identityType, mergeOption, isProxy);
}
///
/// Returns an expression that creates an IEntityWrapper approprioate for the type of entity being materialized.
///
private static Expression CreateEntityWrapper(Expression input, Expression keyReader, Expression entitySetReader, Type actualType, Type identityType, MergeOption mergeOption, bool isProxy)
{
Expression result;
bool isIEntityWithKey = typeof(IEntityWithKey).IsAssignableFrom(actualType);
bool isIEntityWithRelationships = typeof(IEntityWithRelationships).IsAssignableFrom(actualType);
bool isIEntityWithChangeTracker = typeof(IEntityWithChangeTracker).IsAssignableFrom(actualType);
if (isIEntityWithRelationships && isIEntityWithChangeTracker && isIEntityWithKey && !isProxy)
{
// This is the case where all our interfaces are implemented by the entity and we are not creating a proxy.
// This is the case that absolutely must be kept fast. It is a simple call to the wrapper constructor.
Type genericType = typeof(LightweightEntityWrapper<>).MakeGenericType(actualType);
ConstructorInfo ci = genericType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.CreateInstance, null,
new Type[] { actualType, typeof(EntityKey), typeof(EntitySet), typeof(ObjectContext), typeof(MergeOption), typeof(Type) }, null);
result = Expression.New(ci, input, keyReader, entitySetReader, Shaper_Context, Expression.Constant(mergeOption, typeof(MergeOption)), Expression.Constant(identityType, typeof(Type)));
}
else
{
// This is the general case. We choose various strategy objects based on the interfaces implemented and
// whether or not we are creating a proxy.
// We pass in lambdas to create the strategy objects so that they can have the materialized entity as
// a parameter while still being set in the wrapper constructor.
Expression propertyAccessorStrategy = !isIEntityWithRelationships || isProxy ?
Expression.Call(EntityWrapperFactory_GetPocoPropertyAccessorStrategyFunc) :
Expression.Call(EntityWrapperFactory_GetNullPropertyAccessorStrategyFunc);
Expression keyStrategy = isIEntityWithKey ?
Expression.Call(EntityWrapperFactory_GetEntityWithKeyStrategyStrategyFunc) :
Expression.Call(EntityWrapperFactory_GetPocoEntityKeyStrategyFunc);
Expression changeTrackingStrategy = isIEntityWithChangeTracker ?
Expression.Call(EntityWrapperFactory_GetEntityWithChangeTrackerStrategyFunc) :
Expression.Call(EntityWrapperFactory_GetSnapshotChangeTrackingStrategyFunc);
Type genericType = isIEntityWithRelationships ?
typeof(EntityWrapperWithRelationships<>).MakeGenericType(actualType) :
typeof(EntityWrapperWithoutRelationships<>).MakeGenericType(actualType);
ConstructorInfo ci = genericType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.CreateInstance, null,
new Type[] { actualType, typeof(EntityKey), typeof(EntitySet), typeof(ObjectContext), typeof(MergeOption), typeof(Type),
typeof(Func