Imported Upstream version 4.0.0~alpha1

Former-commit-id: 806294f5ded97629b74c85c09952f2a74fe182d9
This commit is contained in:
Jo Shields
2015-04-07 09:35:12 +01:00
parent 283343f570
commit 3c1f479b9d
22469 changed files with 2931443 additions and 869343 deletions

View File

@@ -0,0 +1,236 @@
//---------------------------------------------------------------------
// <copyright file="BaseEntityWrapper.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Collections;
using System.Data.Objects.DataClasses;
using System.Diagnostics;
using System.Reflection;
using System.Data.Metadata.Edm;
namespace System.Data.Objects.Internal
{
/// <summary>
/// Base class containing common code for different implementations of the IEntityWrapper
/// interface. Generally speaking, operations involving the ObjectContext, RelationshipManager
/// and raw Entity are handled through this class.
/// </summary>
/// <typeparam name="TEntity">The type of entity wrapped</typeparam>
internal abstract class BaseEntityWrapper<TEntity> : IEntityWrapper
{
// This enum allows boolean flags to be added to the wrapper without introducing a new field
// for each one. This helps keep the wrapper memory footprint small, which is important
// in some high-performance NoTracking cases.
[Flags]
private enum WrapperFlags
{
None = 0,
NoTracking = 1,
InitializingRelatedEnds = 2,
}
private readonly RelationshipManager _relationshipManager;
private Type _identityType;
private WrapperFlags _flags;
/// <summary>
/// Constructs a wrapper for the given entity and its associated RelationshipManager.
/// </summary>
/// <param name="entity">The entity to be wrapped</param>
/// <param name="relationshipManager">the RelationshipManager associated with this entity</param>
protected BaseEntityWrapper(TEntity entity, RelationshipManager relationshipManager)
{
Debug.Assert(!(entity is IEntityWrapper), "Object is an IEntityWrapper instance instead of the raw entity.");
Debug.Assert(entity != null, "Factory should ensure wrapped entity here is never null.");
if (relationshipManager == null)
{
throw EntityUtil.UnexpectedNullRelationshipManager();
}
_relationshipManager = relationshipManager;
}
/// <summary>
/// Constructs a wrapper as part of the materialization process. This constructor is only used
/// during materialization where it is known that the entity being wrapped is newly constructed.
/// This means that some checks are not performed that might be needed when thw wrapper is
/// created at other times, and information such as the identity type is passed in because
/// it is readily available in the materializer.
/// </summary>
/// <param name="entity">The entity to wrap</param>
/// <param name="relationshipManager">The RelationshipManager associated with this entity</param>
/// <param name="entitySet">The entity set, or null if none is known</param>
/// <param name="context">The context to which the entity should be attached</param>
/// <param name="mergeOption">NoTracking for non-tracked entities, AppendOnly otherwise</param>
/// <param name="identityType">The type of the entity ignoring any possible proxy type</param>
protected BaseEntityWrapper(TEntity entity, RelationshipManager relationshipManager, EntitySet entitySet, ObjectContext context, MergeOption mergeOption, Type identityType)
{
Debug.Assert(!(entity is IEntityWrapper), "Object is an IEntityWrapper instance instead of the raw entity.");
Debug.Assert(entity != null, "Factory should ensure wrapped entity here is never null.");
if (relationshipManager == null)
{
throw EntityUtil.UnexpectedNullRelationshipManager();
}
_identityType = identityType;
_relationshipManager = relationshipManager;
RelationshipManager.SetWrappedOwner(this, entity);
if (entitySet != null)
{
Context = context;
MergeOption = mergeOption;
RelationshipManager.AttachContextToRelatedEnds(context, entitySet, mergeOption);
}
}
// See IEntityWrapper documentation
public RelationshipManager RelationshipManager
{
get
{
return _relationshipManager;
}
}
// See IEntityWrapper documentation
public ObjectContext Context
{
get;
set;
}
// See IEntityWrapper documentation
public MergeOption MergeOption
{
get
{
return (_flags & WrapperFlags.NoTracking) != 0 ? MergeOption.NoTracking : MergeOption.AppendOnly;
}
private set
{
Debug.Assert(value == MergeOption.AppendOnly || value == MergeOption.NoTracking, "Merge option must be one of NoTracking or AppendOnly.");
if (value == MergeOption.NoTracking)
{
_flags |= WrapperFlags.NoTracking;
}
else
{
_flags &= ~WrapperFlags.NoTracking;
}
}
}
// See IEntityWrapper documentation
public bool InitializingProxyRelatedEnds
{
get
{
return (_flags & WrapperFlags.InitializingRelatedEnds) != 0;
}
set
{
if (value)
{
_flags |= WrapperFlags.InitializingRelatedEnds;
}
else
{
_flags &= ~WrapperFlags.InitializingRelatedEnds;
}
}
}
// See IEntityWrapper documentation
public void AttachContext(ObjectContext context, EntitySet entitySet, MergeOption mergeOption)
{
Debug.Assert(null != context, "context");
Context = context;
MergeOption = mergeOption;
if (entitySet != null)
{
RelationshipManager.AttachContextToRelatedEnds(context, entitySet, mergeOption);
}
}
// See IEntityWrapper documentation
public void ResetContext(ObjectContext context, EntitySet entitySet, MergeOption mergeOption)
{
Debug.Assert(null != entitySet, "entitySet should not be null");
Debug.Assert(null != context, "context");
Debug.Assert(MergeOption.NoTracking == mergeOption ||
MergeOption.AppendOnly == mergeOption,
"mergeOption");
if (!object.ReferenceEquals(Context, context))
{
Context = context;
MergeOption = mergeOption;
RelationshipManager.ResetContextOnRelatedEnds(context, entitySet, mergeOption);
}
}
// See IEntityWrapper documentation
public void DetachContext()
{
if (Context != null &&
Context.ObjectStateManager.TransactionManager.IsAttachTracking &&
Context.ObjectStateManager.TransactionManager.OriginalMergeOption == MergeOption.NoTracking)
{
// If AttachTo() failed while attaching graph retrieved with NoTracking option,
// we don't want to clear the Context property of the wrapped entity
MergeOption = MergeOption.NoTracking;
}
else
{
Context = null;
}
RelationshipManager.DetachContextFromRelatedEnds();
}
// See IEntityWrapper documentation
public EntityEntry ObjectStateEntry
{
get;
set;
}
// See IEntityWrapper documentation
public Type IdentityType
{
get
{
if (_identityType == null)
{
_identityType = EntityUtil.GetEntityIdentityType(typeof(TEntity));
}
return _identityType;
}
}
// All these methods defined by IEntityWrapper
public abstract void EnsureCollectionNotNull(RelatedEnd relatedEnd);
public abstract EntityKey EntityKey { get; set; }
public abstract bool OwnsRelationshipManager
{
get;
}
public abstract EntityKey GetEntityKeyFromEntity();
public abstract void SetChangeTracker(IEntityChangeTracker changeTracker);
public abstract void TakeSnapshot(EntityEntry entry);
public abstract void TakeSnapshotOfRelationships(EntityEntry entry);
public abstract object GetNavigationPropertyValue(RelatedEnd relatedEnd);
public abstract void SetNavigationPropertyValue(RelatedEnd relatedEnd, object value);
public abstract void RemoveNavigationPropertyValue(RelatedEnd relatedEnd, object value);
public abstract void CollectionAdd(RelatedEnd relatedEnd, object value);
public abstract bool CollectionRemove(RelatedEnd relatedEnd, object value);
public abstract object Entity { get; }
public abstract TEntity TypedEntity { get; }
public abstract void SetCurrentValue(EntityEntry entry, StateManagerMemberMetadata member, int ordinal, object target, object value);
public abstract void UpdateCurrentValueRecord(object value, EntityEntry entry);
public abstract bool RequiresRelationshipChangeTracking { get; }
}
}

View File

@@ -0,0 +1,180 @@
//---------------------------------------------------------------------
// <copyright file="ComplexTypeMaterializer.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Data.Metadata.Edm;
using System.Data.Common;
using System.Diagnostics;
using System.Data.Mapping;
namespace System.Data.Objects.Internal
{
/// <summary>
/// Supports materialization of complex type instances from records. Used
/// by the ObjectStateManager.
/// </summary>
internal class ComplexTypeMaterializer
{
private readonly MetadataWorkspace _workspace;
private const int MaxPlanCount = 4;
private Plan[] _lastPlans;
private int _lastPlanIndex;
internal ComplexTypeMaterializer(MetadataWorkspace workspace)
{
_workspace = workspace;
}
internal object CreateComplex(IExtendedDataRecord record, DataRecordInfo recordInfo, object result)
{
Debug.Assert(null != record, "null IExtendedDataRecord");
Debug.Assert(null != recordInfo, "null DataRecordInfo");
Debug.Assert(null != recordInfo.RecordType, "null TypeUsage");
Debug.Assert(null != recordInfo.RecordType.EdmType, "null EdmType");
Debug.Assert(Helper.IsEntityType(recordInfo.RecordType.EdmType) ||
Helper.IsComplexType(recordInfo.RecordType.EdmType),
"not EntityType or ComplexType");
Plan plan = GetPlan(record, recordInfo);
if (null == result)
{
result = ((Func<object>)plan.ClrType)();
}
SetProperties(record, result, plan.Properties);
return result;
}
private void SetProperties(IExtendedDataRecord record, object result, PlanEdmProperty[] properties)
{
Debug.Assert(null != record, "null IExtendedDataRecord");
Debug.Assert(null != result, "null object");
Debug.Assert(null != properties, "null object");
for (int i = 0; i < properties.Length; ++i)
{
if (null != properties[i].GetExistingComplex)
{
object existing = properties[i].GetExistingComplex(result);
object obj = CreateComplexRecursive(record.GetValue(properties[i].Ordinal), existing);
if (null == existing)
{
properties[i].ClrProperty(result, obj);
}
}
else
{
properties[i].ClrProperty(result,
ConvertDBNull(
record.GetValue(
properties[i].Ordinal)));
}
}
}
private static object ConvertDBNull(object value)
{
return ((DBNull.Value != value) ? value : null);
}
private object CreateComplexRecursive(object record, object existing)
{
return ((DBNull.Value != record) ? CreateComplexRecursive((IExtendedDataRecord)record, existing) : existing);
}
private object CreateComplexRecursive(IExtendedDataRecord record, object existing)
{
return CreateComplex(record, record.DataRecordInfo, existing);
}
private Plan GetPlan(IExtendedDataRecord record, DataRecordInfo recordInfo)
{
Debug.Assert(null != record, "null IExtendedDataRecord");
Debug.Assert(null != recordInfo, "null DataRecordInfo");
Debug.Assert(null != recordInfo.RecordType, "null TypeUsage");
Plan[] plans = _lastPlans ?? (_lastPlans = new Plan[MaxPlanCount]);
// find an existing plan in circular buffer
int index = _lastPlanIndex - 1;
for (int i = 0; i < MaxPlanCount; ++i)
{
index = (index + 1) % MaxPlanCount;
if (null == plans[index])
{
break;
}
if (plans[index].Key == recordInfo.RecordType)
{
_lastPlanIndex = index;
return plans[index];
}
}
Debug.Assert(0 <= index, "negative index");
Debug.Assert(index != _lastPlanIndex || (null == plans[index]), "index wrapped around");
// create a new plan
ObjectTypeMapping mapping = System.Data.Common.Internal.Materialization.Util.GetObjectMapping(recordInfo.RecordType.EdmType, _workspace);
Debug.Assert(null != mapping, "null ObjectTypeMapping");
Debug.Assert(Helper.IsComplexType(recordInfo.RecordType.EdmType),
"IExtendedDataRecord is not ComplexType");
_lastPlanIndex = index;
plans[index] = new Plan(recordInfo.RecordType, mapping, recordInfo.FieldMetadata);
return plans[index];
}
private sealed class Plan
{
internal readonly TypeUsage Key;
internal readonly Delegate ClrType;
internal readonly PlanEdmProperty[] Properties;
internal Plan(TypeUsage key, ObjectTypeMapping mapping, System.Collections.ObjectModel.ReadOnlyCollection<FieldMetadata> fields)
{
Debug.Assert(null != mapping, "null ObjectTypeMapping");
Debug.Assert(null != fields, "null FieldMetadata");
Key = key;
Debug.Assert(!Helper.IsEntityType(mapping.ClrType), "Expecting complex type");
ClrType = LightweightCodeGenerator.GetConstructorDelegateForType((ClrComplexType)mapping.ClrType);
Properties = new PlanEdmProperty[fields.Count];
int lastOrdinal = -1;
for (int i = 0; i < Properties.Length; ++i)
{
FieldMetadata field = fields[i];
Debug.Assert(unchecked((uint)field.Ordinal) < unchecked((uint)fields.Count), "FieldMetadata.Ordinal out of range of Fields.Count");
Debug.Assert(lastOrdinal < field.Ordinal, "FieldMetadata.Ordinal is not increasing");
lastOrdinal = field.Ordinal;
Properties[i] = new PlanEdmProperty(lastOrdinal, mapping.GetPropertyMap(field.FieldType.Name).ClrProperty);
}
}
}
private struct PlanEdmProperty
{
internal readonly int Ordinal;
internal readonly Func<object, object> GetExistingComplex;
internal readonly Action<object, object> ClrProperty;
internal PlanEdmProperty(int ordinal, EdmProperty property)
{
Debug.Assert(0 <= ordinal, "negative ordinal");
Debug.Assert(null != property, "unsupported shadow state");
this.Ordinal = ordinal;
this.GetExistingComplex = Helper.IsComplexType(property.TypeUsage.EdmType)
? LightweightCodeGenerator.GetGetterDelegateForProperty(property) : null;
this.ClrProperty = LightweightCodeGenerator.GetSetterDelegateForProperty(property);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,220 @@
//---------------------------------------------------------------------
// <copyright file="EntityProxyTypeInfo.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
namespace System.Data.Objects.Internal
{
using System;
using System.Collections.Generic;
using System.Data.Metadata.Edm;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
/// <summary>
/// Contains the Type of a proxy class, along with any behaviors associated with that proxy Type.
/// </summary>
internal sealed class EntityProxyTypeInfo
{
private readonly Type _proxyType;
private readonly ClrEntityType _entityType; // The OSpace entity type that created this proxy info
internal const string EntityWrapperFieldName = "_entityWrapper";
private const string InitializeEntityCollectionsName = "InitializeEntityCollections";
private readonly DynamicMethod _initializeCollections;
private readonly Func<object, string, object> _baseGetter;
private readonly HashSet<string> _propertiesWithBaseGetter;
private readonly Action<object, string, object> _baseSetter;
private readonly HashSet<string> _propertiesWithBaseSetter;
private readonly Func<object, object> Proxy_GetEntityWrapper;
private readonly Func<object, object, object> Proxy_SetEntityWrapper; // IEntityWrapper Func(object proxy, IEntityWrapper value)
private readonly Func<object> _createObject;
// An index of relationship metadata strings to an AssociationType
// This is used when metadata is not otherwise available to the proxy
private readonly Dictionary<Tuple<string, string>, AssociationType> _navigationPropertyAssociationTypes;
internal EntityProxyTypeInfo(Type proxyType, ClrEntityType ospaceEntityType, DynamicMethod initializeCollections, List<PropertyInfo> baseGetters, List<PropertyInfo> baseSetters)
{
Debug.Assert(proxyType != null, "proxyType must be non-null");
_proxyType = proxyType;
_entityType = ospaceEntityType;
_initializeCollections = initializeCollections;
_navigationPropertyAssociationTypes = new Dictionary<Tuple<string, string>, AssociationType>();
foreach (NavigationProperty navigationProperty in ospaceEntityType.NavigationProperties)
{
_navigationPropertyAssociationTypes.Add(
new Tuple<string, string>(
navigationProperty.RelationshipType.FullName,
navigationProperty.ToEndMember.Name),
(AssociationType)navigationProperty.RelationshipType);
if (navigationProperty.RelationshipType.Name != navigationProperty.RelationshipType.FullName)
{
// Sometimes there isn't enough metadata to have a container name
// Default codegen doesn't qualify names
_navigationPropertyAssociationTypes.Add(
new Tuple<string, string>(
navigationProperty.RelationshipType.Name,
navigationProperty.ToEndMember.Name),
(AssociationType)navigationProperty.RelationshipType);
}
}
FieldInfo entityWrapperField = proxyType.GetField(EntityWrapperFieldName, BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
ParameterExpression Object_Parameter = Expression.Parameter(typeof(object), "proxy");
ParameterExpression Value_Parameter = Expression.Parameter(typeof(object), "value");
Debug.Assert(entityWrapperField != null, "entityWrapperField does not exist");
// Create the Wrapper Getter
Expression<Func<object, object>> lambda = Expression.Lambda<Func<object, object>>(
Expression.Field(
Expression.Convert(Object_Parameter, entityWrapperField.DeclaringType), entityWrapperField),
Object_Parameter);
Func<object, object> getEntityWrapperDelegate = lambda.Compile();
Proxy_GetEntityWrapper = (object proxy) =>
{
// This code validates that the wrapper points to the proxy that holds the wrapper.
// This guards against mischief by switching this wrapper out for another one obtained
// from a different object.
IEntityWrapper wrapper = ((IEntityWrapper)getEntityWrapperDelegate(proxy));
if (wrapper != null && !object.ReferenceEquals(wrapper.Entity, proxy))
{
throw new InvalidOperationException(System.Data.Entity.Strings.EntityProxyTypeInfo_ProxyHasWrongWrapper);
}
return wrapper;
};
// Create the Wrapper setter
Proxy_SetEntityWrapper = Expression.Lambda<Func<object, object, object>>(
Expression.Assign(
Expression.Field(
Expression.Convert(Object_Parameter, entityWrapperField.DeclaringType),
entityWrapperField),
Value_Parameter),
Object_Parameter, Value_Parameter).Compile();
ParameterExpression PropertyName_Parameter = Expression.Parameter(typeof(string), "propertyName");
MethodInfo baseGetterMethod = proxyType.GetMethod("GetBasePropertyValue", BindingFlags.Public | BindingFlags.Instance, null, new Type[] { typeof(string) }, null);
if (baseGetterMethod != null)
{
_baseGetter = Expression.Lambda<Func<object, string, object>>(
Expression.Call(Expression.Convert(Object_Parameter, proxyType), baseGetterMethod, PropertyName_Parameter),
Object_Parameter, PropertyName_Parameter).Compile();
}
ParameterExpression PropertyValue_Parameter = Expression.Parameter(typeof(object), "propertyName");
MethodInfo baseSetterMethod = proxyType.GetMethod("SetBasePropertyValue", BindingFlags.Public | BindingFlags.Instance, null, new Type[] { typeof(string), typeof(object) }, null);
if (baseSetterMethod != null)
{
_baseSetter = Expression.Lambda<Action<object, string, object>>(
Expression.Call(Expression.Convert(Object_Parameter, proxyType), baseSetterMethod, PropertyName_Parameter, PropertyValue_Parameter),
Object_Parameter, PropertyName_Parameter, PropertyValue_Parameter).Compile();
}
_propertiesWithBaseGetter = new HashSet<string>(baseGetters.Select(p => p.Name));
_propertiesWithBaseSetter = new HashSet<string>(baseSetters.Select(p => p.Name));
_createObject = LightweightCodeGenerator.CreateConstructor(proxyType) as Func<object>;
}
internal object CreateProxyObject()
{
return _createObject();
}
internal Type ProxyType
{
get { return _proxyType; }
}
internal DynamicMethod InitializeEntityCollections
{
get { return _initializeCollections; }
}
public Func<object, string, object> BaseGetter
{
get { return _baseGetter; }
}
public bool ContainsBaseGetter(string propertyName)
{
return BaseGetter != null && _propertiesWithBaseGetter.Contains(propertyName);
}
public bool ContainsBaseSetter(string propertyName)
{
return BaseSetter != null && _propertiesWithBaseSetter.Contains(propertyName);
}
public Action<object, string, object> BaseSetter
{
get { return _baseSetter; }
}
public bool TryGetNavigationPropertyAssociationType(string relationshipName, string targetRoleName, out AssociationType associationType)
{
return _navigationPropertyAssociationTypes.TryGetValue(new Tuple<string, string>(relationshipName, targetRoleName), out associationType);
}
public void ValidateType(ClrEntityType ospaceEntityType)
{
if (ospaceEntityType != _entityType && ospaceEntityType.HashedDescription != _entityType.HashedDescription)
{
Debug.Assert(ospaceEntityType.ClrType == _entityType.ClrType);
throw EntityUtil.DuplicateTypeForProxyType(ospaceEntityType.ClrType);
}
}
#region Wrapper on the Proxy
/// <summary>
/// Set the proxy object's private entity wrapper field value to the specified entity wrapper object.
/// The proxy object (representing the wrapped entity) is retrieved from the wrapper itself.
/// </summary>
/// <param name="wrapper">Wrapper object to be referenced by the proxy.</param>
/// <returns>
/// The supplied entity wrapper.
/// This is done so that this method can be more easily composed within lambda expressions (such as in the materializer).
/// </returns>
internal IEntityWrapper SetEntityWrapper(IEntityWrapper wrapper)
{
Debug.Assert(wrapper != null, "wrapper must be non-null");
Debug.Assert(wrapper.Entity != null, "proxy must be non-null");
return Proxy_SetEntityWrapper(wrapper.Entity, wrapper) as IEntityWrapper;
}
/// <summary>
/// Gets the proxy object's entity wrapper field value
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
internal IEntityWrapper GetEntityWrapper(object entity)
{
return Proxy_GetEntityWrapper(entity) as IEntityWrapper;
}
internal Func<object, object> EntityWrapperDelegate
{
get { return Proxy_GetEntityWrapper; }
}
#endregion
}
}

View File

@@ -0,0 +1,270 @@
//---------------------------------------------------------------------
// <copyright file="EntitySqlQueryState.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
//---------------------------------------------------------------------
namespace System.Data.Objects
{
using System.Collections.Generic;
using System.Data.Common.CommandTrees;
using System.Data.Common.CommandTrees.ExpressionBuilder;
using System.Data.Common.EntitySql;
using System.Data.Common.QueryCache;
using System.Data.Metadata.Edm;
using System.Data.Objects.Internal;
using System.Diagnostics;
/// <summary>
/// ObjectQueryState based on Entity-SQL query text.
/// </summary>
internal sealed class EntitySqlQueryState : ObjectQueryState
{
/// <summary>
/// The Entity-SQL text that defines the query.
/// </summary>
/// <remarks>
/// It is important that this field is readonly for consistency reasons wrt <see cref="_queryExpression"/>.
/// If this field becomes read-write, then write should be allowed only when <see cref="_queryExpression"/> is null,
/// or there should be a mechanism keeping both fields consistent.
/// </remarks>
private readonly string _queryText;
/// <summary>
/// Optional <see cref="DbExpression"/> that defines the query. Must be semantically equal to the <see cref="_queryText"/>.
/// </summary>
/// <remarks>
/// It is important that this field is readonly for consistency reasons wrt <see cref="_queryText"/>.
/// If this field becomes read-write, then there should be a mechanism keeping both fields consistent.
/// </remarks>
private readonly DbExpression _queryExpression;
/// <summary>
/// Can a Limit subclause be appended to the text of this query?
/// </summary>
private readonly bool _allowsLimit;
/// <summary>
/// Initializes a new query EntitySqlQueryState instance.
/// </summary>
/// <param name="context">
/// The ObjectContext containing the metadata workspace the query was
/// built against, the connection on which to execute the query, and the
/// cache to store the results in. Must not be null.
/// </param>
/// <param name="commandText">
/// The Entity-SQL text of the query
/// </param>
/// <param name="mergeOption">
/// The merge option to use when retrieving results if an explicit merge option is not specified
/// </param>
internal EntitySqlQueryState(Type elementType, string commandText, bool allowsLimit, ObjectContext context, ObjectParameterCollection parameters, Span span)
: this(elementType, commandText, /*expression*/ null, allowsLimit, context, parameters, span)
{ }
/// <summary>
/// Initializes a new query EntitySqlQueryState instance.
/// </summary>
/// <param name="context">
/// The ObjectContext containing the metadata workspace the query was
/// built against, the connection on which to execute the query, and the
/// cache to store the results in. Must not be null.
/// </param>
/// <param name="commandText">
/// The Entity-SQL text of the query
/// </param>
/// <param name="expression">
/// Optional <see cref="DbExpression"/> that defines the query. Must be semantically equal to the <paramref name="commandText"/>.
/// </param>
/// <param name="mergeOption">
/// The merge option to use when retrieving results if an explicit merge option is not specified
/// </param>
internal EntitySqlQueryState(Type elementType, string commandText, DbExpression expression, bool allowsLimit, ObjectContext context, ObjectParameterCollection parameters, Span span)
: base(elementType, context, parameters, span)
{
EntityUtil.CheckArgumentNull(commandText, "commandText");
if (string.IsNullOrEmpty(commandText))
{
throw EntityUtil.Argument(System.Data.Entity.Strings.ObjectQuery_InvalidEmptyQuery, "commandText");
}
_queryText = commandText;
_queryExpression = expression;
_allowsLimit = allowsLimit;
}
/// <summary>
/// Determines whether or not the current query is a 'Skip' or 'Sort' operation
/// and so would allow a 'Limit' clause to be appended to the current query text.
/// </summary>
/// <returns>
/// <c>True</c> if the current query is a Skip or Sort expression, or a
/// Project expression with a Skip or Sort expression input.
/// </returns>
internal bool AllowsLimitSubclause { get { return _allowsLimit; } }
/// <summary>
/// Always returns the Entity-SQL text of the implemented ObjectQuery.
/// </summary>
/// <param name="commandText">Always set to the Entity-SQL text of this ObjectQuery.</param>
/// <returns>Always returns <c>true</c>.</returns>
internal override bool TryGetCommandText(out string commandText)
{
commandText = this._queryText;
return true;
}
internal override bool TryGetExpression(out System.Linq.Expressions.Expression expression)
{
expression = null;
return false;
}
protected override TypeUsage GetResultType()
{
DbExpression query = this.Parse();
return query.ResultType;
}
internal override ObjectQueryState Include<TElementType>(ObjectQuery<TElementType> sourceQuery, string includePath)
{
ObjectQueryState retState = new EntitySqlQueryState(this.ElementType, _queryText, _queryExpression, _allowsLimit, this.ObjectContext, ObjectParameterCollection.DeepCopy(this.Parameters), Span.IncludeIn(this.Span, includePath));
this.ApplySettingsTo(retState);
return retState;
}
internal override ObjectQueryExecutionPlan GetExecutionPlan(MergeOption? forMergeOption)
{
// Metadata is required to generate the execution plan or to retrieve it from the cache.
this.ObjectContext.EnsureMetadata();
// Determine the required merge option, with the following precedence:
// 1. The merge option specified to Execute(MergeOption) as forMergeOption.
// 2. The merge option set via ObjectQuery.MergeOption.
// 3. The global default merge option.
MergeOption mergeOption = EnsureMergeOption(forMergeOption, this.UserSpecifiedMergeOption);
// If a cached plan is present, then it can be reused if it has the required merge option
// (since span and parameters cannot change between executions). However, if the cached
// plan does not have the required merge option we proceed as if it were not present.
ObjectQueryExecutionPlan plan = this._cachedPlan;
if (plan != null)
{
if (plan.MergeOption == mergeOption)
{
return plan;
}
else
{
plan = null;
}
}
// There is no cached plan (or it was cleared), so the execution plan must be retrieved from
// the global query cache (if plan caching is enabled) or rebuilt for the required merge option.
QueryCacheManager cacheManager = null;
EntitySqlQueryCacheKey cacheKey = null;
if (this.PlanCachingEnabled)
{
// Create a new cache key that reflects the current state of the Parameters collection
// and the Span object (if any), and uses the specified merge option.
cacheKey = new EntitySqlQueryCacheKey(
this.ObjectContext.DefaultContainerName,
_queryText,
(null == this.Parameters ? 0 : this.Parameters.Count),
(null == this.Parameters ? null : this.Parameters.GetCacheKey()),
(null == this.Span ? null : this.Span.GetCacheKey()),
mergeOption,
this.ElementType);
cacheManager = this.ObjectContext.MetadataWorkspace.GetQueryCacheManager();
ObjectQueryExecutionPlan executionPlan = null;
if (cacheManager.TryCacheLookup(cacheKey, out executionPlan))
{
plan = executionPlan;
}
}
if (plan == null)
{
// Either caching is not enabled or the execution plan was not found in the cache
DbExpression queryExpression = this.Parse();
Debug.Assert(queryExpression != null, "EntitySqlQueryState.Parse returned null expression?");
DbQueryCommandTree tree = DbQueryCommandTree.FromValidExpression(this.ObjectContext.MetadataWorkspace, DataSpace.CSpace, queryExpression);
plan = ObjectQueryExecutionPlan.Prepare(this.ObjectContext, tree, this.ElementType, mergeOption, this.Span, null, DbExpressionBuilder.AliasGenerator);
// If caching is enabled then update the cache now.
// Note: the logic is the same as in ELinqQueryState.
if (cacheKey != null)
{
var newEntry = new QueryCacheEntry(cacheKey, plan);
QueryCacheEntry foundEntry = null;
if (cacheManager.TryLookupAndAdd(newEntry, out foundEntry))
{
// If TryLookupAndAdd returns 'true' then the entry was already present in the cache when the attempt to add was made.
// In this case the existing execution plan should be used.
plan = (ObjectQueryExecutionPlan)foundEntry.GetTarget();
}
}
}
if (this.Parameters != null)
{
this.Parameters.SetReadOnly(true);
}
// Update the cached plan with the newly retrieved/prepared plan
this._cachedPlan = plan;
// Return the execution plan
return plan;
}
internal DbExpression Parse()
{
if (_queryExpression != null)
{
return _queryExpression;
}
List<DbParameterReferenceExpression> parameters = null;
if (this.Parameters != null)
{
parameters = new List<DbParameterReferenceExpression>(this.Parameters.Count);
foreach (ObjectParameter parameter in this.Parameters)
{
TypeUsage typeUsage = parameter.TypeUsage;
if (null == typeUsage)
{
// Since ObjectParameters do not allow users to specify 'facets', make
// sure that the parameter TypeUsage is not populated with the provider
// default facet values.
this.ObjectContext.Perspective.TryGetTypeByName(
parameter.MappableType.FullName,
false /* bIgnoreCase */,
out typeUsage);
}
Debug.Assert(typeUsage != null, "typeUsage != null");
parameters.Add(typeUsage.Parameter(parameter.Name));
}
}
DbLambda lambda =
CqlQuery.CompileQueryCommandLambda(
_queryText, // Command Text
this.ObjectContext.Perspective, // Perspective
null, // Parser options - null indicates 'use default'
parameters, // Parameters
null // Variables
);
Debug.Assert(lambda.Variables == null || lambda.Variables.Count == 0, "lambda.Variables must be empty");
return lambda.Body;
}
}
}

View File

@@ -0,0 +1,66 @@
//---------------------------------------------------------------------
// <copyright file="EntityWithChangeTrackerStrategy.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Text;
using System.Data.Objects.DataClasses;
using System.Diagnostics;
namespace System.Data.Objects.Internal
{
/// <summary>
/// Implementation of the change tracking strategy for entities that support change trackers.
/// These are typically entities that implement IEntityWithChangeTracker.
/// </summary>
internal sealed class EntityWithChangeTrackerStrategy : IChangeTrackingStrategy
{
private IEntityWithChangeTracker _entity;
/// <summary>
/// Constructs a strategy object that will cause the change tracker to be set onto the
/// given object.
/// </summary>
/// <param name="entity">The object onto which a change tracker will be set</param>
public EntityWithChangeTrackerStrategy(IEntityWithChangeTracker entity)
{
_entity = entity;
}
// See IChangeTrackingStrategy documentation
public void SetChangeTracker(IEntityChangeTracker changeTracker)
{
_entity.SetChangeTracker(changeTracker);
}
// See IChangeTrackingStrategy documentation
public void TakeSnapshot(EntityEntry entry)
{
if (entry != null && entry.RequiresComplexChangeTracking)
{
entry.TakeSnapshot(true);
}
}
// See IChangeTrackingStrategy documentation
public void SetCurrentValue(EntityEntry entry, StateManagerMemberMetadata member, int ordinal, object target, object value)
{
member.SetValue(target, value);
}
// See IChangeTrackingStrategy documentation
public void UpdateCurrentValueRecord(object value, EntityEntry entry)
{
// Has change tracker, but may or may not be a proxy
bool isProxy = entry.WrappedEntity.IdentityType != _entity.GetType();
entry.UpdateRecordWithoutSetModified(value, entry.CurrentValues);
if (isProxy)
{
entry.DetectChangesInProperties(true); // detect only complex property changes
}
}
}
}

View File

@@ -0,0 +1,50 @@
//---------------------------------------------------------------------
// <copyright file="EntityWithKeyStrategy.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.Objects.DataClasses;
using System.Diagnostics;
namespace System.Data.Objects.Internal
{
/// <summary>
/// Implementor of IEntityKeyStrategy for entities that implement IEntityWithKey. Getting and setting
/// the key is deferred to the entity itself.
/// </summary>
internal sealed class EntityWithKeyStrategy : IEntityKeyStrategy
{
private IEntityWithKey _entity;
/// <summary>
/// Creates a strategy object for the given entity. Keys will be stored in the entity.
/// </summary>
/// <param name="entity">The entity to use</param>
public EntityWithKeyStrategy(IEntityWithKey entity)
{
_entity = entity;
}
// See IEntityKeyStrategy
public EntityKey GetEntityKey()
{
return _entity.EntityKey;
}
// See IEntityKeyStrategy
public void SetEntityKey(EntityKey key)
{
_entity.EntityKey = key;
}
// See IEntityKeyStrategy
public EntityKey GetEntityKeyFromEntity()
{
return _entity.EntityKey;
}
}
}

View File

@@ -0,0 +1,323 @@
//---------------------------------------------------------------------
// <copyright file="EntityWrapper.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Collections;
using System.Data.Objects.DataClasses;
using System.Diagnostics;
using System.Reflection;
using System.Data.Metadata.Edm;
namespace System.Data.Objects.Internal
{
/// <summary>
/// An extension of the EntityWrapper class for entities that are known not to implement
/// IEntityWithRelationships. Using this class causes the RelationshipManager to be created
/// independently.
/// </summary>
/// <typeparam name="TEntity">The type of entity wrapped</typeparam>
internal sealed class EntityWrapperWithoutRelationships<TEntity> : EntityWrapper<TEntity>
{
/// <summary>
/// Constructs a wrapper as part of the materialization process. This constructor is only used
/// during materialization where it is known that the entity being wrapped is newly constructed.
/// This means that some checks are not performed that might be needed when thw wrapper is
/// created at other times, and information such as the identity type is passed in because
/// it is readily available in the materializer.
/// </summary>
/// <param name="entity">The entity to wrap</param>
/// <param name="key">The entity's key</param>
/// <param name="entitySet">The entity set, or null if none is known</param>
/// <param name="context">The context to which the entity should be attached</param>
/// <param name="mergeOption">NoTracking for non-tracked entities, AppendOnly otherwise</param>
/// <param name="identityType">The type of the entity ignoring any possible proxy type</param>
/// <param name="propertyStrategy">A delegate to create the property accesor strategy object</param>
/// <param name="changeTrackingStrategy">A delegate to create the change tracking strategy object</param>
/// <param name="keyStrategy">A delegate to create the entity key strategy object</param>
internal EntityWrapperWithoutRelationships(TEntity entity, EntityKey key, EntitySet entitySet, ObjectContext context, MergeOption mergeOption, Type identityType,
Func<object, IPropertyAccessorStrategy> propertyStrategy, Func<object, IChangeTrackingStrategy> changeTrackingStrategy, Func<object, IEntityKeyStrategy> keyStrategy)
: base(entity, RelationshipManager.Create(), key, entitySet, context, mergeOption, identityType,
propertyStrategy, changeTrackingStrategy, keyStrategy)
{
}
/// <summary>
/// Constructs a wrapper for the given entity.
/// Note: use EntityWrapperFactory instead of calling this constructor directly.
/// </summary>
/// <param name="entity">The entity to wrap</param>
/// <param name="propertyStrategy">A delegate to create the property accesor strategy object</param>
/// <param name="changeTrackingStrategy">A delegate to create the change tracking strategy object</param>
/// <param name="keyStrategy">A delegate to create the entity key strategy object</param>
internal EntityWrapperWithoutRelationships(TEntity entity, Func<object, IPropertyAccessorStrategy> propertyStrategy, Func<object, IChangeTrackingStrategy> changeTrackingStrategy, Func<object, IEntityKeyStrategy> keyStrategy)
: base(entity, RelationshipManager.Create(), propertyStrategy, changeTrackingStrategy, keyStrategy)
{
}
public override bool OwnsRelationshipManager
{
get { return false; }
}
public override void TakeSnapshotOfRelationships(EntityEntry entry)
{
entry.TakeSnapshotOfRelationships();
}
// See IEntityWrapper documentation
public override bool RequiresRelationshipChangeTracking
{
get { return true; }
}
}
/// <summary>
/// An extension of the EntityWrapper class for entities that implement IEntityWithRelationships.
/// Using this class causes creation of the RelationshipManager to be defered to the entity object.
/// </summary>
/// <typeparam name="TEntity">The type of entity wrapped</typeparam>
internal sealed class EntityWrapperWithRelationships<TEntity> : EntityWrapper<TEntity>
where TEntity : IEntityWithRelationships
{
/// <summary>
/// Constructs a wrapper as part of the materialization process. This constructor is only used
/// during materialization where it is known that the entity being wrapped is newly constructed.
/// This means that some checks are not performed that might be needed when thw wrapper is
/// created at other times, and information such as the identity type is passed in because
/// it is readily available in the materializer.
/// </summary>
/// <param name="entity">The entity to wrap</param>
/// <param name="key">The entity's key</param>
/// <param name="entitySet">The entity set, or null if none is known</param>
/// <param name="context">The context to which the entity should be attached</param>
/// <param name="mergeOption">NoTracking for non-tracked entities, AppendOnly otherwise</param>
/// <param name="identityType">The type of the entity ignoring any possible proxy type</param>
/// <param name="propertyStrategy">A delegate to create the property accesor strategy object</param>
/// <param name="changeTrackingStrategy">A delegate to create the change tracking strategy object</param>
/// <param name="keyStrategy">A delegate to create the entity key strategy object</param>
internal EntityWrapperWithRelationships(TEntity entity, EntityKey key, EntitySet entitySet, ObjectContext context, MergeOption mergeOption, Type identityType,
Func<object, IPropertyAccessorStrategy> propertyStrategy, Func<object, IChangeTrackingStrategy> changeTrackingStrategy, Func<object, IEntityKeyStrategy> keyStrategy)
: base(entity, entity.RelationshipManager, key, entitySet, context, mergeOption, identityType,
propertyStrategy, changeTrackingStrategy, keyStrategy)
{
}
/// <summary>
/// Constructs a wrapper for the given entity.
/// Note: use EntityWrapperFactory instead of calling this constructor directly.
/// </summary>
/// <param name="entity">The entity to wrap</param>
/// <param name="propertyStrategy">A delegate to create the property accesor strategy object</param>
/// <param name="changeTrackingStrategy">A delegate to create the change tracking strategy object</param>
/// <param name="keyStrategy">A delegate to create the entity key strategy object</param>
internal EntityWrapperWithRelationships(TEntity entity, Func<object, IPropertyAccessorStrategy> propertyStrategy, Func<object, IChangeTrackingStrategy> changeTrackingStrategy, Func<object, IEntityKeyStrategy> keyStrategy)
: base(entity, entity.RelationshipManager, propertyStrategy, changeTrackingStrategy, keyStrategy)
{
}
public override bool OwnsRelationshipManager
{
get { return true; }
}
public override void TakeSnapshotOfRelationships(EntityEntry entry)
{
}
// See IEntityWrapper documentation
public override bool RequiresRelationshipChangeTracking
{
get { return false; }
}
}
/// <summary>
/// Implementation of the IEntityWrapper interface that is used for non-null entities that do not implement
/// all of our standard interfaces: IEntityWithKey, IEntityWithRelationships, and IEntityWithChangeTracker, and
/// are not proxies.
/// Different strategies for dealing with these entities are defined by strategy objects that are set into the
/// wrapper at constructionn time.
/// </summary>
internal abstract class EntityWrapper<TEntity> : BaseEntityWrapper<TEntity>
{
private readonly TEntity _entity;
private IPropertyAccessorStrategy _propertyStrategy;
private IChangeTrackingStrategy _changeTrackingStrategy;
private IEntityKeyStrategy _keyStrategy;
/// <summary>
/// Constructs a wrapper for the given entity.
/// Note: use EntityWrapperFactory instead of calling this constructor directly.
/// </summary>
/// <param name="entity">The entity to wrap</param>
/// <param name="relationshipManager">The RelationshipManager associated with the entity</param>
/// <param name="propertyStrategy">A delegate to create the property accesor strategy object</param>
/// <param name="changeTrackingStrategy">A delegate to create the change tracking strategy object</param>
/// <param name="keyStrategy">A delegate to create the entity key strategy object</param>
protected EntityWrapper(TEntity entity, RelationshipManager relationshipManager,
Func<object, IPropertyAccessorStrategy> propertyStrategy, Func<object, IChangeTrackingStrategy> changeTrackingStrategy, Func<object, IEntityKeyStrategy> keyStrategy)
: base(entity, relationshipManager)
{
if (relationshipManager == null)
{
throw EntityUtil.UnexpectedNullRelationshipManager();
}
_entity = entity;
_propertyStrategy = propertyStrategy(entity);
_changeTrackingStrategy = changeTrackingStrategy(entity);
_keyStrategy = keyStrategy(entity);
Debug.Assert(_changeTrackingStrategy != null, "Change tracking strategy cannot be null.");
Debug.Assert(_keyStrategy != null, "Key strategy cannot be null.");
}
/// <summary>
/// Constructs a wrapper as part of the materialization process. This constructor is only used
/// during materialization where it is known that the entity being wrapped is newly constructed.
/// This means that some checks are not performed that might be needed when thw wrapper is
/// created at other times, and information such as the identity type is passed in because
/// it is readily available in the materializer.
/// </summary>
/// <param name="entity">The entity to wrap</param>
/// <param name="relationshipManager">The RelationshipManager associated with the entity</param>
/// <param name="key">The entity's key</param>
/// <param name="entitySet">The entity set, or null if none is known</param>
/// <param name="context">The context to which the entity should be attached</param>
/// <param name="mergeOption">NoTracking for non-tracked entities, AppendOnly otherwise</param>
/// <param name="identityType">The type of the entity ignoring any possible proxy type</param>
/// <param name="propertyStrategy">A delegate to create the property accesor strategy object</param>
/// <param name="changeTrackingStrategy">A delegate to create the change tracking strategy object</param>
/// <param name="keyStrategy">A delegate to create the entity key strategy object</param>
protected EntityWrapper(TEntity entity, RelationshipManager relationshipManager, EntityKey key, EntitySet set, ObjectContext context, MergeOption mergeOption, Type identityType,
Func<object, IPropertyAccessorStrategy> propertyStrategy, Func<object, IChangeTrackingStrategy> changeTrackingStrategy, Func<object, IEntityKeyStrategy> keyStrategy)
: base(entity, relationshipManager, set, context, mergeOption, identityType)
{
if (relationshipManager == null)
{
throw EntityUtil.UnexpectedNullRelationshipManager();
}
_entity = entity;
_propertyStrategy = propertyStrategy(entity);
_changeTrackingStrategy = changeTrackingStrategy(entity);
_keyStrategy = keyStrategy(entity);
Debug.Assert(_changeTrackingStrategy != null, "Change tracking strategy cannot be null.");
Debug.Assert(_keyStrategy != null, "Key strategy cannot be null.");
_keyStrategy.SetEntityKey(key);
}
// See IEntityWrapper documentation
public override void SetChangeTracker(IEntityChangeTracker changeTracker)
{
_changeTrackingStrategy.SetChangeTracker(changeTracker);
}
// See IEntityWrapper documentation
public override void TakeSnapshot(EntityEntry entry)
{
_changeTrackingStrategy.TakeSnapshot(entry);
}
// See IEntityWrapper documentation
public override EntityKey EntityKey
{
// If no strategy is set, then the key maintained by the wrapper is used,
// otherwise the request is passed to the strategy.
get
{
return _keyStrategy.GetEntityKey();
}
set
{
_keyStrategy.SetEntityKey(value);
}
}
public override EntityKey GetEntityKeyFromEntity()
{
return _keyStrategy.GetEntityKeyFromEntity();
}
public override void CollectionAdd(RelatedEnd relatedEnd, object value)
{
if (_propertyStrategy != null)
{
_propertyStrategy.CollectionAdd(relatedEnd, value);
}
}
public override bool CollectionRemove(RelatedEnd relatedEnd, object value)
{
return _propertyStrategy != null ? _propertyStrategy.CollectionRemove(relatedEnd, value) : false;
}
// See IEntityWrapper documentation
public override void EnsureCollectionNotNull(RelatedEnd relatedEnd)
{
if (_propertyStrategy != null)
{
object collection = _propertyStrategy.GetNavigationPropertyValue(relatedEnd);
if (collection == null)
{
collection = _propertyStrategy.CollectionCreate(relatedEnd);
_propertyStrategy.SetNavigationPropertyValue(relatedEnd, collection);
}
}
}
// See IEntityWrapper documentation
public override object GetNavigationPropertyValue(RelatedEnd relatedEnd)
{
return _propertyStrategy != null ? _propertyStrategy.GetNavigationPropertyValue(relatedEnd) : null;
}
// See IEntityWrapper documentation
public override void SetNavigationPropertyValue(RelatedEnd relatedEnd, object value)
{
if (_propertyStrategy != null)
{
_propertyStrategy.SetNavigationPropertyValue(relatedEnd, value);
}
}
// See IEntityWrapper documentation
public override void RemoveNavigationPropertyValue(RelatedEnd relatedEnd, object value)
{
if (_propertyStrategy != null)
{
object currentValue = _propertyStrategy.GetNavigationPropertyValue(relatedEnd);
if (Object.ReferenceEquals(currentValue, value))
{
_propertyStrategy.SetNavigationPropertyValue(relatedEnd, null);
}
}
}
// See IEntityWrapper documentation
public override object Entity
{
get { return _entity; }
}
// See IEntityWrapper<TEntity> documentation
public override TEntity TypedEntity
{
get { return _entity; }
}
// See IEntityWrapper documentation
public override void SetCurrentValue(EntityEntry entry, StateManagerMemberMetadata member, int ordinal, object target, object value)
{
_changeTrackingStrategy.SetCurrentValue(entry, member, ordinal, target, value);
}
// See IEntityWrapper documentation
public override void UpdateCurrentValueRecord(object value, EntityEntry entry)
{
_changeTrackingStrategy.UpdateCurrentValueRecord(value, entry);
}
}
}

View File

@@ -0,0 +1,363 @@
//---------------------------------------------------------------------
// <copyright file="EntityWrapperFactory.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Data.Objects.DataClasses;
using System.Data.Metadata.Edm;
using System.Diagnostics;
using System.ComponentModel;
using System.Collections.Generic;
using System.Reflection;
using System.Linq.Expressions;
using System.Security.Permissions;
using System.Threading;
using System.Data.Common.Utils;
using System.Runtime.CompilerServices;
namespace System.Data.Objects.Internal
{
/// <summary>
/// Factory class for creating IEntityWrapper instances.
/// </summary>
internal static class EntityWrapperFactory
{
// A cache of functions used to create IEntityWrapper instances for a given type
private static readonly Memoizer<Type, Func<object, IEntityWrapper>> _delegateCache = new Memoizer<Type, Func<object, IEntityWrapper>>(CreateWrapperDelegate, null);
/// <summary>
/// The single instance of the NullEntityWrapper.
/// </summary>
internal static IEntityWrapper NullWrapper
{
get { return NullEntityWrapper.NullWrapper; }
}
/// <summary>
/// Called to create a new wrapper outside of the normal materialization process.
/// This method is typically used when a new entity is created outside the context and then is
/// added or attached. The materializer bypasses this method and calls wrapper constructors
/// directory for performance reasons.
/// This method does not check whether or not the wrapper already exists in the context.
/// </summary>
/// <param name="entity">The entity for which a wrapper will be created</param>
/// <param name="key">The key associated with that entity, or null</param>
/// <returns>The new wrapper instance</returns>
internal static IEntityWrapper CreateNewWrapper(object entity, EntityKey key)
{
Debug.Assert(!(entity is IEntityWrapper), "Object is an IEntityWrapper instance instead of the raw entity.");
if (entity == null)
{
return NullEntityWrapper.NullWrapper;
}
// We used a cache of functions based on the actual type of entity that we need to wrap.
// Creatung these functions is slow, but once they are created they are relatively fast.
IEntityWrapper wrappedEntity = _delegateCache.Evaluate(entity.GetType())(entity);
wrappedEntity.RelationshipManager.SetWrappedOwner(wrappedEntity, entity);
// We cast to object here to avoid calling the overridden != operator on EntityKey.
// This creates a very small perf gain, which is none-the-less significant for lean no-tracking cases.
if ((object)key != null && (object)wrappedEntity.EntityKey == null)
{
wrappedEntity.EntityKey = key;
}
// If the entity is a proxy, set the wrapper to match
EntityProxyTypeInfo proxyTypeInfo;
if (EntityProxyFactory.TryGetProxyType(entity.GetType(), out proxyTypeInfo))
{
proxyTypeInfo.SetEntityWrapper(wrappedEntity);
}
return wrappedEntity;
}
// Creates a delegate that can then be used to create wrappers for a given type.
// This is slow which is why we only create the delegate once and then cache it.
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
private static Func<object, IEntityWrapper> CreateWrapperDelegate(Type entityType)
{
// For entities that implement all our interfaces we create a special lightweight wrapper that is both
// smaller and faster than the strategy-based wrapper.
// Otherwise, the wrapper is provided with different delegates depending on which interfaces are implemented.
bool isIEntityWithRelationships = typeof(IEntityWithRelationships).IsAssignableFrom(entityType);
bool isIEntityWithChangeTracker = typeof(IEntityWithChangeTracker).IsAssignableFrom(entityType);
bool isIEntityWithKey = typeof(IEntityWithKey).IsAssignableFrom(entityType);
bool isProxy = EntityProxyFactory.IsProxyType(entityType);
MethodInfo createDelegate;
if (isIEntityWithRelationships && isIEntityWithChangeTracker && isIEntityWithKey && !isProxy)
{
createDelegate = typeof(EntityWrapperFactory).GetMethod("CreateWrapperDelegateTypedLightweight", BindingFlags.NonPublic | BindingFlags.Static);
}
else if (isIEntityWithRelationships)
{
// This type of strategy wrapper is used when the entity implements IEntityWithRelationships
// In this case it is important that the entity itself is used to create the RelationshipManager
createDelegate = typeof(EntityWrapperFactory).GetMethod("CreateWrapperDelegateTypedWithRelationships", BindingFlags.NonPublic | BindingFlags.Static);
}
else
{
createDelegate = typeof(EntityWrapperFactory).GetMethod("CreateWrapperDelegateTypedWithoutRelationships", BindingFlags.NonPublic | BindingFlags.Static);
}
createDelegate = createDelegate.MakeGenericMethod(entityType);
return (Func<object, IEntityWrapper>)createDelegate.Invoke(null, new object[0]);
}
// Returns a delegate that creates the fast LightweightEntityWrapper
private static Func<object, IEntityWrapper> CreateWrapperDelegateTypedLightweight<TEntity>()
where TEntity : IEntityWithRelationships, IEntityWithKey, IEntityWithChangeTracker
{
return (entity) => new LightweightEntityWrapper<TEntity>((TEntity)entity);
}
// Returns a delegate that creates a strategy-based wrapper for entities that implement IEntityWithRelationships
private static Func<object, IEntityWrapper> CreateWrapperDelegateTypedWithRelationships<TEntity>()
where TEntity : IEntityWithRelationships
{
Func<object, IPropertyAccessorStrategy> propertyAccessorStrategy;
Func<object, IEntityKeyStrategy> keyStrategy;
Func<object, IChangeTrackingStrategy> changeTrackingStrategy;
CreateStrategies<TEntity>(out propertyAccessorStrategy, out changeTrackingStrategy, out keyStrategy);
return (entity) => new EntityWrapperWithRelationships<TEntity>((TEntity)entity, propertyAccessorStrategy, changeTrackingStrategy, keyStrategy);
}
// Returns a delegate that creates a strategy-based wrapper for entities that do not implement IEntityWithRelationships
private static Func<object, IEntityWrapper> CreateWrapperDelegateTypedWithoutRelationships<TEntity>()
{
Func<object, IPropertyAccessorStrategy> propertyAccessorStrategy;
Func<object, IEntityKeyStrategy> keyStrategy;
Func<object, IChangeTrackingStrategy> changeTrackingStrategy;
CreateStrategies<TEntity>(out propertyAccessorStrategy, out changeTrackingStrategy, out keyStrategy);
return (entity) => new EntityWrapperWithoutRelationships<TEntity>((TEntity)entity, propertyAccessorStrategy, changeTrackingStrategy, keyStrategy);
}
// Creates delegates that create strategy objects appropriate for the type of entity.
private static void CreateStrategies<TEntity>(out Func<object, IPropertyAccessorStrategy> createPropertyAccessorStrategy,
out Func<object, IChangeTrackingStrategy> createChangeTrackingStrategy,
out Func<object, IEntityKeyStrategy> createKeyStrategy)
{
Type entityType = typeof(TEntity);
bool isIEntityWithRelationships = typeof(IEntityWithRelationships).IsAssignableFrom(entityType);
bool isIEntityWithChangeTracker = typeof(IEntityWithChangeTracker).IsAssignableFrom(entityType);
bool isIEntityWithKey = typeof(IEntityWithKey).IsAssignableFrom(entityType);
bool isProxy = EntityProxyFactory.IsProxyType(entityType);
if (!isIEntityWithRelationships || isProxy)
{
createPropertyAccessorStrategy = GetPocoPropertyAccessorStrategyFunc();
}
else
{
createPropertyAccessorStrategy = GetNullPropertyAccessorStrategyFunc();
}
if (isIEntityWithChangeTracker)
{
createChangeTrackingStrategy = GetEntityWithChangeTrackerStrategyFunc();
}
else
{
createChangeTrackingStrategy = GetSnapshotChangeTrackingStrategyFunc();
}
if (isIEntityWithKey)
{
createKeyStrategy = GetEntityWithKeyStrategyStrategyFunc();
}
else
{
createKeyStrategy = GetPocoEntityKeyStrategyFunc();
}
}
/// <summary>
/// Convenience function that gets the ObjectStateManager from the context and calls
/// WrapEntityUsingStateManager.
/// </summary>
/// <param name="entity">the entity to wrap</param>
/// <param name="context">the context in which the entity may exist, or null</param>
/// <returns>a new or existing wrapper</returns>
internal static IEntityWrapper WrapEntityUsingContext(object entity, ObjectContext context)
{
EntityEntry existingEntry;
return WrapEntityUsingStateManagerGettingEntry(entity, context == null ? null : context.ObjectStateManager, out existingEntry);
}
/// <summary>
/// Convenience function that gets the ObjectStateManager from the context and calls
/// WrapEntityUsingStateManager.
/// </summary>
/// <param name="entity">The entity to wrap</param>
/// <param name="context">The context in which the entity may exist, or null</param>
/// <param name="existingEntry">Set to the existing state entry if one is found, else null</param>
/// <returns>a new or existing wrapper</returns>
internal static IEntityWrapper WrapEntityUsingContextGettingEntry(object entity, ObjectContext context, out EntityEntry existingEntry)
{
return WrapEntityUsingStateManagerGettingEntry(entity, context == null ? null : context.ObjectStateManager, out existingEntry);
}
/// <summary>
/// Wraps an entity and returns a new wrapper, or returns an existing wrapper if one
/// already exists in the ObjectStateManager or in a RelationshipManager associated with
/// the entity.
/// </summary>
/// <param name="entity">the entity to wrap</param>
/// <param name="context">the state manager in which the entity may exist, or null</param>
/// <returns>a new or existing wrapper</returns>
internal static IEntityWrapper WrapEntityUsingStateManager(object entity, ObjectStateManager stateManager)
{
EntityEntry existingEntry;
return WrapEntityUsingStateManagerGettingEntry(entity, stateManager, out existingEntry);
}
/// <summary>
/// Wraps an entity and returns a new wrapper, or returns an existing wrapper if one
/// already exists in the ObjectStateManager or in a RelationshipManager associated with
/// the entity.
/// </summary>
/// <param name="entity">The entity to wrap</param>
/// <param name="context">The state manager in which the entity may exist, or null</param>
/// <param name="existingEntry">The existing state entry for the given entity if one exists, otherwise null</param>
/// <returns>A new or existing wrapper</returns>
internal static IEntityWrapper WrapEntityUsingStateManagerGettingEntry(object entity, ObjectStateManager stateManager, out EntityEntry existingEntry)
{
Debug.Assert(!(entity is IEntityWrapper), "Object is an IEntityWrapper instance instead of the raw entity.");
IEntityWrapper wrapper = null;
existingEntry = null;
if (entity == null)
{
return NullEntityWrapper.NullWrapper;
}
// First attempt to find an existing wrapper in the ObjectStateMager.
if (stateManager != null)
{
existingEntry = stateManager.FindEntityEntry(entity);
if (existingEntry != null)
{
return existingEntry.WrappedEntity;
}
if (stateManager.TransactionManager.TrackProcessedEntities)
{
if (stateManager.TransactionManager.WrappedEntities.TryGetValue(entity, out wrapper))
{
return wrapper;
}
}
}
// If no entity was found in the OSM, then check if one exists on an associated
// RelationshipManager. This only works where the entity implements IEntityWithRelationshops.
IEntityWithRelationships entityWithRelationships = entity as IEntityWithRelationships;
if (entityWithRelationships != null)
{
RelationshipManager relManager = entityWithRelationships.RelationshipManager;
if (relManager == null)
{
throw EntityUtil.UnexpectedNullRelationshipManager();
}
IEntityWrapper wrappedEntity = relManager.WrappedOwner;
if (!Object.ReferenceEquals(wrappedEntity.Entity, entity))
{
// This means that the owner of the RelationshipManager must have been set
// incorrectly in the call to RelationshipManager.Create().
throw EntityUtil.InvalidRelationshipManagerOwner();
}
return wrappedEntity;
}
else
{
// Finally look to see if the instance is a proxy and get the wrapper from the proxy
EntityProxyFactory.TryGetProxyWrapper(entity, out wrapper);
}
// If we could not find an existing wrapper, then go create a new one
if (wrapper == null)
{
IEntityWithKey withKey = entity as IEntityWithKey;
wrapper = CreateNewWrapper(entity, withKey == null ? null : withKey.EntityKey);
}
if (stateManager != null && stateManager.TransactionManager.TrackProcessedEntities)
{
stateManager.TransactionManager.WrappedEntities.Add(entity, wrapper);
}
return wrapper;
}
/// <summary>
/// When an entity enters Object Services that was retreived with NoTracking, it may not have certain fields set that are in many cases
/// assumed to be present. This method updates the wrapper with a key and a context.
/// </summary>
/// <param name="wrapper">The wrapped entity</param>
/// <param name="context">The context that will be using this wrapper</param>
/// <param name="entitySet">The entity set this wrapped entity belongs to</param>
internal static void UpdateNoTrackingWrapper(IEntityWrapper wrapper, ObjectContext context, EntitySet entitySet)
{
if (wrapper.EntityKey == null)
{
wrapper.EntityKey = context.ObjectStateManager.CreateEntityKey(entitySet, wrapper.Entity);
}
if (wrapper.Context == null)
{
wrapper.AttachContext(context, entitySet, MergeOption.NoTracking);
}
}
/// <summary>
/// Returns a func that will create a PocoPropertyAccessorStrategy object for a given entity.
/// </summary>
/// <returns>The func to be used to create the strategy object.</returns>
internal static Func<object, IPropertyAccessorStrategy> GetPocoPropertyAccessorStrategyFunc()
{
return (object entity) => new PocoPropertyAccessorStrategy(entity);
}
/// <summary>
/// Returns a func that will create a null IPropertyAccessorStrategy strategy object for a given entity.
/// </summary>
/// <returns>The func to be used to create the strategy object.</returns>
internal static Func<object, IPropertyAccessorStrategy> GetNullPropertyAccessorStrategyFunc()
{
return (object entity) => null;
}
/// <summary>
/// Returns a func that will create a EntityWithChangeTrackerStrategy object for a given entity.
/// </summary>
/// <returns>The func to be used to create the strategy object.</returns>
internal static Func<object, IChangeTrackingStrategy> GetEntityWithChangeTrackerStrategyFunc()
{
return (object entity) => new EntityWithChangeTrackerStrategy((IEntityWithChangeTracker)entity);
}
/// <summary>
/// Returns a func that will create a SnapshotChangeTrackingStrategy object for a given entity.
/// </summary>
/// <returns>The func to be used to create the strategy object.</returns>
internal static Func<object, IChangeTrackingStrategy> GetSnapshotChangeTrackingStrategyFunc()
{
return (object entity) => SnapshotChangeTrackingStrategy.Instance;
}
/// <summary>
/// Returns a func that will create a EntityWithKeyStrategy object for a given entity.
/// </summary>
/// <returns>The func to be used to create the strategy object.</returns>
internal static Func<object, IEntityKeyStrategy> GetEntityWithKeyStrategyStrategyFunc()
{
return (object entity) => new EntityWithKeyStrategy((IEntityWithKey)entity);
}
/// <summary>
/// Returns a func that will create a GetPocoEntityKeyStrategyFunc object for a given entity.
/// </summary>
/// <returns>The func to be used to create the strategy object.</returns>
internal static Func<object, IEntityKeyStrategy> GetPocoEntityKeyStrategyFunc()
{
return (object entity) => new PocoEntityKeyStrategy();
}
}
}

View File

@@ -0,0 +1,134 @@
//---------------------------------------------------------------------
// <copyright file="ForeignKeyFactory.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.Metadata.Edm;
using System.Diagnostics;
using System.Data.Objects.DataClasses;
namespace System.Data.Objects.Internal
{
internal class ForeignKeyFactory
{
private const string s_NullPart = "EntityHasNullForeignKey";
private const string s_NullForeignKey = "EntityHasNullForeignKey.EntityHasNullForeignKey";
/// <summary>
/// Returns true if the supplied key represents a Conceptual Null
/// </summary>
/// <param name="key">The key to be checked</param>
public static bool IsConceptualNullKey(EntityKey key)
{
if (key == null)
{
return false;
}
return string.Equals(key.EntityContainerName, s_NullPart) &&
string.Equals(key.EntitySetName, s_NullPart);
}
/// <summary>
/// Checks if the Real Key represents different FK values
/// than those present when the Conceptual Null was created
/// </summary>
/// <param name="conceptualNullKey">The key representing the Conceptual Null</param>
/// <param name="realKey">The key to be checked</param>
/// <returns>True if the values are different, false otherwise</returns>
public static bool IsConceptualNullKeyChanged(EntityKey conceptualNullKey, EntityKey realKey)
{
Debug.Assert(IsConceptualNullKey(conceptualNullKey), "The key supplied is not a null key");
if (realKey == null)
{
return true;
}
return !EntityKey.InternalEquals(conceptualNullKey, realKey, compareEntitySets: false);
}
/// <summary>
/// Creates an EntityKey that represents a Conceptual Null
/// </summary>
/// <param name="originalKey">An EntityKey representing the existing FK values that could not be nulled</param>
/// <returns>EntityKey marked as a conceptual null with the FK values from the original key</returns>
public static EntityKey CreateConceptualNullKey(EntityKey originalKey)
{
Debug.Assert(originalKey != null, "Original key can not be null");
//Conceptual nulls have special entity set name and a copy of the previous values
EntityKey nullKey = new EntityKey(s_NullForeignKey, originalKey.EntityKeyValues);
return nullKey;
}
/// <summary>
/// Creates an EntityKey for a principal entity based on the foreign key values contained
/// in this entity. This implies that this entity is at the dependent end of the relationship.
/// </summary>
/// <param name="dependentEntry">The EntityEntry for the dependent that contains the FK</param>
/// <param name="relatedEnd">Identifies the principal end for which a key is required</param>
/// <returns>The key, or null if any value in the key is null</returns>
public static EntityKey CreateKeyFromForeignKeyValues(EntityEntry dependentEntry, RelatedEnd relatedEnd)
{
// Note: there is only ever one constraint per association type
ReferentialConstraint constraint = ((AssociationType)relatedEnd.RelationMetadata).ReferentialConstraints.First();
Debug.Assert(constraint.FromRole.Identity == relatedEnd.TargetRoleName, "Unexpected constraint role");
return CreateKeyFromForeignKeyValues(dependentEntry, constraint, relatedEnd.GetTargetEntitySetFromRelationshipSet(), useOriginalValues: false);
}
/// <summary>
/// Creates an EntityKey for a principal entity based on the foreign key values contained
/// in this entity. This implies that this entity is at the dependent end of the relationship.
/// </summary>
/// <param name="dependentEntry">The EntityEntry for the dependent that contains the FK</param>
/// <param name="constraint">The constraint that describes this FK relationship</param>
/// <param name="principalEntitySet">The entity set at the principal end of the the relationship</param>
/// <param name="useOriginalValues">If true then the key will be constructed from the original FK values</param>
/// <returns>The key, or null if any value in the key is null</returns>
public static EntityKey CreateKeyFromForeignKeyValues(EntityEntry dependentEntry, ReferentialConstraint constraint, EntitySet principalEntitySet, bool useOriginalValues)
{
// Build the key values. If any part of the key is null, then the entire key
// is considered null.
var dependentProps = constraint.ToProperties;
int numValues = dependentProps.Count;
if (numValues == 1)
{
object keyValue = useOriginalValues ?
dependentEntry.GetOriginalEntityValue(dependentProps.First().Name) :
dependentEntry.GetCurrentEntityValue(dependentProps.First().Name);
return keyValue == DBNull.Value ? null : new EntityKey(principalEntitySet, keyValue);
}
// Note that the properties in the principal entity set may be in a different order than
// they appear in the constraint. Therefore, we create name value mappings to ensure that
// the correct values are associated with the correct properties.
// Unfortunately, there is not way to call the public EntityKey constructor that takes pairs
// because the internal "object" constructor hides it. Even this doesn't work:
// new EntityKey(principalEntitySet, (IEnumerable<KeyValuePair<string, object>>)keyValues)
string[] keyNames = principalEntitySet.ElementType.KeyMemberNames;
Debug.Assert(keyNames.Length == numValues, "Number of entity set key names does not match constraint names");
object[] values = new object[numValues];
var principalProps = constraint.FromProperties;
for (int i = 0; i < numValues; i++)
{
object value = useOriginalValues ?
dependentEntry.GetOriginalEntityValue(dependentProps[i].Name) :
dependentEntry.GetCurrentEntityValue(dependentProps[i].Name);
if (value == DBNull.Value)
{
return null;
}
int keyIndex = Array.IndexOf(keyNames, principalProps[i].Name);
Debug.Assert(keyIndex >= 0 && keyIndex < numValues, "Could not find constraint prop name in entity set key names");
values[keyIndex] = value;
}
return new EntityKey(principalEntitySet, values);
}
}
}

View File

@@ -0,0 +1,52 @@
//------------------------------------------------------------------------------
// <copyright file="IChangeTrackingStrategy.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Text;
using System.Data.Objects.DataClasses;
namespace System.Data.Objects.Internal
{
/// <summary>
/// A strategy interface that defines methods used for different types of change tracking.
/// Implementors of this interface are used by the EntityWrapper class.
/// </summary>
internal interface IChangeTrackingStrategy
{
/// <summary>
/// Sets a change tracker onto an entity, or does nothing if the entity does not support change trackers.
/// </summary>
/// <param name="changeTracker">The change tracker to set</param>
void SetChangeTracker(IEntityChangeTracker changeTracker);
/// <summary>
/// Takes a snapshot of the entity contained in the given state entry, or does nothing if
/// snapshots are not required for the entity.
/// </summary>
/// <param name="entry">The state entry representing the entity to snapshot</param>
void TakeSnapshot(EntityEntry entry);
/// <summary>
/// Sets the given value onto the entity with the registered change either handled by the
/// entity itself or by using the given EntityEntry as the change tracker.
/// </summary>
/// <param name="entry">The state entry of the entity to for which a value should be set</param>
/// <param name="member">State member information indicating the member to set</param>
/// <param name="ordinal">The ordinal of the member to set</param>
/// <param name="target">The object onto which the value should be set; may be the entity, or a contained complex value</param>
/// <param name="value">The value to set</param>
void SetCurrentValue(EntityEntry entry, StateManagerMemberMetadata member, int ordinal, object target, object value);
/// <summary>
/// Updates the current value records using Shaper.UpdateRecord but with additional change tracking logic
/// added as required by POCO and proxy entities.
/// </summary>
/// <param name="value">The value</param>
/// <param name="entry">The existing ObjectStateEntry</param>
void UpdateCurrentValueRecord(object value, EntityEntry entry);
}
}

View File

@@ -0,0 +1,37 @@
//---------------------------------------------------------------------
// <copyright file="IEntityKeyStrategy.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Text;
using System.Data.Objects.DataClasses;
namespace System.Data.Objects.Internal
{
/// <summary>
/// A strategy interface that defines methods used for setting and getting EntityKey values on an entity.
/// Implementors of this interface are used by the EntityWrapper class.
/// </summary>
internal interface IEntityKeyStrategy
{
/// <summary>
/// Gets the entity key.
/// </summary>
/// <returns>The key</returns>
EntityKey GetEntityKey();
/// <summary>
/// Sets the entity key
/// </summary>
/// <param name="key">The key</param>
void SetEntityKey(EntityKey key);
/// <summary>
/// Returns the entity key directly from the entity
/// </summary>
/// <returns>the key</returns>
EntityKey GetEntityKeyFromEntity();
}
}

View File

@@ -0,0 +1,200 @@
//---------------------------------------------------------------------
// <copyright file="IEntityWrapper.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Collections;
using System.Data.Objects.DataClasses;
using System.Runtime.CompilerServices;
using System.Data.Metadata.Edm;
namespace System.Data.Objects.Internal
{
/// <summary>
/// Internally, entities are wrapped in some implementation of this
/// interface. This allows the RelationshipManager and other classes
/// to treat POCO entities and traditional entities in the same way
/// where ever possible.
/// </summary>
internal interface IEntityWrapper
{
/// <summary>
/// The Relationship Manager that is associated with the wrapped entity.
/// </summary>
RelationshipManager RelationshipManager { get; }
/// <summary>
/// Information about whether or not the entity instance actually owns and uses its RelationshipManager
/// This is used to determine how to do relationship fixup in some cases
/// </summary>
bool OwnsRelationshipManager { get; }
/// <summary>
/// The actual entity that is wrapped by this wrapper object.
/// </summary>
object Entity { get; }
/// <summary>
/// If this IEntityWrapper is tracked, accesses the ObjectStateEntry that is used in the state manager
/// </summary>
EntityEntry ObjectStateEntry { get; set; }
/// <summary>
/// Ensures that the collection with the given name is not null by setting a new empty
/// collection onto the property if necessary.
/// </summary>
/// <param name="collectionName">The name of the collection to operate on</param>
void EnsureCollectionNotNull(RelatedEnd relatedEnd);
/// <summary>
/// The key associated with this entity, which may be null if no key is known.
/// </summary>
EntityKey EntityKey { get; set; }
/// <summary>
/// Retrieves the EntityKey from the entity if it implements IEntityWithKey
/// </summary>
/// <returns>The EntityKey on the entity</returns>
EntityKey GetEntityKeyFromEntity();
/// <summary>
/// The context with which the wrapped entity is associated, or null if the entity
/// is detached.
/// </summary>
ObjectContext Context { get; set; }
/// <summary>
/// The merge option ----oicated with the wrapped entity.
/// </summary>
MergeOption MergeOption { get; }
/// <summary>
/// Attaches the wrapped entity to the given context.
/// </summary>
/// <param name="context">the context with which to associate this entity</param>
/// <param name="entitySet">the entity set to which the entity belongs</param>
/// <param name="mergeOption">the merge option to use</param>
void AttachContext(ObjectContext context, EntitySet entitySet, MergeOption mergeOption);
/// <summary>
/// Resets the context with which the wrapped entity is associated.
/// </summary>
/// <param name="context">the context with which to associate this entity</param>
/// <param name="entitySet">the entity set to which the entity belongs</param>
/// <param name="mergeOption">the merge option to use</param>
void ResetContext(ObjectContext context, EntitySet entitySet, MergeOption mergeOption);
/// <summary>
/// Detaches the wrapped entity from its associated context.
/// </summary>
void DetachContext();
/// <summary>
/// Sets the entity's ObjectStateEntry as the entity's change tracker if possible.
/// The ObjectStateEntry may be null when a change tracker is being removed from an
/// entity.
/// </summary>
/// <param name="changeTracker">the object to use as a change tracker</param>
void SetChangeTracker(IEntityChangeTracker changeTracker);
/// <summary>
/// Takes a snapshot of the entity state unless the entity has an associated
/// change tracker or the given entry is null, in which case no action is taken.
/// </summary>
/// <param name="entry">the entity's associated state entry</param>
void TakeSnapshot(EntityEntry entry);
/// <summary>
/// Takes a snapshot of the relationships of the entity stored in the entry
/// </summary>
/// <param name="entry"></param>
void TakeSnapshotOfRelationships(EntityEntry entry);
/// <summary>
/// The Type object that should be used to identify this entity in o-space.
/// This is normally just the type of the entity object, but if the object
/// is a proxy that we have generated, then the type of the base class is returned instead.
/// This ensures that both proxy entities and normal entities are treated as the
/// same kind of entity in the metadata and places where the metadata is used.
/// </summary>
Type IdentityType { get; }
/// <summary>
/// Populates a value into a collection of values stored in a property of the entity.
/// If the collection to be populated is actually managed by and returned from
/// the RelationshipManager when needed, then this method is a no-op. This is
/// typically the case for non-POCO entities.
/// </summary>
void CollectionAdd(RelatedEnd relatedEnd, object value);
/// <summary>
/// Removes a value from a collection of values stored in a property of the entity.
/// If the collection to be updated is actually managed by and returned from
/// the RelationshipManager when needed, then this method is a no-op. This is
/// typically the case for non-POCO entities.
/// </summary>
bool CollectionRemove(RelatedEnd relatedEnd, object value);
/// <summary>
/// Returns value of the entity's property described by the navigation property.
/// </summary>
/// <param name="relatedEnd">navigation property to retrieve</param>
/// <returns></returns>
object GetNavigationPropertyValue(RelatedEnd relatedEnd);
/// <summary>
/// Populates a single value into a field or property of the entity.
/// If the element to be populated is actually managed by and returned from
/// the RelationshipManager when needed, then this method is a no-op. This is
/// typically the case for non-POCO entities.
/// </summary>
void SetNavigationPropertyValue(RelatedEnd relatedEnd, object value);
/// <summary>
/// Removes a single value from a field or property of the entity.
/// If the field or property contains reference to a different object,
/// this method is a no-op.
/// If the element to be populated is actually managed by and returned from
/// the RelationshipManager when needed, then this method is a no-op. This is
/// typically the case for non-POCO entities.
/// </summary>
/// <param name="value">The value to remove</param>
void RemoveNavigationPropertyValue(RelatedEnd relatedEnd, object value);
/// <summary>
/// Sets the given value onto the entity with the registered change either handled by the
/// entity itself or by using the given EntityEntry as the change tracker.
/// </summary>
/// <param name="entry">The state entry of the entity to for which a value should be set</param>
/// <param name="member">State member information indicating the member to set</param>
/// <param name="ordinal">The ordinal of the member to set</param>
/// <param name="target">The object onto which the value should be set; may be the entity, or a contained complex value</param>
/// <param name="value">The value to set</param>
void SetCurrentValue(EntityEntry entry, StateManagerMemberMetadata member, int ordinal, object target, object value);
/// <summary>
/// Set to true while the process of initalizing RelatedEnd objects for an IPOCO proxy is in process.
/// This flag prevents the context from being set onto the related ends, which in turn means that
/// the related ends don't need to have keys, which in turn means they don't need to be part of an EntitySet.
/// </summary>
bool InitializingProxyRelatedEnds { get; set; }
/// <summary>
/// Updates the current value records using Shaper.UpdateRecord but with additional change tracking logic
/// added as required by POCO and proxy entities. For the simple case of no proxy and an entity with
/// a change tracker, this translates into a simple call to ShaperUpdateRecord.
/// </summary>
/// <param name="value">The value</param>
/// <param name="entry">The existing ObjectStateEntry</param>
void UpdateCurrentValueRecord(object value, EntityEntry entry);
/// <summary>
/// True if the underlying entity is not capable of tracking changes to relationships such that
/// DetectChanges is required to do this.
/// </summary>
bool RequiresRelationshipChangeTracking { get; }
}
}

View File

@@ -0,0 +1,56 @@
//---------------------------------------------------------------------
// <copyright file="IPropertyAccessorStrategy.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Text;
using System.Data.Objects.DataClasses;
namespace System.Data.Objects.Internal
{
/// <summary>
/// A strategy interface that defines methods used for setting and getting values of
/// properties and collections on entities.
/// Implementors of this interface are used by the EntityWrapper class.
/// </summary>
internal interface IPropertyAccessorStrategy
{
/// <summary>
/// Gets the value of a navigation property for the given related end.
/// </summary>
/// <param name="relatedEnd">Specifies the related end for which a value is required</param>
/// <returns>The property value</returns>
object GetNavigationPropertyValue(RelatedEnd relatedEnd);
/// <summary>
/// Sets the value of a navigation property for the given related end.
/// </summary>
/// <param name="relatedEnd">Specifies the related end for which a value should be set</param>
/// <param name="value">The value to set</param>
void SetNavigationPropertyValue(RelatedEnd relatedEnd, object value);
/// <summary>
/// Adds a value to the collection represented by the given related end.
/// </summary>
/// <param name="relatedEnd">The related end for the collection to use</param>
/// <param name="value">The value to add to the collection</param>
void CollectionAdd(RelatedEnd relatedEnd, object value);
/// <summary>
/// Removes a value from the collection represented by the given related end.
/// </summary>
/// <param name="relatedEnd">The related end for the collection to use</param>
/// <param name="value">The value to remove from the collection</param>
/// <returns>True if a value was found and removed; false otherwise</returns>
bool CollectionRemove(RelatedEnd relatedEnd, object value);
/// <summary>
/// Creates a new collection for the given related end.
/// </summary>
/// <param name="relatedEnd">The related end for which a collection should be created</param>
/// <returns>The new collection</returns>
object CollectionCreate(RelatedEnd relatedEnd);
}
}

View File

@@ -0,0 +1,183 @@
//---------------------------------------------------------------------
// <copyright file="LazyLoadedCollectionBehavior.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Security;
using System.Security.Permissions;
using System.Data.Metadata.Edm;
using System.Data.Objects.DataClasses;
using System.Collections;
namespace System.Data.Objects.Internal
{
/// <summary>
/// Defines and injects behavior into proxy class Type definitions
/// to allow navigation properties to lazily load their references or collection elements.
/// </summary>
internal sealed class LazyLoadBehavior
{
/// <summary>
/// Return an expression tree that represents the actions required to load the related end
/// associated with the intercepted proxy member.
/// </summary>
/// <param name="member">
/// EdmMember that specifies the member to be intercepted.
/// </param>
/// <param name="property">
/// PropertyInfo that specifies the CLR property to be intercepted.
/// </param>
/// <param name="proxyParameter">
/// ParameterExpression that represents the proxy object.
/// </param>
/// <param name="itemParameter">
/// ParameterExpression that represents the proxied property value.
/// </param>
/// <param name="getEntityWrapperDelegate">The Func that retrieves the wrapper from a proxy</param>
/// <returns>
/// Expression tree that encapsulates lazy loading behavior for the supplied member,
/// or null if the expression tree could not be constructed.
/// </returns>
internal static Func<TProxy, TItem, bool> GetInterceptorDelegate<TProxy, TItem>(EdmMember member, Func<object, object> getEntityWrapperDelegate)
where TProxy : class
where TItem : class
{
Func<TProxy, TItem, bool> interceptorDelegate = (proxy, item) => true;
Debug.Assert(member.BuiltInTypeKind == BuiltInTypeKind.NavigationProperty, "member should represent a navigation property");
if (member.BuiltInTypeKind == BuiltInTypeKind.NavigationProperty)
{
NavigationProperty navProperty = (NavigationProperty)member;
RelationshipMultiplicity multiplicity = navProperty.ToEndMember.RelationshipMultiplicity;
// Given the proxy and item parameters, construct one of the following expressions:
//
// For collections:
// LazyLoadBehavior.LoadCollection(collection, "relationshipName", "targetRoleName", proxy._entityWrapperField)
//
// For entity references:
// LazyLoadBehavior.LoadReference(item, "relationshipName", "targetRoleName", proxy._entityWrapperField)
//
// Both of these expressions return an object of the same type as the first parameter to LoadXYZ method.
// In many cases, this will be the first parameter.
if (multiplicity == RelationshipMultiplicity.Many)
{
interceptorDelegate = (proxy, item) => LoadProperty<TItem>(item,
navProperty.RelationshipType.Identity,
navProperty.ToEndMember.Identity,
false,
getEntityWrapperDelegate(proxy));
}
else
{
interceptorDelegate = (proxy, item) => LoadProperty<TItem>(item,
navProperty.RelationshipType.Identity,
navProperty.ToEndMember.Identity,
true,
getEntityWrapperDelegate(proxy));
}
}
return interceptorDelegate;
}
/// <summary>
/// Determine if the specified member is compatible with lazy loading.
/// </summary>
/// <param name="ospaceEntityType">
/// OSpace EntityType representing a type that may be proxied.
/// </param>
/// <param name="member">
/// Member of the <paramref name="ospaceEntityType" /> to be examined.
/// </param>
/// <returns>
/// True if the member is compatible with lazy loading; otherwise false.
/// </returns>
/// <remarks>
/// To be compatible with lazy loading,
/// a member must meet the criteria for being able to be proxied (defined elsewhere),
/// and must be a navigation property.
/// In addition, for relationships with a multiplicity of Many,
/// the property type must be an implementation of ICollection&lt;T&gt;.
/// </remarks>
internal static bool IsLazyLoadCandidate(EntityType ospaceEntityType, EdmMember member)
{
Debug.Assert(ospaceEntityType.DataSpace == DataSpace.OSpace, "ospaceEntityType.DataSpace must be OSpace");
bool isCandidate = false;
if (member.BuiltInTypeKind == BuiltInTypeKind.NavigationProperty)
{
NavigationProperty navProperty = (NavigationProperty)member;
RelationshipMultiplicity multiplicity = navProperty.ToEndMember.RelationshipMultiplicity;
PropertyInfo propertyInfo = EntityUtil.GetTopProperty(ospaceEntityType.ClrType, member.Name);
Debug.Assert(propertyInfo != null, "Should have found lazy loading property");
Type propertyValueType = propertyInfo.PropertyType;
if (multiplicity == RelationshipMultiplicity.Many)
{
Type elementType;
isCandidate = EntityUtil.TryGetICollectionElementType(propertyValueType, out elementType);
}
else if (multiplicity == RelationshipMultiplicity.One || multiplicity == RelationshipMultiplicity.ZeroOrOne)
{
// This is an EntityReference property.
isCandidate = true;
}
}
return isCandidate;
}
/// <summary>
/// Method called by proxy interceptor delegate to provide lazy loading behavior for navigation properties.
/// </summary>
/// <typeparam name="TItem">property type</typeparam>
/// <param name="propertyValue">The property value whose associated relationship is to be loaded.</param>
/// <param name="relationshipName">String name of the relationship.</param>
/// <param name="targetRoleName">String name of the related end to be loaded for the relationship specified by <paramref name="relationshipName"/>.</param>
/// <param name="wrapperObject">Entity wrapper object used to retrieve RelationshipManager for the proxied entity.</param>
/// <returns>
/// True if the value instance was mutated and can be returned
/// False if the class should refetch the value because the instance has changed
/// </returns>
private static bool LoadProperty<TItem>(TItem propertyValue, string relationshipName, string targetRoleName, bool mustBeNull, object wrapperObject) where TItem : class
{
// Only attempt to load collection if:
//
// 1. Collection is non-null.
// 2. ObjectContext.ContextOptions.LazyLoadingEnabled is true
// 3. A non-null RelationshipManager can be retrieved (this is asserted).
// 4. The EntityCollection is not already loaded.
Debug.Assert(wrapperObject == null || wrapperObject is IEntityWrapper, "wrapperObject must be an IEntityWrapper");
IEntityWrapper wrapper = (IEntityWrapper)wrapperObject; // We want an exception if the cast fails.
if (wrapper != null && wrapper.Context != null)
{
RelationshipManager relationshipManager = wrapper.RelationshipManager;
Debug.Assert(relationshipManager != null, "relationshipManager should be non-null");
if (relationshipManager != null && (!mustBeNull || propertyValue == null))
{
RelatedEnd relatedEnd = relationshipManager.GetRelatedEndInternal(relationshipName, targetRoleName);
relatedEnd.DeferredLoad();
}
}
return propertyValue != null;
}
}
}

View File

@@ -0,0 +1,170 @@
//---------------------------------------------------------------------
// <copyright file="LightweightEntityWrapper.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
using System.Collections;
using System.Data.Objects.DataClasses;
using System.Diagnostics;
using System.Reflection;
using System.Data.Metadata.Edm;
namespace System.Data.Objects.Internal
{
/// <summary>
/// Implementation of IEntityWrapper for any entity that implements IEntityWithChangeTracker, IEntityWithRelationships,
/// and IEntityWithKey and is not a proxy. This is a lightweight wrapper that delegates functionality to those iterfaces.
/// This improves the speed and memory utilization for the standard code-gen cases in materialization.
/// </summary>
/// <typeparam name="TEntity">The type of entity wrapped</typeparam>
internal sealed class LightweightEntityWrapper<TEntity> : BaseEntityWrapper<TEntity>
where TEntity : IEntityWithRelationships, IEntityWithKey, IEntityWithChangeTracker
{
private readonly TEntity _entity;
/// <summary>
/// Constructs a wrapper for the given entity.
/// Note: use EntityWrapperFactory instead of calling this constructor directly.
/// </summary>
/// <param name="entity">The entity to wrap</param>
internal LightweightEntityWrapper(TEntity entity)
: base(entity, entity.RelationshipManager)
{
Debug.Assert(entity is IEntityWithChangeTracker, "LightweightEntityWrapper only works with entities that implement IEntityWithChangeTracker");
Debug.Assert(entity is IEntityWithRelationships, "LightweightEntityWrapper only works with entities that implement IEntityWithRelationships");
Debug.Assert(entity is IEntityWithKey, "LightweightEntityWrapper only works with entities that implement IEntityWithKey");
Debug.Assert(!EntityProxyFactory.IsProxyType(entity.GetType()), "LightweightEntityWrapper only works with entities that are not proxies");
_entity = entity;
}
/// <summary>
/// Constructs a wrapper as part of the materialization process. This constructor is only used
/// during materialization where it is known that the entity being wrapped is newly constructed.
/// This means that some checks are not performed that might be needed when thw wrapper is
/// created at other times, and information such as the identity type is passed in because
/// it is readily available in the materializer.
/// </summary>
/// <param name="entity">The entity to wrap</param>
/// <param name="key">The key for the entity</param>
/// <param name="entitySet">The entity set, or null if none is known</param>
/// <param name="context">The context to which the entity should be attached</param>
/// <param name="mergeOption">NoTracking for non-tracked entities, AppendOnly otherwise</param>
/// <param name="identityType">The type of the entity ignoring any possible proxy type</param>
internal LightweightEntityWrapper(TEntity entity, EntityKey key, EntitySet entitySet, ObjectContext context, MergeOption mergeOption, Type identityType)
: base(entity, entity.RelationshipManager, entitySet, context, mergeOption, identityType)
{
Debug.Assert(entity is IEntityWithChangeTracker, "LightweightEntityWrapper only works with entities that implement IEntityWithChangeTracker");
Debug.Assert(entity is IEntityWithRelationships, "LightweightEntityWrapper only works with entities that implement IEntityWithRelationships");
Debug.Assert(entity is IEntityWithKey, "LightweightEntityWrapper only works with entities that implement IEntityWithKey");
Debug.Assert(!EntityProxyFactory.IsProxyType(entity.GetType()), "LightweightEntityWrapper only works with entities that are not proxies");
_entity = entity;
_entity.EntityKey = key;
}
// See IEntityWrapper documentation
public override void SetChangeTracker(IEntityChangeTracker changeTracker)
{
_entity.SetChangeTracker(changeTracker);
}
// See IEntityWrapper documentation
public override void TakeSnapshot(EntityEntry entry)
{
}
// See IEntityWrapper documentation
public override void TakeSnapshotOfRelationships(EntityEntry entry)
{
}
// See IEntityWrapper documentation
public override EntityKey EntityKey
{
get
{
return _entity.EntityKey;
}
set
{
_entity.EntityKey = value;
}
}
public override bool OwnsRelationshipManager
{
get { return true; }
}
// See IEntityWrapper documentation
public override EntityKey GetEntityKeyFromEntity()
{
return _entity.EntityKey;
}
// See IEntityWrapper documentation
public override void CollectionAdd(RelatedEnd relatedEnd, object value)
{
}
// See IEntityWrapper documentation
public override bool CollectionRemove(RelatedEnd relatedEnd, object value)
{
return false;
}
// See IEntityWrapper documentation
public override void SetNavigationPropertyValue(RelatedEnd relatedEnd, object value)
{
}
// See IEntityWrapper documentation
public override void RemoveNavigationPropertyValue(RelatedEnd relatedEnd, object value)
{
}
// See IEntityWrapper documentation
public override void EnsureCollectionNotNull(RelatedEnd relatedEnd)
{
}
// See IEntityWrapper documentation
public override object GetNavigationPropertyValue(RelatedEnd relatedEnd)
{
return null;
}
// See IEntityWrapper documentation
public override object Entity
{
get { return _entity; }
}
// See IEntityWrapper<TEntity> documentation
public override TEntity TypedEntity
{
get { return _entity; }
}
// See IEntityWrapper documentation
public override void SetCurrentValue(EntityEntry entry, StateManagerMemberMetadata member, int ordinal, object target, object value)
{
member.SetValue(target, value);
}
// See IEntityWrapper documentation
public override void UpdateCurrentValueRecord(object value, EntityEntry entry)
{
// No extra work to do because we know that the entity is not a proxy and has a change tracker
entry.UpdateRecordWithoutSetModified(value, entry.CurrentValues);
}
// See IEntityWrapper documentation
public override bool RequiresRelationshipChangeTracking
{
get { return false; }
}
}
}

View File

@@ -0,0 +1,207 @@
//---------------------------------------------------------------------
// <copyright file="NullEntityWrapper.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupOwner [....]
//---------------------------------------------------------------------
namespace System.Data.Objects.Internal
{
using System.Data.Metadata.Edm;
using System.Data.Objects.DataClasses;
using System.Diagnostics;
/// <summary>
/// Defines an entity wrapper that wraps an entity with a null value.
/// This is a singleton class for which the same instance is always returned
/// any time a wrapper around a null entity is requested. Objects of this
/// type are immutable and mutable to allow this behavior to work correctly.
/// </summary>
internal class NullEntityWrapper : IEntityWrapper
{
private static IEntityWrapper s_nullWrapper = new NullEntityWrapper();
// Private constructor prevents anyone else from creating an instance
private NullEntityWrapper()
{
}
/// <summary>
/// The single instance of this class.
/// </summary>
internal static IEntityWrapper NullWrapper
{
get { return s_nullWrapper; }
}
public RelationshipManager RelationshipManager
{
get
{
Debug.Fail("Cannot access RelationshipManager from null wrapper.");
return null;
}
}
public bool OwnsRelationshipManager
{
get
{
Debug.Fail("Cannot access RelationshipManager from null wrapper.");
return false;
}
}
public object Entity
{
get { return null; }
}
public EntityEntry ObjectStateEntry
{
get { return null; }
set { }
}
public void CollectionAdd(RelatedEnd relatedEnd, object value)
{
Debug.Fail("Cannot modify collection from null wrapper.");
}
public bool CollectionRemove(RelatedEnd relatedEnd, object value)
{
Debug.Fail("Cannot modify collection from null wrapper.");
return false;
}
public EntityKey EntityKey
{
get
{
Debug.Fail("Cannot access EntityKey from null wrapper.");
return null;
}
set
{
Debug.Fail("Cannot access EntityKey from null wrapper.");
}
}
public EntityKey GetEntityKeyFromEntity()
{
Debug.Assert(false, "Method on NullEntityWrapper should not be called");
return null;
}
public ObjectContext Context
{
get
{
Debug.Fail("Cannot access Context from null wrapper.");
return null;
}
set
{
Debug.Fail("Cannot access Context from null wrapper.");
}
}
public MergeOption MergeOption
{
get
{
Debug.Fail("Cannot access MergeOption from null wrapper.");
return MergeOption.NoTracking;
}
}
public void AttachContext(ObjectContext context, EntitySet entitySet, MergeOption mergeOption)
{
Debug.Fail("Cannot access Context from null wrapper.");
}
public void ResetContext(ObjectContext context, EntitySet entitySet, MergeOption mergeOption)
{
Debug.Fail("Cannot access Context from null wrapper.");
}
public void DetachContext()
{
Debug.Fail("Cannot access Context from null wrapper.");
}
public void SetChangeTracker(IEntityChangeTracker changeTracker)
{
Debug.Fail("Cannot access ChangeTracker from null wrapper.");
}
public void TakeSnapshot(EntityEntry entry)
{
Debug.Fail("Cannot take snapshot of using null wrapper.");
}
public void TakeSnapshotOfRelationships(EntityEntry entry)
{
Debug.Fail("Cannot take snapshot using null wrapper.");
}
public Type IdentityType
{
get
{
Debug.Fail("Cannot access IdentityType from null wrapper.");
return null;
}
}
public void EnsureCollectionNotNull(RelatedEnd relatedEnd)
{
Debug.Fail("Cannot modify collection from null wrapper.");
}
public object GetNavigationPropertyValue(RelatedEnd relatedEnd)
{
Debug.Fail("Cannot access property using null wrapper.");
return null;
}
public void SetNavigationPropertyValue(RelatedEnd relatedEnd, object value)
{
Debug.Fail("Cannot access property using null wrapper.");
}
public void RemoveNavigationPropertyValue(RelatedEnd relatedEnd, object value)
{
Debug.Fail("Cannot access property using null wrapper.");
}
public void SetCurrentValue(EntityEntry entry, StateManagerMemberMetadata member, int ordinal, object target, object value)
{
Debug.Fail("Cannot set a value onto a null entity.");
}
public bool InitializingProxyRelatedEnds
{
get
{
Debug.Fail("Cannot access flag on null wrapper.");
return false;
}
set
{
Debug.Fail("Cannot access flag on null wrapper.");
}
}
public void UpdateCurrentValueRecord(object value, EntityEntry entry)
{
Debug.Fail("Cannot UpdateCurrentValueRecord on a null entity.");
}
public bool RequiresRelationshipChangeTracking
{
get { return false; }
}
}
}

View File

@@ -0,0 +1,263 @@
//---------------------------------------------------------------------
// <copyright file="ObjectFullSpanRewriter.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
// @backupowner [....]
//---------------------------------------------------------------------
namespace System.Data.Objects.Internal
{
using System.Collections.Generic;
using System.Data.Common.CommandTrees;
using System.Data.Common.CommandTrees.ExpressionBuilder;
using System.Data.Common.Utils;
using System.Data.Metadata.Edm;
using System.Diagnostics;
internal class ObjectFullSpanRewriter : ObjectSpanRewriter
{
/// <summary>
/// Represents a node in the 'Include' navigation property tree
/// built from the list of SpanPaths on the Span object with which
/// the FullSpanRewriter is constructed.
/// </summary>
private class SpanPathInfo
{
internal SpanPathInfo(EntityType declaringType)
{
this.DeclaringType = declaringType;
}
/// <summary>
/// The effective Entity type of this node in the tree
/// </summary>
internal EntityType DeclaringType;
/// <summary>
/// Describes the navigation properties that should be retrieved
/// from this node in the tree and the Include sub-paths that extend
/// from each of those navigation properties
/// </summary>
internal Dictionary<NavigationProperty, SpanPathInfo> Children;
}
/// <summary>
/// Maintains a reference to the SpanPathInfo tree node representing the
/// current position in the 'Include' path that is currently being expanded.
/// </summary>
private Stack<SpanPathInfo> _currentSpanPath = new Stack<SpanPathInfo>();
internal ObjectFullSpanRewriter(DbCommandTree tree, DbExpression toRewrite, Span span, AliasGenerator aliasGenerator)
: base(tree, toRewrite, aliasGenerator)
{
Debug.Assert(span != null, "Span cannot be null");
Debug.Assert(span.SpanList.Count > 0, "At least one span path is required");
// Retrieve the effective 'T' of the ObjectQuery<T> that produced
// the Command Tree that is being rewritten. This could be either
// literally 'T' or Collection<T>.
EntityType entityType = null;
if (!TryGetEntityType(this.Query.ResultType, out entityType))
{
// If the result type of the query is neither an Entity type nor a collection
// type with an Entity element type, then full Span is currently not allowed.
throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.ObjectQuery_Span_IncludeRequiresEntityOrEntityCollection);
}
// Construct the SpanPathInfo navigation property tree using the
// list of Include Span paths from the Span object:
// Create a SpanPathInfo instance that represents the root of the tree
// and takes its Entity type from the Entity type of the result type of the query.
SpanPathInfo spanRoot = new SpanPathInfo(entityType);
// Populate the tree of navigation properties based on the navigation property names
// in the Span paths from the Span object. Commonly rooted span paths are merged, so
// that paths of "Customer.Order" and "Customer.Address", for example, will share a
// common SpanPathInfo for "Customer" in the Children collection of the root SpanPathInfo,
// and that SpanPathInfo will contain one child for "Order" and another for "Address".
foreach (Span.SpanPath path in span.SpanList)
{
AddSpanPath(spanRoot, path.Navigations);
}
// The 'current' span path is initialized to the root of the Include span tree
_currentSpanPath.Push(spanRoot);
}
/// <summary>
/// Populates the Include span tree with appropriate branches for the Include path
/// represented by the specified list of navigation property names.
/// </summary>
/// <param name="parentInfo">The root SpanPathInfo</param>
/// <param name="navPropNames">A list of navigation property names that describes a single Include span path</param>
private void AddSpanPath(SpanPathInfo parentInfo, List<string> navPropNames)
{
ConvertSpanPath(parentInfo, navPropNames, 0);
}
private void ConvertSpanPath(SpanPathInfo parentInfo, List<string> navPropNames, int pos)
{
// Attempt to retrieve the next navigation property from the current entity type
// using the name of the current navigation property in the Include path.
NavigationProperty nextNavProp = null;
if (!parentInfo.DeclaringType.NavigationProperties.TryGetValue(navPropNames[pos], true, out nextNavProp))
{
// The navigation property name is not valid for this Entity type
throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.ObjectQuery_Span_NoNavProp(parentInfo.DeclaringType.FullName, navPropNames[pos]));
}
// The navigation property was retrieved, an entry for it must be ensured in the Children
// collection of the parent SpanPathInfo instance.
// If the parent's Children collection does not exist then instantiate it now:
if (null == parentInfo.Children)
{
parentInfo.Children = new Dictionary<NavigationProperty, SpanPathInfo>();
}
// If a sub-path that begins with the current navigation property name was already
// encountered, then a SpanPathInfo for this navigation property may already exist
// in the Children dictionary...
SpanPathInfo nextChild = null;
if (!parentInfo.Children.TryGetValue(nextNavProp, out nextChild))
{
// ... otherwise, create a new SpanPathInfo instance that this navigation
// property maps to and ensure its presence in the Children dictionary.
nextChild = new SpanPathInfo(EntityTypeFromResultType(nextNavProp));
parentInfo.Children[nextNavProp] = nextChild;
}
// If this navigation property is not the end of the span path then
// increment the position and recursively call ConvertSpanPath, specifying
// the (retrieved or newly-created) SpanPathInfo of this navigation property
// as the new 'parent' info.
if (pos < navPropNames.Count - 1)
{
ConvertSpanPath(nextChild, navPropNames, pos + 1);
}
}
/// <summary>
/// Retrieves the Entity (result or element) type produced by a Navigation Property.
/// </summary>
/// <param name="navProp">The navigation property</param>
/// <returns>
/// The Entity type produced by the navigation property.
/// This may be the immediate result type (if the result is at most one)
/// or the element type of the result type, otherwise.
/// </returns>
private static EntityType EntityTypeFromResultType(NavigationProperty navProp)
{
EntityType retType = null;
TryGetEntityType(navProp.TypeUsage, out retType);
// Currently, navigation properties may only return an Entity or Collection<Entity> result
Debug.Assert(retType != null, "Navigation property has non-Entity and non-Entity collection result type?");
return retType;
}
/// <summary>
/// Retrieves the Entity (result or element) type referenced by the specified TypeUsage, if
/// its EdmType is an Entity type or a collection type with an Entity element type.
/// </summary>
/// <param name="resultType">The TypeUsage that provides the EdmType to examine</param>
/// <param name="entityType">The referenced Entity (element) type, if present.</param>
/// <returns>
/// <c>true</c> if the specified <paramref name="resultType"/> is an Entity type or a
/// collection type with an Entity element type; otherwise <c>false</c>.
/// </returns>
private static bool TryGetEntityType(TypeUsage resultType, out EntityType entityType)
{
// If the result type is an Entity, then simply use that type.
if (BuiltInTypeKind.EntityType == resultType.EdmType.BuiltInTypeKind)
{
entityType = (EntityType)resultType.EdmType;
return true;
}
else if (BuiltInTypeKind.CollectionType == resultType.EdmType.BuiltInTypeKind)
{
// If the result type of the query is a collection, attempt to extract
// the element type of the collection and determine if it is an Entity type.
EdmType elementType = ((CollectionType)resultType.EdmType).TypeUsage.EdmType;
if (BuiltInTypeKind.EntityType == elementType.BuiltInTypeKind)
{
entityType = (EntityType)elementType;
return true;
}
}
entityType = null;
return false;
}
/// <summary>
/// Utility method to retrieve the 'To' AssociationEndMember of a NavigationProperty
/// </summary>
/// <param name="property">The navigation property</param>
/// <returns>The AssociationEndMember that is the target of the navigation operation represented by the NavigationProperty</returns>
private AssociationEndMember GetNavigationPropertyTargetEnd(NavigationProperty property)
{
AssociationType relationship = this.Metadata.GetItem<AssociationType>(property.RelationshipType.FullName, DataSpace.CSpace);
Debug.Assert(relationship.AssociationEndMembers.Contains(property.ToEndMember.Name), "Association does not declare member referenced by Navigation property?");
return relationship.AssociationEndMembers[property.ToEndMember.Name];
}
internal override SpanTrackingInfo CreateEntitySpanTrackingInfo(DbExpression expression, EntityType entityType)
{
SpanTrackingInfo tracking = new SpanTrackingInfo();
SpanPathInfo currentInfo = _currentSpanPath.Peek();
if (currentInfo.Children != null)
{
// The current SpanPathInfo instance on the top of the span path stack indicates
// which navigation properties should be retrieved from this Entity-typed expression
// and also specifies (in the form of child SpanPathInfo instances) which sub-paths
// must be expanded for each of those navigation properties.
// The SpanPathInfo instance may be the root instance or a SpanPathInfo that represents a sub-path.
int idx = 1; // SpanRoot is always the first (zeroth) column, full- and relationship-span columns follow.
foreach (KeyValuePair<NavigationProperty, SpanPathInfo> nextInfo in currentInfo.Children)
{
// If the tracking information was not initialized yet, do so now.
if (null == tracking.ColumnDefinitions)
{
tracking = InitializeTrackingInfo(this.RelationshipSpan);
}
// Create a property expression that retrieves the specified navigation property from the Entity-typed expression.
// Note that the expression is cloned since it may be used as the instance of multiple property expressions.
DbExpression columnDef = expression.Property(nextInfo.Key);
// Rewrite the result of the navigation property. This is required for two reasons:
// 1. To continue spanning the current Include path.
// 2. To apply relationship span to the Entity or EntityCollection produced by the navigation property, if necessary.
// Consider an Include path of "Order" for a query that returns OrderLines - the Include'd Orders should have
// their associated Customer relationship spanned.
// Note that this will recursively call this method with the Entity type of the result of the
// navigation property, which will in turn call loop through the sub-paths of this navigation
// property and adjust the stack to track which Include path is being expanded and which
// element of that path is considered 'current'.
_currentSpanPath.Push(nextInfo.Value);
columnDef = this.Rewrite(columnDef);
_currentSpanPath.Pop();
// Add a new column to the tracked columns using the rewritten column definition
tracking.ColumnDefinitions.Add(new KeyValuePair<string, DbExpression>(tracking.ColumnNames.Next(), columnDef));
AssociationEndMember targetEnd = GetNavigationPropertyTargetEnd(nextInfo.Key);
tracking.SpannedColumns[idx] = targetEnd;
// If full span and relationship span are both required, a relationship span may be rendered
// redundant by an already added full span. Therefore the association ends that have been expanded
// as part of full span are tracked using a dictionary.
if (this.RelationshipSpan)
{
tracking.FullSpannedEnds[targetEnd] = true;
}
idx++;
}
}
return tracking;
}
}
}

View File

@@ -0,0 +1,229 @@
//---------------------------------------------------------------------
// <copyright file="ObjectQueryExecutionPlan.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner [....]
//---------------------------------------------------------------------
namespace System.Data.Objects.Internal
{
using System;
using System.Data.Common;
using System.Data.Common.CommandTrees;
using System.Data.Common.Internal.Materialization;
using System.Data.Common.QueryCache;
using System.Data.Common.Utils;
using System.Data.EntityClient;
using System.Data.Metadata.Edm;
using System.Data.Objects;
using System.Diagnostics;
using CompiledQueryParameters = System.Collections.ObjectModel.ReadOnlyCollection<System.Collections.Generic.KeyValuePair<ObjectParameter, System.Data.Objects.ELinq.QueryParameterExpression>>;
/// <summary>
/// Represents the 'compiled' form of all elements (query + result assembly) required to execute a specific <see cref="ObjectQuery"/>
/// </summary>
internal sealed class ObjectQueryExecutionPlan
{
internal readonly DbCommandDefinition CommandDefinition;
internal readonly ShaperFactory ResultShaperFactory;
internal readonly TypeUsage ResultType;
internal readonly MergeOption MergeOption;
internal readonly CompiledQueryParameters CompiledQueryParameters;
/// <summary>If the query yields entities from a single entity set, the value is stored here.</summary>
private readonly EntitySet _singleEntitySet;
private ObjectQueryExecutionPlan(DbCommandDefinition commandDefinition, ShaperFactory resultShaperFactory, TypeUsage resultType, MergeOption mergeOption, EntitySet singleEntitySet, CompiledQueryParameters compiledQueryParameters)
{
Debug.Assert(commandDefinition != null, "A command definition is required");
Debug.Assert(resultShaperFactory != null, "A result shaper factory is required");
Debug.Assert(resultType != null, "A result type is required");
this.CommandDefinition = commandDefinition;
this.ResultShaperFactory = resultShaperFactory;
this.ResultType = resultType;
this.MergeOption = mergeOption;
this._singleEntitySet = singleEntitySet;
this.CompiledQueryParameters = compiledQueryParameters;
}
internal static ObjectQueryExecutionPlan Prepare(ObjectContext context, DbQueryCommandTree tree, Type elementType, MergeOption mergeOption, Span span, CompiledQueryParameters compiledQueryParameters, AliasGenerator aliasGenerator)
{
TypeUsage treeResultType = tree.Query.ResultType;
// Rewrite this tree for Span?
DbExpression spannedQuery = null;
SpanIndex spanInfo;
if (ObjectSpanRewriter.TryRewrite(tree, span, mergeOption, aliasGenerator, out spannedQuery, out spanInfo))
{
tree = DbQueryCommandTree.FromValidExpression(tree.MetadataWorkspace, tree.DataSpace, spannedQuery);
}
else
{
spanInfo = null;
}
DbConnection connection = context.Connection;
DbCommandDefinition definition = null;
// The connection is required to get to the CommandDefinition builder.
if (connection == null)
{
throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.ObjectQuery_InvalidConnection);
}
DbProviderServices services = DbProviderServices.GetProviderServices(connection);
try
{
definition = services.CreateCommandDefinition(tree);
}
catch (EntityCommandCompilationException)
{
// If we're running against EntityCommand, we probably already caught the providers'
// exception and wrapped it, we don't want to do that again, so we'll just rethrow
// here instead.
throw;
}
catch (Exception e)
{
// we should not be wrapping all exceptions
if (EntityUtil.IsCatchableExceptionType(e))
{
// we don't wan't folks to have to know all the various types of exceptions that can
// occur, so we just rethrow a CommandDefinitionException and make whatever we caught
// the inner exception of it.
throw EntityUtil.CommandCompilation(System.Data.Entity.Strings.EntityClient_CommandDefinitionPreparationFailed, e);
}
throw;
}
if (definition == null)
{
throw EntityUtil.ProviderDoesNotSupportCommandTrees();
}
EntityCommandDefinition entityDefinition = (EntityCommandDefinition)definition;
QueryCacheManager cacheManager = context.Perspective.MetadataWorkspace.GetQueryCacheManager();
ShaperFactory shaperFactory = ShaperFactory.Create(elementType, cacheManager, entityDefinition.CreateColumnMap(null),
context.MetadataWorkspace, spanInfo, mergeOption, false);
// attempt to determine entity information for this query (e.g. which entity type and which entity set)
//EntityType rootEntityType = null;
EntitySet singleEntitySet = null;
if (treeResultType.EdmType.BuiltInTypeKind == BuiltInTypeKind.CollectionType)
{
// determine if the entity set is unambiguous given the entity type
if (null != entityDefinition.EntitySets)
{
foreach (EntitySet entitySet in entityDefinition.EntitySets)
{
if (null != entitySet)
{
if (entitySet.ElementType.IsAssignableFrom(((CollectionType)treeResultType.EdmType).TypeUsage.EdmType))
{
if (singleEntitySet == null)
{
// found a single match
singleEntitySet = entitySet;
}
else
{
// there's more than one matching entity set
singleEntitySet = null;
break;
}
}
}
}
}
}
return new ObjectQueryExecutionPlan(definition, shaperFactory, treeResultType, mergeOption, singleEntitySet, compiledQueryParameters);
}
internal string ToTraceString()
{
string traceString = string.Empty;
EntityCommandDefinition entityCommandDef = this.CommandDefinition as EntityCommandDefinition;
if (entityCommandDef != null)
{
traceString = entityCommandDef.ToTraceString();
}
return traceString;
}
internal ObjectResult<TResultType> Execute<TResultType>(ObjectContext context, ObjectParameterCollection parameterValues)
{
DbDataReader storeReader = null;
try
{
// create entity command (just do this to snarf store command)
EntityCommandDefinition commandDefinition = (EntityCommandDefinition)this.CommandDefinition;
EntityCommand entityCommand = new EntityCommand((EntityConnection)context.Connection, commandDefinition);
// pass through parameters and timeout values
if (context.CommandTimeout.HasValue)
{
entityCommand.CommandTimeout = context.CommandTimeout.Value;
}
if (parameterValues != null)
{
foreach (ObjectParameter parameter in parameterValues)
{
int index = entityCommand.Parameters.IndexOf(parameter.Name);
if (index != -1)
{
entityCommand.Parameters[index].Value = parameter.Value ?? DBNull.Value;
}
}
}
// acquire store reader
storeReader = commandDefinition.ExecuteStoreCommands(entityCommand, CommandBehavior.Default);
ShaperFactory<TResultType> shaperFactory = (ShaperFactory<TResultType>)this.ResultShaperFactory;
Shaper<TResultType> shaper = shaperFactory.Create(storeReader, context, context.MetadataWorkspace, this.MergeOption, true);
// create materializer delegate
TypeUsage resultItemEdmType;
if (ResultType.EdmType.BuiltInTypeKind == BuiltInTypeKind.CollectionType)
{
resultItemEdmType = ((CollectionType)ResultType.EdmType).TypeUsage;
}
else
{
resultItemEdmType = ResultType;
}
return new ObjectResult<TResultType>(shaper, this._singleEntitySet, resultItemEdmType);
}
catch (Exception)
{
if (null != storeReader)
{
// Note: The caller is responsible for disposing reader if creating
// the enumerator fails.
storeReader.Dispose();
}
throw;
}
}
internal static ObjectResult<TResultType> ExecuteCommandTree<TResultType>(ObjectContext context, DbQueryCommandTree query, MergeOption mergeOption)
{
Debug.Assert(context != null, "ObjectContext cannot be null");
Debug.Assert(query != null, "Command tree cannot be null");
ObjectQueryExecutionPlan execPlan = ObjectQueryExecutionPlan.Prepare(context, query, typeof(TResultType), mergeOption, null, null, System.Data.Common.CommandTrees.ExpressionBuilder.DbExpressionBuilder.AliasGenerator);
return execPlan.Execute<TResultType>(context, null);
}
}
}

Some files were not shown because too many files have changed in this diff Show More