//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // // Microsoft // Microsoft //------------------------------------------------------------------------------ namespace System.Data.Common.Internal.Materialization { 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.DataClasses; using System.Data.Objects.ELinq; using System.Data.Objects.Internal; using System.Data.Query.InternalTrees; using System.Diagnostics; using System.Globalization; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Security.Permissions; /// /// 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; /// /// 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, ShaperFactory>(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 QueryCacheEntry(cacheKey, result); if (queryCacheManager.TryLookupAndAdd(cacheEntry, out cacheEntry)) { // Someone beat us to it. Use their result instead. result = (ShaperFactory)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 }); } #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. return _hasNonPublicMembers ? (Action)DemandMemberAccess : null; } 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.EnumType: if (IsValueLayer) { result = DetermineClrType(((EnumType)edmType).UnderlyingType); } else { result = LookupObjectMapping(edmType).ClrType.ClrType; result = typeof(Nullable<>).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_GetGeographyColumnValue = typeof(Shaper).GetMethod("GetGeographyColumnValue"); private static readonly MethodInfo Shaper_GetGeometryColumnValue = typeof(Shaper).GetMethod("GetGeometryColumnValue"); private static readonly MethodInfo Shaper_GetSpatialColumnValueWithErrorHandling = typeof(Shaper).GetMethod("GetSpatialColumnValueWithErrorHandling"); private static readonly MethodInfo Shaper_GetSpatialPropertyValueWithErrorHandling = typeof(Shaper).GetMethod("GetSpatialPropertyValueWithErrorHandling"); 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"); internal 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), typeof(Func), typeof(Func) }, null); result = Expression.New(ci, input, keyReader, entitySetReader, Shaper_Context, Expression.Constant(mergeOption, typeof(MergeOption)), Expression.Constant(identityType, typeof(Type)), propertyAccessorStrategy, changeTrackingStrategy, keyStrategy); } result = Expression.Convert(result, typeof(IEntityWrapper)); return result; } /// /// Takes an expression that represents an IEntityWrapper instance and creates a new /// expression that extracts the raw entity from this. /// internal static Expression Emit_UnwrapAndEnsureType(Expression input, Type type) { return Translator.Emit_EnsureType(Expression.Property(input, IEntityWrapper_Entity), type); } /// /// Method that the generated expression calls when the types are not /// assignable /// private static TTarget CheckedConvert(TSource value) { checked { try { return (TTarget)(object)value; } catch (InvalidCastException) { Type valueType = value.GetType(); // In the case of CompensatingCollection, simply report IEnumerable in the // exception message because the user has no reason to know what the type represents. if (valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof(CompensatingCollection<>)) { valueType = typeof(IEnumerable<>).MakeGenericType(valueType.GetGenericArguments()); } throw EntityUtil.ValueInvalidCast(valueType, typeof(TTarget)); } catch (NullReferenceException) { throw EntityUtil.ValueNullReferenceCast(typeof(TTarget)); } } } /// /// Create expression to compare the results of two expressions and return /// whether they are equal. Note we have special case logic for byte arrays. /// private static Expression Emit_Equal(Expression left, Expression right) { Expression result; Debug.Assert(left.Type == right.Type, "equals with different types"); if (typeof(byte[]) == left.Type) { result = Expression.Call(Translator_BinaryEquals, left, right); } else { result = Expression.Equal(left, right); } return result; } /// /// Helper method used in expressions generated by Emit_Equal to perform a /// byte-by-byte comparison of two byte arrays. There really ought to be /// a way to do this in the framework but I'm unaware of it. /// private static bool BinaryEquals(byte[] left, byte[] right) { if (null == left) { return null == right; } else if (null == right) { return false; } if (left.Length != right.Length) { return false; } for (int i = 0; i < left.Length; i++) { if (left[i] != right[i]) { return false; } } return true; } /// /// Creates expression to construct an EntityKey. Assumes that both the key has /// a value (Emit_EntityKey_HasValue == true) and that the EntitySet has value /// (EntitySet != null). /// private static Expression Emit_EntityKey_ctor(Translator translator, EntityIdentity entityIdentity, bool isForColumnValue, out Expression entitySetReader) { Expression result; Expression setEntitySetStateSlotValue = null; // First build the expressions that read each value that comprises the EntityKey List keyReaders = new List(entityIdentity.Keys.Length); for (int i = 0; i < entityIdentity.Keys.Length; i++) { Expression keyReader = entityIdentity.Keys[i].Accept(translator, new TranslatorArg(typeof(object))).Expression; keyReaders.Add(keyReader); } // Next build the expression that determines us the entitySet; how we do this differs // depending on whether we have a simple or discriminated identity. SimpleEntityIdentity simpleEntityIdentity = entityIdentity as SimpleEntityIdentity; if (null != simpleEntityIdentity) { if (simpleEntityIdentity.EntitySet == null) { // 'Free-floating' entities do not have entity keys. entitySetReader = Expression.Constant(null, typeof(EntitySet)); return Expression.Constant(null, typeof(EntityKey)); } // For SimpleEntityIdentities, the entitySet expression is a constant entitySetReader = Expression.Constant(simpleEntityIdentity.EntitySet, typeof(EntitySet)); } else { // For DiscriminatedEntityIdentities, the we have to search the EntitySetMap // for the matching discriminator value; we'll get the discriminator first, // the compare them all in sequence. DiscriminatedEntityIdentity discriminatedEntityIdentity = (DiscriminatedEntityIdentity)entityIdentity; Expression discriminator = discriminatedEntityIdentity.EntitySetColumnMap.Accept(translator, new TranslatorArg(typeof(int?))).Expression; EntitySet[] entitySets = discriminatedEntityIdentity.EntitySetMap; // // (_discriminator == 0 ? entitySets[0] : (_discriminator == 1 ? entitySets[1] ... : null) entitySetReader = Expression.Constant(null, typeof(EntitySet)); for (int i = 0; i < entitySets.Length; i++) { entitySetReader = Expression.Condition( Expression.Equal(discriminator, Expression.Constant(i, typeof(int?))), Expression.Constant(entitySets[i], typeof(EntitySet)), entitySetReader ); } // Allocate a stateSlot to contain the entitySet we determine, and ensure we // store it there on the way to constructing the key. int entitySetStateSlotNumber = translator.AllocateStateSlot(); setEntitySetStateSlotValue = Emit_Shaper_SetStatePassthrough(entitySetStateSlotNumber, entitySetReader); entitySetReader = Emit_Shaper_GetState(entitySetStateSlotNumber, typeof(EntitySet)); } // And now that we have all the pieces, construct the EntityKey using the appropriate // constructor (there's an optimized constructor for the single key case) if (1 == entityIdentity.Keys.Length) { // new EntityKey(entitySet, keyReaders[0]) result = Expression.New(EntityKey_ctor_SingleKey, entitySetReader, keyReaders[0]); } else { // new EntityKey(entitySet, { keyReaders[0], ... keyReaders[n] }) result = Expression.New(EntityKey_ctor_CompositeKey, entitySetReader, Expression.NewArrayInit(typeof(object), keyReaders)); } // In the case where we've had to store the entitySetReader value in a // state slot, we test the value for non-null before we construct the // entityKey. We use this opportunity to stuff the value into the state // slot, so the code above that attempts to read it from there will find // it. if (null != setEntitySetStateSlotValue) { Expression noEntityKeyExpression; if (translator.IsValueLayer && !isForColumnValue) { noEntityKeyExpression = Expression.Constant(EntityKey.NoEntitySetKey, typeof(EntityKey)); } else { noEntityKeyExpression = Expression.Constant(null, typeof(EntityKey)); } result = Expression.Condition( Expression.Equal(setEntitySetStateSlotValue, Expression.Constant(null, typeof(EntitySet))), noEntityKeyExpression, result ); } return result; } /// /// Create expression that verifies that the entityKey has a value. Note we just /// presume that if the first key is non-null, all the keys will be valid. /// private static Expression Emit_EntityKey_HasValue(SimpleColumnMap[] keyColumns) { Debug.Assert(0 < keyColumns.Length, "empty keyColumns?"); // !shaper.Reader.IsDBNull(keyColumn[0].ordinal) Expression result = Emit_Reader_IsDBNull(keyColumns[0]); result = Expression.Not(result); return result; } /// /// Create expression to call the GetValue method of the shaper's source data reader /// private static Expression Emit_Reader_GetValue(int ordinal, Type type) { // (type)shaper.Reader.GetValue(ordinal) Expression result = Emit_EnsureType(Expression.Call(Shaper_Reader, DbDataReader_GetValue, Expression.Constant(ordinal)), type); return result; } /// /// Create expression to call the IsDBNull method of the shaper's source data reader /// private static Expression Emit_Reader_IsDBNull(int ordinal) { // shaper.Reader.IsDBNull(ordinal) Expression result = Expression.Call(Shaper_Reader, DbDataReader_IsDBNull, Expression.Constant(ordinal)); return result; } /// /// Create expression to call the IsDBNull method of the shaper's source data reader /// for the scalar column represented by the column map. /// private static Expression Emit_Reader_IsDBNull(ColumnMap columnMap) { // Expression result = Emit_Reader_IsDBNull(((ScalarColumnMap)columnMap).ColumnPos); return result; } /// /// Create expression to read a property value with error handling /// private static Expression Emit_Shaper_GetPropertyValueWithErrorHandling(Type propertyType, int ordinal, string propertyName, string typeName, TypeUsage columnType) { // // shaper.GetSpatialColumnValueWithErrorHandling(ordinal, propertyName, typeName, primitiveColumnType) OR shaper.GetColumnValueWithErrorHandling(ordinal, propertyName, typeName) Expression result; PrimitiveTypeKind primitiveColumnType; if (Helper.IsSpatialType(columnType, out primitiveColumnType)) { result = Expression.Call(Shaper_Parameter, Shaper_GetSpatialPropertyValueWithErrorHandling.MakeGenericMethod(propertyType), Expression.Constant(ordinal), Expression.Constant(propertyName), Expression.Constant(typeName), Expression.Constant(primitiveColumnType, typeof(PrimitiveTypeKind))); } else { result = Expression.Call(Shaper_Parameter, Shaper_GetPropertyValueWithErrorHandling.MakeGenericMethod(propertyType), Expression.Constant(ordinal), Expression.Constant(propertyName), Expression.Constant(typeName)); } return result; } /// /// Create expression to read a column value with error handling /// private static Expression Emit_Shaper_GetColumnValueWithErrorHandling(Type resultType, int ordinal, TypeUsage columnType) { // shaper.GetSpatialColumnValueWithErrorHandling(ordinal, primitiveColumnType) OR shaper.GetColumnValueWithErrorHandling(ordinal) Expression result; PrimitiveTypeKind primitiveColumnType; if (Helper.IsSpatialType(columnType, out primitiveColumnType)) { primitiveColumnType = Helper.IsGeographicType((PrimitiveType)columnType.EdmType) ? PrimitiveTypeKind.Geography : PrimitiveTypeKind.Geometry; result = Expression.Call(Shaper_Parameter, Shaper_GetSpatialColumnValueWithErrorHandling.MakeGenericMethod(resultType), Expression.Constant(ordinal), Expression.Constant(primitiveColumnType, typeof(PrimitiveTypeKind))); } else { result = Expression.Call(Shaper_Parameter, Shaper_GetColumnValueWithErrorHandling.MakeGenericMethod(resultType), Expression.Constant(ordinal)); } return result; } /// /// Create expression to read a column value of type System.Data.Spatial.DbGeography by delegating to the DbSpatialServices implementation of the underlying provider /// private static Expression Emit_Shaper_GetGeographyColumnValue(int ordinal) { // shaper.GetGeographyColumnValue(ordinal) Expression result = Expression.Call(Shaper_Parameter, Shaper_GetGeographyColumnValue, Expression.Constant(ordinal)); return result; } /// /// Create expression to read a column value of type System.Data.Spatial.DbGeometry by delegating to the DbSpatialServices implementation of the underlying provider /// private static Expression Emit_Shaper_GetGeometryColumnValue(int ordinal) { // shaper.GetGeometryColumnValue(ordinal) Expression result = Expression.Call(Shaper_Parameter, Shaper_GetGeometryColumnValue, Expression.Constant(ordinal)); return result; } /// /// Create expression to read an item from the shaper's state array /// private static Expression Emit_Shaper_GetState(int stateSlotNumber, Type type) { // (type)shaper.State[stateSlotNumber] Expression result = Emit_EnsureType(Expression.ArrayIndex(Shaper_State, Expression.Constant(stateSlotNumber)), type); return result; } /// /// Create expression to set an item in the shaper's state array /// private static Expression Emit_Shaper_SetState(int stateSlotNumber, Expression value) { // shaper.SetState(stateSlotNumber, value) Expression result = Expression.Call(Shaper_Parameter, Shaper_SetState.MakeGenericMethod(value.Type), Expression.Constant(stateSlotNumber), value); return result; } /// /// Create expression to set an item in the shaper's state array /// private static Expression Emit_Shaper_SetStatePassthrough(int stateSlotNumber, Expression value) { // shaper.SetState(stateSlotNumber, value) Expression result = Expression.Call(Shaper_Parameter, Shaper_SetStatePassthrough.MakeGenericMethod(value.Type), Expression.Constant(stateSlotNumber), value); return result; } #endregion #region ColumnMapVisitor implementation // utility accept that looks up CLR type private static TranslatorResult AcceptWithMappedType(Translator translator, ColumnMap columnMap, ColumnMap parent) { Type type = translator.DetermineClrType(columnMap.Type); TranslatorResult result = columnMap.Accept(translator, new TranslatorArg(type)); return result; } #region structured columns /// /// Visit(ComplexTypeColumnMap) /// internal override TranslatorResult Visit(ComplexTypeColumnMap columnMap, TranslatorArg arg) { Expression result = null; Expression nullSentinelCheck = null; if (null != columnMap.NullSentinel) { nullSentinelCheck = Emit_Reader_IsDBNull(columnMap.NullSentinel); } if (IsValueLayer) { result = BuildExpressionToGetRecordState(columnMap, null, null, nullSentinelCheck); } else { ComplexType complexType = (ComplexType)columnMap.Type.EdmType; Type clrType = DetermineClrType(complexType); ConstructorInfo constructor = GetConstructor(clrType); // Build expressions to read the property values from the source data // reader and bind them to their target properties List propertyBindings = CreatePropertyBindings(columnMap, clrType, complexType.Properties); // We have all the property bindings now; go ahead and build the expression to // construct the type and store the property values. result = Expression.MemberInit(Expression.New(constructor), propertyBindings); // If there's a null sentinel, then everything above is gated upon whether // it's value is DBNull.Value. if (null != nullSentinelCheck) { // shaper.Reader.IsDBNull(nullsentinelOridinal) ? (type)null : result result = Expression.Condition(nullSentinelCheck, Emit_NullConstant(result.Type), result); } } return new TranslatorResult(result, arg.RequestedType); } /// /// Visit(EntityColumnMap) /// internal override TranslatorResult Visit(EntityColumnMap columnMap, TranslatorArg arg) { Expression result; // Build expressions to read the entityKey and determine the entitySet. Note // that we attempt to optimize things such that we won't construct anything // that isn't needed, depending upon the interfaces the clrType derives from // and the MergeOption that was requested. // // We always need the entitySet, except when MergeOption.NoTracking // // We always need the entityKey, except when MergeOption.NoTracking and the // clrType doesn't derive from IEntityWithKey EntityIdentity entityIdentity = columnMap.EntityIdentity; Expression entitySetReader = null; Expression entityKeyReader = Emit_EntityKey_ctor(this, entityIdentity, false, out entitySetReader); if (IsValueLayer) { Expression nullCheckExpression = Expression.Not(Emit_EntityKey_HasValue(entityIdentity.Keys)); //Expression nullCheckExpression = Emit_EntityKey_HasValue(entityIdentity.Keys); result = BuildExpressionToGetRecordState(columnMap, entityKeyReader, entitySetReader, nullCheckExpression); } else { Expression constructEntity = null; EntityType cSpaceType = (EntityType)columnMap.Type.EdmType; Debug.Assert(cSpaceType.BuiltInTypeKind == BuiltInTypeKind.EntityType, "Type was " + cSpaceType.BuiltInTypeKind); ClrEntityType oSpaceType = (ClrEntityType)LookupObjectMapping(cSpaceType).ClrType; Type clrType = oSpaceType.ClrType; // Build expressions to read the property values from the source data // reader and bind them to their target properties List propertyBindings = CreatePropertyBindings(columnMap, clrType, cSpaceType.Properties); // We have all the property bindings now; go ahead and build the expression to // construct the entity or proxy and store the property values. We'll wrap it with more // stuff that needs to happen (or not) below. EntityProxyTypeInfo proxyTypeInfo = EntityProxyFactory.GetProxyType(oSpaceType); // If no proxy type exists for the entity, construct the regular entity object. // If a proxy type does exist, examine the ObjectContext.ContextOptions.ProxyCreationEnabled flag // to determine whether to create a regular or proxy entity object. Expression constructNonProxyEntity = Emit_ConstructEntity(oSpaceType, propertyBindings, entityKeyReader, entitySetReader, arg, null); if (proxyTypeInfo == null) { constructEntity = constructNonProxyEntity; } else { Expression constructProxyEntity = Emit_ConstructEntity(oSpaceType, propertyBindings, entityKeyReader, entitySetReader, arg, proxyTypeInfo); constructEntity = Expression.Condition(Shaper_ProxyCreationEnabled, constructProxyEntity, constructNonProxyEntity); } // If we're tracking, call HandleEntity (or HandleIEntityWithKey or // HandleEntityAppendOnly) as appropriate if (MergeOption.NoTracking != _mergeOption) { Type actualType = proxyTypeInfo == null ? clrType : proxyTypeInfo.ProxyType; if (typeof(IEntityWithKey).IsAssignableFrom(actualType) && MergeOption.AppendOnly != _mergeOption) { constructEntity = Expression.Call(Shaper_Parameter, Shaper_HandleIEntityWithKey.MakeGenericMethod(clrType), constructEntity, entitySetReader ); } else { if (MergeOption.AppendOnly == _mergeOption) { // pass through a delegate creating the entity rather than the actual entity, so we can avoid // the cost of materialization when the entity is already in the state manager //Func entityDelegate = shaper => constructEntity(shaper); LambdaExpression entityDelegate = CreateInlineDelegate(constructEntity); constructEntity = Expression.Call(Shaper_Parameter, Shaper_HandleEntityAppendOnly.MakeGenericMethod(clrType), entityDelegate, entityKeyReader, entitySetReader ); } else { constructEntity = Expression.Call(Shaper_Parameter, Shaper_HandleEntity.MakeGenericMethod(clrType), constructEntity, entityKeyReader, entitySetReader ); } } } else { constructEntity = Expression.Call(Shaper_Parameter, Shaper_HandleEntityNoTracking.MakeGenericMethod(clrType), constructEntity ); } // All the above is gated upon whether there really is an entity value; // we won't bother executing anything unless there is an entityKey value, // otherwise we'll just return a typed null. result = Expression.Condition( Emit_EntityKey_HasValue(entityIdentity.Keys), constructEntity, Emit_WrappedNullConstant(arg.RequestedType) ); } return new TranslatorResult(result, arg.RequestedType); } private Expression Emit_ConstructEntity(EntityType oSpaceType, IEnumerable propertyBindings, Expression entityKeyReader, Expression entitySetReader, TranslatorArg arg, EntityProxyTypeInfo proxyTypeInfo) { bool isProxy = proxyTypeInfo != null; Type clrType = oSpaceType.ClrType; Type actualType; Expression constructEntity; if (isProxy) { constructEntity = Expression.MemberInit(Expression.New(proxyTypeInfo.ProxyType), propertyBindings); actualType = proxyTypeInfo.ProxyType; } else { ConstructorInfo constructor = GetConstructor(clrType); constructEntity = Expression.MemberInit(Expression.New(constructor), propertyBindings); actualType = clrType; } // After calling the constructor, immediately create an IEntityWrapper instance for the entity. constructEntity = Emit_EnsureTypeAndWrap(constructEntity, entityKeyReader, entitySetReader, arg.RequestedType, clrType, actualType, _mergeOption == MergeOption.NoTracking ? MergeOption.NoTracking : MergeOption.AppendOnly, isProxy); if (isProxy) { // Since we created a proxy, we now need to give it a reference to the wrapper that we just created. constructEntity = Expression.Call(Expression.Constant(proxyTypeInfo), EntityProxyTypeInfo_SetEntityWrapper, constructEntity); if (proxyTypeInfo.InitializeEntityCollections != null) { constructEntity = Expression.Call(proxyTypeInfo.InitializeEntityCollections, constructEntity); } } return constructEntity; } /// /// Prepare a list of PropertyBindings for each item in the specified property /// collection such that the mapped property of the specified clrType has its /// value set from the source data reader. /// /// Along the way we'll keep track of non-public properties and properties that /// have link demands, so we can ensure enforce them. /// private List CreatePropertyBindings(StructuredColumnMap columnMap, Type clrType, ReadOnlyMetadataCollection properties) { List result = new List(columnMap.Properties.Length); ObjectTypeMapping mapping = LookupObjectMapping(columnMap.Type.EdmType); for (int i = 0; i < columnMap.Properties.Length; i++) { EdmProperty edmProperty = mapping.GetPropertyMap(properties[i].Name).ClrProperty; // get MethodInfo for setter MethodInfo propertyAccessor; Type propertyType; LightweightCodeGenerator.ValidateSetterProperty(edmProperty.EntityDeclaringType, edmProperty.PropertySetterHandle, out propertyAccessor, out propertyType); // determine if any security checks are required if (!LightweightCodeGenerator.IsPublic(propertyAccessor)) { _hasNonPublicMembers = true; } // get translation of property value Expression valueReader = columnMap.Properties[i].Accept(this, new TranslatorArg(propertyType)).Expression; ScalarColumnMap scalarColumnMap = columnMap.Properties[i] as ScalarColumnMap; if (null != scalarColumnMap) { string propertyName = propertyAccessor.Name.Substring(4); // substring to strip "set_" // create a value reader with error handling Expression valueReaderWithErrorHandling = Emit_Shaper_GetPropertyValueWithErrorHandling(propertyType, scalarColumnMap.ColumnPos, propertyName, propertyAccessor.DeclaringType.Name, scalarColumnMap.Type); _currentCoordinatorScratchpad.AddExpressionWithErrorHandling(valueReader, valueReaderWithErrorHandling); } Type entityDeclaringType = Type.GetTypeFromHandle(edmProperty.EntityDeclaringType); MemberBinding binding = Expression.Bind(GetProperty(propertyAccessor, entityDeclaringType), valueReader); result.Add(binding); } return result; } /// /// Gets the PropertyInfo representing the property with which the given setter method is associated. /// This code is taken from Expression.Bind(MethodInfo) but adapted to take a type such that it /// will work in cases in which the property was declared on a generic base class. In such cases, /// the declaringType needs to be the actual entity type, rather than the base class type. Note that /// declaringType can be null, in which case the setterMethod.DeclaringType is used. /// private static PropertyInfo GetProperty(MethodInfo setterMethod, Type declaringType) { if (declaringType == null) { declaringType = setterMethod.DeclaringType; } BindingFlags bindingAttr = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance; foreach (PropertyInfo propertyInfo in declaringType.GetProperties(bindingAttr)) { if (propertyInfo.GetSetMethod(nonPublic: true) == setterMethod) { return propertyInfo; } } Debug.Fail("Should always find a property for the setterMethod since we got the setter method from a property in the first place."); return null; } /// /// Visit(SimplePolymorphicColumnMap) /// internal override TranslatorResult Visit(SimplePolymorphicColumnMap columnMap, TranslatorArg arg) { Expression result; // We're building a conditional ladder, where we'll compare each // discriminator value with the one from the source data reader, and // we'll pick that type if they match. Expression discriminatorReader = AcceptWithMappedType(this, columnMap.TypeDiscriminator, columnMap).Expression; if (IsValueLayer) { result = Emit_EnsureType( BuildExpressionToGetRecordState(columnMap, null, null, Expression.Constant(true)), arg.RequestedType); } else { result = Emit_WrappedNullConstant(arg.RequestedType); // the default } foreach (var typeChoice in columnMap.TypeChoices) { // determine CLR type for the type choice, and don't bother adding // this choice if it can't produce a result Type type = DetermineClrType(typeChoice.Value.Type); if (type.IsAbstract) { continue; } Expression discriminatorConstant = Expression.Constant(typeChoice.Key, discriminatorReader.Type); Expression discriminatorMatches; // For string types, we have to use a specific comparison that handles // trailing spaces properly, not just the general equality test we use // elsewhere. if (discriminatorReader.Type == typeof(string)) { discriminatorMatches = Expression.Call(Expression.Constant(TrailingSpaceStringComparer.Instance), IEqualityComparerOfString_Equals, discriminatorConstant, discriminatorReader); } else { discriminatorMatches = Emit_Equal(discriminatorConstant, discriminatorReader); } result = Expression.Condition(discriminatorMatches, typeChoice.Value.Accept(this, arg).Expression, result); } return new TranslatorResult(result, arg.RequestedType); } /// /// Visit(MultipleDiscriminatorPolymorphicColumnMap) /// [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] internal override TranslatorResult Visit(MultipleDiscriminatorPolymorphicColumnMap columnMap, TranslatorArg arg) { MethodInfo multipleDiscriminatorPolymorphicColumnMapHelper = Translator_MultipleDiscriminatorPolymorphicColumnMapHelper.MakeGenericMethod(arg.RequestedType); Expression result = (Expression)multipleDiscriminatorPolymorphicColumnMapHelper.Invoke(this, new object[] { columnMap, arg }); return new TranslatorResult(result, arg.RequestedType); } /// /// Helper method to simplify the construction of the types; I'm just too lazy to /// create all the nested generic types needed to this by hand. /// private Expression MultipleDiscriminatorPolymorphicColumnMapHelper(MultipleDiscriminatorPolymorphicColumnMap columnMap, TranslatorArg arg) { // construct an array of discriminator values Expression[] discriminatorReaders = new Expression[columnMap.TypeDiscriminators.Length]; for (int i = 0; i < discriminatorReaders.Length; i++) { discriminatorReaders[i] = columnMap.TypeDiscriminators[i].Accept(this, new TranslatorArg(typeof(object))).Expression; } Expression discriminatorValues = Expression.NewArrayInit(typeof(object), discriminatorReaders); // Next build the expressions that will construct the type choices. An array of KeyValuePair> List elementDelegates = new List(); Type typeDelegatePairType = typeof(KeyValuePair>); ConstructorInfo typeDelegatePairConstructor = typeDelegatePairType.GetConstructor(new Type[] { typeof(EntityType), typeof(Func) }); foreach (var typeChoice in columnMap.TypeChoices) { Expression typeReader = Emit_EnsureType(AcceptWithMappedType(this, typeChoice.Value, columnMap).UnwrappedExpression, typeof(TElement)); LambdaExpression typeReaderDelegate = CreateInlineDelegate(typeReader); Expression typeDelegatePair = Expression.New( typeDelegatePairConstructor, Expression.Constant(typeChoice.Key), typeReaderDelegate ); elementDelegates.Add(typeDelegatePair); } // invoke shaper.Discrimate({ discriminatorValue1...discriminatorValueN }, discriminateDelegate, elementDelegates) MethodInfo shaperDiscriminateOfT = Shaper_Discriminate.MakeGenericMethod(typeof(TElement)); Expression result = Expression.Call(Shaper_Parameter, shaperDiscriminateOfT, discriminatorValues, Expression.Constant(columnMap.Discriminate), Expression.NewArrayInit(typeDelegatePairType, elementDelegates) ); return result; } /// /// Visit(RecordColumnMap) /// internal override TranslatorResult Visit(RecordColumnMap columnMap, TranslatorArg arg) { Expression result = null; Expression nullSentinelCheck = null; if (null != columnMap.NullSentinel) { nullSentinelCheck = Emit_Reader_IsDBNull(columnMap.NullSentinel); } if (IsValueLayer) { result = BuildExpressionToGetRecordState(columnMap, null, null, nullSentinelCheck); } else { Debug.Assert(columnMap.Type.EdmType.BuiltInTypeKind == BuiltInTypeKind.RowType, "RecordColumnMap without RowType?"); // we kind of depend upon this Expression nullConstant; // There are (at least) three different reasons we have a RecordColumnMap // so pick the method that handles the reason we have for this one. InitializerMetadata initializerMetadata; if (InitializerMetadata.TryGetInitializerMetadata(columnMap.Type, out initializerMetadata)) { result = HandleLinqRecord(columnMap, initializerMetadata); nullConstant = Emit_NullConstant(result.Type); } else { RowType spanRowType = (RowType)columnMap.Type.EdmType; if (null != _spanIndex && _spanIndex.HasSpanMap(spanRowType)) { result = HandleSpandexRecord(columnMap, arg, spanRowType); nullConstant = Emit_WrappedNullConstant(result.Type); } else { result = HandleRegularRecord(columnMap, arg, spanRowType); nullConstant = Emit_NullConstant(result.Type); } } // If there is a null sentinel process it accordingly. if (null != nullSentinelCheck) { // shaper.Reader.IsDBNull(nullsentinelOridinal) ? (type)null : result result = Expression.Condition(nullSentinelCheck, nullConstant, result); } } return new TranslatorResult(result, arg.RequestedType); } private Expression BuildExpressionToGetRecordState(StructuredColumnMap columnMap, Expression entityKeyReader, Expression entitySetReader, Expression nullCheckExpression) { RecordStateScratchpad recordStateScratchpad = _currentCoordinatorScratchpad.CreateRecordStateScratchpad(); int stateSlotNumber = AllocateStateSlot(); recordStateScratchpad.StateSlotNumber = stateSlotNumber; int propertyCount = columnMap.Properties.Length; int readerCount = (null != entityKeyReader) ? propertyCount + 1 : propertyCount; recordStateScratchpad.ColumnCount = propertyCount; // We can have an entity here, even though it's a RecordResultColumn, because // it may be a polymorphic type; eg: TREAT(Product AS DiscontinuedProduct); we // construct an EntityRecordInfo with a sentinel EntityNotValidKey as it's Key EntityType entityTypeMetadata = null; if (TypeHelpers.TryGetEdmType(columnMap.Type, out entityTypeMetadata)) { recordStateScratchpad.DataRecordInfo = new EntityRecordInfo(entityTypeMetadata, EntityKey.EntityNotValidKey, null); } else { TypeUsage edmType = Helper.GetModelTypeUsage(columnMap.Type); recordStateScratchpad.DataRecordInfo = new DataRecordInfo(edmType); } Expression[] propertyReaders = new Expression[readerCount]; string[] propertyNames = new string[recordStateScratchpad.ColumnCount]; TypeUsage[] typeUsages = new TypeUsage[recordStateScratchpad.ColumnCount]; for (int ordinal = 0; ordinal < propertyCount; ordinal++) { Expression propertyReader = columnMap.Properties[ordinal].Accept(this, new TranslatorArg(typeof(Object))).Expression; // recordState.SetColumnValue(i, propertyReader ?? DBNull.Value) propertyReaders[ordinal] = Expression.Call(Shaper_Parameter, Shaper_SetColumnValue, Expression.Constant(stateSlotNumber), Expression.Constant(ordinal), Expression.Coalesce(propertyReader, DBNull_Value) ); propertyNames[ordinal] = columnMap.Properties[ordinal].Name; typeUsages[ordinal] = columnMap.Properties[ordinal].Type; } if (null != entityKeyReader) { propertyReaders[readerCount - 1] = Expression.Call(Shaper_Parameter, Shaper_SetEntityRecordInfo, Expression.Constant(stateSlotNumber), entityKeyReader, entitySetReader); } recordStateScratchpad.GatherData = Emit_BitwiseOr(propertyReaders); recordStateScratchpad.PropertyNames = propertyNames; recordStateScratchpad.TypeUsages = typeUsages; // Finally, build the expression to read the recordState from the shaper state // (RecordState)shaperState.State[stateSlotNumber].GatherData(shaper) Expression result = Expression.Call(Emit_Shaper_GetState(stateSlotNumber, typeof(RecordState)), RecordState_GatherData, Shaper_Parameter); // If there's a null check, then everything above is gated upon whether // it's value is DBNull.Value. if (null != nullCheckExpression) { Expression nullResult = Expression.Call(Emit_Shaper_GetState(stateSlotNumber, typeof(RecordState)), RecordState_SetNullRecord, Shaper_Parameter); // nullCheckExpression ? (type)null : result result = Expression.Condition(nullCheckExpression, nullResult, result); } return result; } /// /// Build expression to materialize LINQ initialization types (anonymous /// types, IGrouping, EntityCollection) /// private Expression HandleLinqRecord(RecordColumnMap columnMap, InitializerMetadata initializerMetadata) { List propertyReaders = new List(columnMap.Properties.Length); foreach (var pair in columnMap.Properties.Zip(initializerMetadata.GetChildTypes())) { ColumnMap propertyColumnMap = pair.Key; Type type = pair.Value; // Note that we're not just blindly using the type from the column map // because we need to match the type thatthe initializer says it needs; // that's why were not using AcceptWithMappedType; if (null == type) { type = DetermineClrType(propertyColumnMap.Type); } TranslatorResult propertyReader = propertyColumnMap.Accept(this, new TranslatorArg(type)); propertyReaders.Add(propertyReader); } Expression result = initializerMetadata.Emit(this, propertyReaders); return result; } /// /// Build expression to materialize a data record. /// private Expression HandleRegularRecord(RecordColumnMap columnMap, TranslatorArg arg, RowType spanRowType) { // handle regular records // Build an array of expressions that read the individual values from the // source data reader. Expression[] columnReaders = new Expression[columnMap.Properties.Length]; for (int i = 0; i < columnReaders.Length; i++) { Expression columnReader = AcceptWithMappedType(this, columnMap.Properties[i], columnMap).UnwrappedExpression; // ((object)columnReader) ?? DBNull.Value columnReaders[i] = Expression.Coalesce(Emit_EnsureType(columnReader, typeof(object)), DBNull_Value); } // new object[] {columnReader0..columnReaderN} Expression columnReaderArray = Expression.NewArrayInit(typeof(object), columnReaders); // Get an expression representing the TypeUsage of the MaterializedDataRecord // we're about to construct; we need to remove the span information from it, // though, since we don't want to surface that... TypeUsage type = columnMap.Type; if (null != _spanIndex) { type = _spanIndex.GetSpannedRowType(spanRowType) ?? type; } Expression typeUsage = Expression.Constant(type, typeof(TypeUsage)); // new MaterializedDataRecord(Shaper.Workspace, typeUsage, values) Expression result = Emit_EnsureType(Expression.New(MaterializedDataRecord_ctor, Shaper_Workspace, typeUsage, columnReaderArray), arg.RequestedType); return result; } /// /// Build expression to materialize the spanned information /// private Expression HandleSpandexRecord(RecordColumnMap columnMap, TranslatorArg arg, RowType spanRowType) { Dictionary spanMap = _spanIndex.GetSpanMap(spanRowType); // First, build the expression to materialize the root item. Expression result = columnMap.Properties[0].Accept(this, arg).Expression; // Now build expressions that call into the appropriate shaper method // for the type of span for each spanned item. for (int i = 1; i < columnMap.Properties.Length; i++) { AssociationEndMember targetMember = spanMap[i]; TranslatorResult propertyTranslatorResult = AcceptWithMappedType(this, columnMap.Properties[i], columnMap); Expression spannedResultReader = propertyTranslatorResult.Expression; // figure out the flavor of the span CollectionTranslatorResult collectionTranslatorResult = propertyTranslatorResult as CollectionTranslatorResult; if (null != collectionTranslatorResult) { Expression expressionToGetCoordinator = collectionTranslatorResult.ExpressionToGetCoordinator; // full span collection Type elementType = spannedResultReader.Type.GetGenericArguments()[0]; MethodInfo handleFullSpanCollectionMethod = Shaper_HandleFullSpanCollection.MakeGenericMethod(arg.RequestedType, elementType); result = Expression.Call(Shaper_Parameter, handleFullSpanCollectionMethod, result, expressionToGetCoordinator, Expression.Constant(targetMember)); } else { if (typeof(EntityKey) == spannedResultReader.Type) { // relationship span MethodInfo handleRelationshipSpanMethod = Shaper_HandleRelationshipSpan.MakeGenericMethod(arg.RequestedType); result = Expression.Call(Shaper_Parameter, handleRelationshipSpanMethod, result, spannedResultReader, Expression.Constant(targetMember)); } else { // full span element MethodInfo handleFullSpanElementMethod = Shaper_HandleFullSpanElement.MakeGenericMethod(arg.RequestedType, spannedResultReader.Type); result = Expression.Call(Shaper_Parameter, handleFullSpanElementMethod, result, spannedResultReader, Expression.Constant(targetMember)); } } } return result; } #endregion #region collection columns /// /// Visit(SimpleCollectionColumnMap) /// internal override TranslatorResult Visit(SimpleCollectionColumnMap columnMap, TranslatorArg arg) { return ProcessCollectionColumnMap(columnMap, arg); } /// /// Visit(DiscriminatedCollectionColumnMap) /// internal override TranslatorResult Visit(DiscriminatedCollectionColumnMap columnMap, TranslatorArg arg) { return ProcessCollectionColumnMap(columnMap, arg, columnMap.Discriminator, columnMap.DiscriminatorValue); } /// /// Common code for both Simple and Discrminated Column Maps. /// private TranslatorResult ProcessCollectionColumnMap(CollectionColumnMap columnMap, TranslatorArg arg) { return ProcessCollectionColumnMap(columnMap, arg, null, null); } /// /// Common code for both Simple and Discrminated Column Maps. /// private TranslatorResult ProcessCollectionColumnMap(CollectionColumnMap columnMap, TranslatorArg arg, ColumnMap discriminatorColumnMap, object discriminatorValue) { Type elementType = DetermineElementType(arg.RequestedType, columnMap); // CoordinatorScratchpad aggregates information about the current nested // result (represented by the given CollectionColumnMap) CoordinatorScratchpad coordinatorScratchpad = new CoordinatorScratchpad(elementType); // enter scope for current coordinator when translating children, etc. EnterCoordinatorTranslateScope(coordinatorScratchpad); ColumnMap elementColumnMap = columnMap.Element; if (IsValueLayer) { StructuredColumnMap structuredElement = elementColumnMap as StructuredColumnMap; // If we have a collection of non-structured types we have to put // a structure around it, because we don't have data readers of // scalars, only structures. We don't need a null sentinel because // this structure can't ever be null. if (null == structuredElement) { ColumnMap[] columnMaps = new ColumnMap[1] { columnMap.Element }; elementColumnMap = new RecordColumnMap(columnMap.Element.Type, columnMap.Element.Name, columnMaps, null); } } // Build the expression that will construct the element of the collection // from the source data reader. // We use UnconvertedExpression here so we can defer doing type checking in case // we need to translate to a POCO collection later in the process. Expression elementReader = elementColumnMap.Accept(this, new TranslatorArg(elementType)).UnconvertedExpression; // Build the expression(s) that read the collection's keys from the source // data reader; note that the top level collection may not have keys if there // are no children. Expression[] keyReaders; if (null != columnMap.Keys) { keyReaders = new Expression[columnMap.Keys.Length]; for (int i = 0; i < keyReaders.Length; i++) { Expression keyReader = AcceptWithMappedType(this, columnMap.Keys[i], columnMap).Expression; keyReaders[i] = keyReader; } } else { keyReaders = new Expression[] { }; } // Build the expression that reads the discriminator value from the source // data reader. Expression discriminatorReader = null; if (null != discriminatorColumnMap) { discriminatorReader = AcceptWithMappedType(this, discriminatorColumnMap, columnMap).Expression; } // get expression retrieving the coordinator Expression expressionToGetCoordinator = BuildExpressionToGetCoordinator(elementType, elementReader, keyReaders, discriminatorReader, discriminatorValue, coordinatorScratchpad); MethodInfo getElementsExpression = typeof(Coordinator<>).MakeGenericType(elementType).GetMethod("GetElements", BindingFlags.NonPublic | BindingFlags.Instance); Expression result; if (IsValueLayer) { result = expressionToGetCoordinator; } else { // coordinator.GetElements() result = Expression.Call(expressionToGetCoordinator, getElementsExpression); // Perform the type check that was previously deferred so we could process POCO collections. coordinatorScratchpad.Element = Emit_EnsureType(coordinatorScratchpad.Element, elementType); // When materializing specifically requested collection types, we need // to transfer the results from the Enumerable to the requested collection. Type innerElementType; if (EntityUtil.TryGetICollectionElementType(arg.RequestedType, out innerElementType)) { // Given we have some type that implements ICollection, we need to decide what concrete // collection type to instantiate--See EntityUtil.DetermineCollectionType for details. var typeToInstantiate = EntityUtil.DetermineCollectionType(arg.RequestedType); if (typeToInstantiate == null) { throw EntityUtil.InvalidOperation(Strings.ObjectQuery_UnableToMaterializeArbitaryProjectionType(arg.RequestedType)); } Type listOfElementType = typeof(List<>).MakeGenericType(innerElementType); if (typeToInstantiate != listOfElementType) { coordinatorScratchpad.InitializeCollection = Emit_EnsureType( Expression.New(GetConstructor(typeToInstantiate)), typeof(ICollection<>).MakeGenericType(innerElementType)); } result = Emit_EnsureType(result, arg.RequestedType); } else { // If any compensation is required (returning IOrderedEnumerable, not // just vanilla IEnumerable we must wrap the result with a static class // that is of the type expected. if (!arg.RequestedType.IsAssignableFrom(result.Type)) { // new CompensatingCollection(_collectionReader) Type compensatingCollectionType = typeof(CompensatingCollection<>).MakeGenericType(elementType); ConstructorInfo constructorInfo = compensatingCollectionType.GetConstructors()[0]; result = Emit_EnsureType(Expression.New(constructorInfo, result), compensatingCollectionType); } } } ExitCoordinatorTranslateScope(); return new CollectionTranslatorResult(result, columnMap, arg.RequestedType, expressionToGetCoordinator); } /// /// Returns the CLR Type of the element of the collection /// private Type DetermineElementType(Type collectionType, CollectionColumnMap columnMap) { Type result = null; if (IsValueLayer) { result = typeof(RecordState); } else { result = TypeSystem.GetElementType(collectionType); // GetElementType returns the input type if it is not a collection. if (result == collectionType) { // if the user isn't asking for a CLR collection type (e.g. ObjectQuery("{{1, 2}}")), we choose for them TypeUsage edmElementType = ((CollectionType)columnMap.Type.EdmType).TypeUsage; // the TypeUsage of the Element of the collection. result = DetermineClrType(edmElementType); } } return result; } /// /// Build up the coordinator graph using Enter/ExitCoordinatorTranslateScope. /// private void EnterCoordinatorTranslateScope(CoordinatorScratchpad coordinatorScratchpad) { if (null == _rootCoordinatorScratchpad) { coordinatorScratchpad.Depth = 0; _rootCoordinatorScratchpad = coordinatorScratchpad; _currentCoordinatorScratchpad = coordinatorScratchpad; } else { coordinatorScratchpad.Depth = _currentCoordinatorScratchpad.Depth + 1; _currentCoordinatorScratchpad.AddNestedCoordinator(coordinatorScratchpad); _currentCoordinatorScratchpad = coordinatorScratchpad; } } private void ExitCoordinatorTranslateScope() { _currentCoordinatorScratchpad = _currentCoordinatorScratchpad.Parent; } /// /// Return an expression to read the coordinator from a state slot at /// runtime. This is the method where we store the expressions we've /// been building into the CoordinatorScratchpad, which we'll compile /// later, once we've left the visitor. /// private Expression BuildExpressionToGetCoordinator(Type elementType, Expression element, Expression[] keyReaders, Expression discriminator, object discriminatorValue, CoordinatorScratchpad coordinatorScratchpad) { int stateSlotNumber = AllocateStateSlot(); coordinatorScratchpad.StateSlotNumber = stateSlotNumber; // Ensure that the element type of the collec element translator coordinatorScratchpad.Element = element; // Build expressions to set the key values into their state slots, and // to compare the current values from the source reader with the values // in the slots. List setKeyTerms = new List(keyReaders.Length); List checkKeyTerms = new List(keyReaders.Length); foreach (Expression keyReader in keyReaders) { // allocate space for the key value in the reader state int keyStateSlotNumber = AllocateStateSlot(); // SetKey: readerState.SetState(stateSlot, keyReader) setKeyTerms.Add(Emit_Shaper_SetState(keyStateSlotNumber, keyReader)); // CheckKey: ((T)readerState.State[ordinal]).Equals(keyValue) checkKeyTerms.Add(Emit_Equal( Emit_Shaper_GetState(keyStateSlotNumber, keyReader.Type), keyReader ) ); } // For setting keys, we use BitwiseOr so that we don't short-circuit (all // key terms are set) coordinatorScratchpad.SetKeys = Emit_BitwiseOr(setKeyTerms); // When checking for equality, we use AndAlso so that we short-circuit (return // as soon as key values don't match) coordinatorScratchpad.CheckKeys = Emit_AndAlso(checkKeyTerms); if (null != discriminator) { // discriminatorValue == discriminator coordinatorScratchpad.HasData = Emit_Equal( Expression.Constant(discriminatorValue, discriminator.Type), discriminator ); } // Finally, build the expression to read the coordinator from the state // (Coordinator)readerState.State[stateOrdinal] Expression result = Emit_Shaper_GetState(stateSlotNumber, typeof(Coordinator<>).MakeGenericType(elementType)); return result; } #endregion #region "scalar" columns /// /// Visit(RefColumnMap) /// /// If the entityKey has a value, then return it otherwise return a null /// valued EntityKey. The EntityKey construction is the tricky part. /// internal override TranslatorResult Visit(RefColumnMap columnMap, TranslatorArg arg) { EntityIdentity entityIdentity = columnMap.EntityIdentity; Expression entitySetReader; // Ignored here; used when constructing Entities // hasValue ? entityKey : (EntityKey)null Expression result = Expression.Condition( Emit_EntityKey_HasValue(entityIdentity.Keys), Emit_EntityKey_ctor(this, entityIdentity, true, out entitySetReader), Expression.Constant(null, typeof(EntityKey)) ); return new TranslatorResult(result, arg.RequestedType); } /// /// Visit(ScalarColumnMap) /// /// Pretty basic stuff here; we just call the method that matches the /// type of the column. Of course we have to handle nullable/non-nullable /// types, and non-value types. /// internal override TranslatorResult Visit(ScalarColumnMap columnMap, TranslatorArg arg) { Type type = arg.RequestedType; TypeUsage columnType = columnMap.Type; int ordinal = columnMap.ColumnPos; Expression result; // 1. Create an expression to access the column value as an instance of the correct type. For non-spatial types this requires a call to one of the // DbDataReader GetXXX methods; spatial values must be read using the provider's spatial services implementation. // 2. If the type was nullable (strings, byte[], Nullable), wrap the expression with a check for the DBNull value and produce the correct typed null instead. // Since the base spatial types (DbGeography/DbGeometry) are reference types, this is always required for spatial columns. // 3. Also create a version of the expression with error handling so that we can throw better exception messages when needed // PrimitiveTypeKind typeKind; if (Helper.IsSpatialType(columnType, out typeKind)) { Debug.Assert(Helper.IsGeographicType((PrimitiveType)columnType.EdmType) || Helper.IsGeometricType((PrimitiveType)columnType.EdmType), "Spatial primitive type is neither Geometry or Geography?"); result = Emit_Conditional_NotDBNull(Helper.IsGeographicType((PrimitiveType)columnType.EdmType) ? Emit_EnsureType(Emit_Shaper_GetGeographyColumnValue(ordinal), type) : Emit_EnsureType(Emit_Shaper_GetGeometryColumnValue(ordinal), type), ordinal, type); } else { bool needsNullableCheck; MethodInfo readerMethod = GetReaderMethod(type, out needsNullableCheck); result = Expression.Call(Shaper_Reader, readerMethod, Expression.Constant(ordinal)); // if the requested type is a nullable enum we need to cast it first to the non-nullable enum type to avoid InvalidCastException. // Note that we guard against null values by wrapping the expression with DbNullCheck later. Also we don't actually // look at the type of the value returned by reader. If the value is not castable to enum we will fail with cast exception. Type nonNullableType = TypeSystem.GetNonNullableType(type); if (nonNullableType.IsEnum && nonNullableType != type) { Debug.Assert(needsNullableCheck, "This is a nullable enum so needsNullableCheck should be true to emit code that handles null values read from the reader."); result = Expression.Convert(result, nonNullableType); } else if(type == typeof(object)) { Debug.Assert(!needsNullableCheck, "If the requested type is object there is no special handling for null values returned from the reader."); // special case for an OSpace query where the requested type is object but the column type is of an enum type. In this case // we want to return a boxed value of enum type instead a boxed value of the enum underlying type. We also need to handle null // values to return DBNull to be consistent with behavior for primitive types (e.g. int) if (!IsValueLayer && TypeSemantics.IsEnumerationType(columnType)) { result = Expression.Condition( Emit_Reader_IsDBNull(ordinal), result, Expression.Convert(Expression.Convert(result, TypeSystem.GetNonNullableType(DetermineClrType(columnType.EdmType))), typeof(object))); } } // (type)shaper.Reader.Get???(ordinal) result = Emit_EnsureType(result, type); if (needsNullableCheck) { result = Emit_Conditional_NotDBNull(result, ordinal, type); } } Expression resultWithErrorHandling = Emit_Shaper_GetColumnValueWithErrorHandling(arg.RequestedType, ordinal, columnType); _currentCoordinatorScratchpad.AddExpressionWithErrorHandling(result, resultWithErrorHandling); return new TranslatorResult(result, type); } private static Expression Emit_Conditional_NotDBNull(Expression result, int ordinal, Type columnType) { result = Expression.Condition(Emit_Reader_IsDBNull(ordinal), Expression.Constant(TypeSystem.GetDefaultValue(columnType), columnType), result); return result; } internal static MethodInfo GetReaderMethod(Type type, out bool isNullable) { Debug.Assert(null != type, "type required"); MethodInfo result; isNullable = false; // determine if this is a Nullable Type underlyingType = Nullable.GetUnderlyingType(type); if (null != underlyingType) { isNullable = true; type = underlyingType; } TypeCode typeCode = Type.GetTypeCode(type); switch (typeCode) { case TypeCode.String: result = DbDataReader_GetString; isNullable = true; break; case TypeCode.Int16: result = DbDataReader_GetInt16; break; case TypeCode.Int32: result = DbDataReader_GetInt32; break; case TypeCode.Int64: result = DbDataReader_GetInt64; break; case TypeCode.Boolean: result = DbDataReader_GetBoolean; break; case TypeCode.Decimal: result = DbDataReader_GetDecimal; break; case TypeCode.Double: result = DbDataReader_GetDouble; break; case TypeCode.Single: result = DbDataReader_GetFloat; break; case TypeCode.DateTime: result = DbDataReader_GetDateTime; break; case TypeCode.Byte: result = DbDataReader_GetByte; break; default: if (typeof(Guid) == type) { // Guid doesn't have a type code result = DbDataReader_GetGuid; } else if (typeof(TimeSpan) == type || typeof(DateTimeOffset) == type) { // TimeSpan and DateTimeOffset don't have a type code or a specific // GetXXX method result = DbDataReader_GetValue; } else if (typeof(Object) == type) { // We assume that Object means we want DBNull rather than null. I believe this is a bug. result = DbDataReader_GetValue; } else { result = DbDataReader_GetValue; isNullable = true; } break; } return result; } /// /// Visit(VarRefColumnMap) /// /// This should throw; VarRefColumnMaps should be removed by the PlanCompiler. /// internal override TranslatorResult Visit(VarRefColumnMap columnMap, TranslatorArg arg) { Debug.Fail("VarRefColumnMap should be substituted at this point"); throw EntityUtil.InvalidOperation(String.Empty); } #endregion #endregion } }