364 lines
18 KiB
C#
364 lines
18 KiB
C#
//---------------------------------------------------------------------
|
|
// <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();
|
|
}
|
|
}
|
|
}
|