//------------------------------------------------------------------------------
//
// 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;
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;
}
}
}
///
/// 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 has a security LinkDemand attribute. Used to determine which security
/// checks are necessary when invoking the delegate.
///
private bool _hasLinkDemand;
///
/// 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?
// Before compiling delegates (which asserts the permissions needed to emit
// references to non public assembly types) check that user delegates can be
// compiled in non-elevated context.
translator.VerifyUserExpressions();
// 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 & ReflectionEmit so we can generate
/// the method. This means that that security checks are skipped. Before calling this
/// method you must ensure that you've done a TestComple to ensure the compilation
/// doesn't violate them.
///
[SecurityCritical]
[SecurityTreatAsSafe]
[ReflectionPermission(SecurityAction.Assert, MemberAccess = true, ReflectionEmit = 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)
///
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)
{
_userExpressions.Add(expression);
}
#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;
if (_hasLinkDemand)
{
checkPermissions = DemandFullTrust;
}
else if (_hasNonPublicMembers)
{
checkPermissions = DemandMemberAccess;
}
else
{
checkPermissions = null;
}
return checkPermissions;
}
private static void DemandFullTrust()
{
LightweightCodeGenerator.FullTrustPermission.Demand();
}
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).
///
private void VerifyUserExpressions()
{
foreach (Expression expression in _userExpressions)
{
MethodInfo testCompileMethod = Translator_TestCompile.MakeGenericMethod(expression.Type);
testCompileMethod.Invoke(null, new object[] { expression });
}
}
///
/// Compile a user expression to ensure that security will be
/// maintained when we build the delegates that call it.
///
private static void TestCompile(Expression expression)
{
Debug.Assert(expression.Type == typeof(T), "incorrect lambda expresion body type)");
Expression.Lambda>(expression).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:
throw EntityUtil.UnexpectedMetadataType(edmType);
}
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.HasLinkDemand(result))
{
_hasLinkDemand = true;
}
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>
///
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_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_HandleIEntityWithRelationships = typeof(Shaper).GetMethod("HandleIEntityWithRelationships");
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_TestCompile = typeof(Translator).GetMethod("TestCompile", BindingFlags.NonPublic | BindingFlags.Static);
private static readonly MethodInfo Translator_TypedCreateInlineDelegate = typeof(Translator).GetMethod("TypedCreateInlineDelegate", BindingFlags.NonPublic | BindingFlags.Instance);
#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 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"));
#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;
}
///
/// Create expression that guarantees the input expression is of the specified
/// type; no Convert is added if the expression is alread 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)
{
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;
}
///
/// 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)
{
throw EntityUtil.ValueInvalidCast(value.GetType(), 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)
{
// 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 specified Get method(ie: GetInt32, GetString, et al)
/// of the shaper's source data reader
///
private static Expression Emit_Reader_GetXXX(int ordinal, Type type, MethodInfo dataReaderMethod)
{
// (type)shaper.Reader.Get???(ordinal)
Expression result = Emit_EnsureType(Expression.Call(Shaper_Reader, dataReaderMethod, Expression.Constant(ordinal)), type);
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)
{
// shaper.GetColumnValueWithErrorHandling(ordinal, propertyName, typeName)
Expression 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 columnType, int ordinal)
{
// shaper.GetColumnValueWithErrorHandling(ordinal, propertyName, typeName)
Expression result = Expression.Call(Shaper_Parameter, Shaper_GetColumnValueWithErrorHandling.MakeGenericMethod(columnType), 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 and the
// clrType doesn't derive from IEntityWithRelationships
//
// 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 entityType = (EntityType)columnMap.Type.EdmType;
Type clrType = DetermineClrType(entityType);
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, entityType.Properties);
bool isIEntityWithKey = typeof(System.Data.Objects.DataClasses.IEntityWithKey).IsAssignableFrom(clrType);
bool isIEntityWithRelationships = typeof(System.Data.Objects.DataClasses.IEntityWithRelationships).IsAssignableFrom(clrType);
// We only need to add a propertyBinding for the EntityKey when the clrType
// derives from IEntityWithKey
if (isIEntityWithKey)
{
// set key value as we construct the entity
MemberBinding entityKeyMemberBinding = Expression.Bind(IEntityKeyWithKey_EntityKey, entityKeyReader);
propertyBindings.Add(entityKeyMemberBinding);
}
// We have all the property bindings now; go ahead and build the expression to
// construct the entity and store the property values. We'll wrap it with more
// stuff that needs to happen (or not) below.
constructEntity = Expression.MemberInit(Expression.New(constructor), propertyBindings);
// Setup the RelationshipManager if needed
if (isIEntityWithRelationships)
{
constructEntity = Expression.Call(Shaper_Parameter, Shaper_HandleIEntityWithRelationships.MakeGenericMethod(clrType),
constructEntity,
entitySetReader
);
}
// If we're tracking, call HandleEntity (or HandleIEntityWithKey or
// HandleEntityAppendOnly) as appropriate
if (MergeOption.NoTracking != _mergeOption)
{
if (isIEntityWithKey && 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
);
}
}
}
// 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_NullConstant(clrType)
);
}
return new TranslatorResult(result, arg.RequestedType);
}
///
/// 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.PropertySetterHandle, out propertyAccessor, out propertyType);
// determine if any security checks are required
if (LightweightCodeGenerator.HasLinkDemand(propertyAccessor))
{
_hasLinkDemand = true;
}
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);
_currentCoordinatorScratchpad.AddExpressionWithErrorHandling(valueReader, valueReaderWithErrorHandling);
}
MemberBinding binding = Expression.Bind(propertyAccessor, valueReader);
result.Add(binding);
}
return result;
}
///
/// 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_NullConstant(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)
///
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).Expression, 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
// 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);
}
else
{
RowType spanRowType = (RowType)columnMap.Type.EdmType;
if (null != _spanIndex && _spanIndex.HasSpanMap(spanRowType))
{
result = HandleSpandexRecord(columnMap, arg, spanRowType);
}
else
{
result = HandleRegularRecord(columnMap, arg, spanRowType);
}
}
// If there is a null sentinel process it accordingly.
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);
}
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).Expression;
// ((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, Emit_EnsureType(result, arg.RequestedType), 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, Emit_EnsureType(result, arg.RequestedType), spannedResultReader, Expression.Constant(targetMember));
}
else
{
// full span element
MethodInfo handleFullSpanElementMethod = Shaper_HandleFullSpanElement.MakeGenericMethod(arg.RequestedType, spannedResultReader.Type);
result = Expression.Call(Shaper_Parameter, handleFullSpanElementMethod, Emit_EnsureType(result, arg.RequestedType), 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.
Expression elementReader = elementColumnMap.Accept(this, new TranslatorArg(elementType)).Expression;
// 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);
// 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))
{
// verify that the type is supported
if (typeof(ObjectQuery).IsAssignableFrom(arg.RequestedType))
{
throw EntityUtil.NotSupported(Strings.ELinq_UnsupportedObjectQueryMaterialization);
}
if (!arg.RequestedType.IsAssignableFrom(typeof(IOrderedQueryable<>).MakeGenericType(elementType)) &&
!arg.RequestedType.IsAssignableFrom(typeof(IOrderedEnumerable<>).MakeGenericType(elementType)))
{
throw EntityUtil.NotSupported(Strings.Materializer_UnsupportedCollectionType(arg.RequestedType));
}
// new CompensatingCollection(_collectionReader)
Type compensatingCollectionType = typeof(CompensatingCollection<>).MakeGenericType(elementType);
ConstructorInfo constructorInfo = compensatingCollectionType.GetConstructors()[0];
result = Emit_EnsureType(Expression.New(constructorInfo, result), compensatingCollectionType);
}
}
ExitCoodinatorTranslateScope();
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