//--------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // // @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 { /// /// Factory class for creating IEntityWrapper instances. /// internal static class EntityWrapperFactory { // A cache of functions used to create IEntityWrapper instances for a given type private static readonly Memoizer> _delegateCache = new Memoizer>(CreateWrapperDelegate, null); /// /// The single instance of the NullEntityWrapper. /// internal static IEntityWrapper NullWrapper { get { return NullEntityWrapper.NullWrapper; } } /// /// 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. /// /// The entity for which a wrapper will be created /// The key associated with that entity, or null /// The new wrapper instance 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 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)createDelegate.Invoke(null, new object[0]); } // Returns a delegate that creates the fast LightweightEntityWrapper private static Func CreateWrapperDelegateTypedLightweight() where TEntity : IEntityWithRelationships, IEntityWithKey, IEntityWithChangeTracker { return (entity) => new LightweightEntityWrapper((TEntity)entity); } // Returns a delegate that creates a strategy-based wrapper for entities that implement IEntityWithRelationships private static Func CreateWrapperDelegateTypedWithRelationships() where TEntity : IEntityWithRelationships { Func propertyAccessorStrategy; Func keyStrategy; Func changeTrackingStrategy; CreateStrategies(out propertyAccessorStrategy, out changeTrackingStrategy, out keyStrategy); return (entity) => new EntityWrapperWithRelationships((TEntity)entity, propertyAccessorStrategy, changeTrackingStrategy, keyStrategy); } // Returns a delegate that creates a strategy-based wrapper for entities that do not implement IEntityWithRelationships private static Func CreateWrapperDelegateTypedWithoutRelationships() { Func propertyAccessorStrategy; Func keyStrategy; Func changeTrackingStrategy; CreateStrategies(out propertyAccessorStrategy, out changeTrackingStrategy, out keyStrategy); return (entity) => new EntityWrapperWithoutRelationships((TEntity)entity, propertyAccessorStrategy, changeTrackingStrategy, keyStrategy); } // Creates delegates that create strategy objects appropriate for the type of entity. private static void CreateStrategies(out Func createPropertyAccessorStrategy, out Func createChangeTrackingStrategy, out Func 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(); } } /// /// Convenience function that gets the ObjectStateManager from the context and calls /// WrapEntityUsingStateManager. /// /// the entity to wrap /// the context in which the entity may exist, or null /// a new or existing wrapper internal static IEntityWrapper WrapEntityUsingContext(object entity, ObjectContext context) { EntityEntry existingEntry; return WrapEntityUsingStateManagerGettingEntry(entity, context == null ? null : context.ObjectStateManager, out existingEntry); } /// /// Convenience function that gets the ObjectStateManager from the context and calls /// WrapEntityUsingStateManager. /// /// The entity to wrap /// The context in which the entity may exist, or null /// Set to the existing state entry if one is found, else null /// a new or existing wrapper internal static IEntityWrapper WrapEntityUsingContextGettingEntry(object entity, ObjectContext context, out EntityEntry existingEntry) { return WrapEntityUsingStateManagerGettingEntry(entity, context == null ? null : context.ObjectStateManager, out existingEntry); } /// /// 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. /// /// the entity to wrap /// the state manager in which the entity may exist, or null /// a new or existing wrapper internal static IEntityWrapper WrapEntityUsingStateManager(object entity, ObjectStateManager stateManager) { EntityEntry existingEntry; return WrapEntityUsingStateManagerGettingEntry(entity, stateManager, out existingEntry); } /// /// 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. /// /// The entity to wrap /// The state manager in which the entity may exist, or null /// The existing state entry for the given entity if one exists, otherwise null /// A new or existing wrapper 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; } /// /// 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. /// /// The wrapped entity /// The context that will be using this wrapper /// The entity set this wrapped entity belongs to 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); } } /// /// Returns a func that will create a PocoPropertyAccessorStrategy object for a given entity. /// /// The func to be used to create the strategy object. internal static Func GetPocoPropertyAccessorStrategyFunc() { return (object entity) => new PocoPropertyAccessorStrategy(entity); } /// /// Returns a func that will create a null IPropertyAccessorStrategy strategy object for a given entity. /// /// The func to be used to create the strategy object. internal static Func GetNullPropertyAccessorStrategyFunc() { return (object entity) => null; } /// /// Returns a func that will create a EntityWithChangeTrackerStrategy object for a given entity. /// /// The func to be used to create the strategy object. internal static Func GetEntityWithChangeTrackerStrategyFunc() { return (object entity) => new EntityWithChangeTrackerStrategy((IEntityWithChangeTracker)entity); } /// /// Returns a func that will create a SnapshotChangeTrackingStrategy object for a given entity. /// /// The func to be used to create the strategy object. internal static Func GetSnapshotChangeTrackingStrategyFunc() { return (object entity) => SnapshotChangeTrackingStrategy.Instance; } /// /// Returns a func that will create a EntityWithKeyStrategy object for a given entity. /// /// The func to be used to create the strategy object. internal static Func GetEntityWithKeyStrategyStrategyFunc() { return (object entity) => new EntityWithKeyStrategy((IEntityWithKey)entity); } /// /// Returns a func that will create a GetPocoEntityKeyStrategyFunc object for a given entity. /// /// The func to be used to create the strategy object. internal static Func GetPocoEntityKeyStrategyFunc() { return (object entity) => new PocoEntityKeyStrategy(); } } }