You've already forked linux-packaging-mono
Imported Upstream version 4.6.0.125
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
This commit is contained in:
parent
a569aebcfd
commit
e79aa3c0ed
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
//---------------------------------------------------------------------
|
||||
// <copyright file="IEntityWrapper.cs" company="Microsoft">
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// </copyright>
|
||||
//
|
||||
// @owner avickers
|
||||
// @backupOwner jeffders
|
||||
//---------------------------------------------------------------------
|
||||
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 assoicated 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; }
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<T>.
|
||||
/// </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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user