3

我在那里,我在一个WCF服务中使用实体框架的场景,并在经由代码优先(非平凡映射到数据库的类型的非跟踪的情况下改变发生更新和删除整个实例的对象树)。当我尝试将未被跟踪的实例附加到上下文中时,EF仅识别对根对象上的简单值类型的更改。实体框架游离对象合并

有谁知道这个场景优雅的解决方案?我在寻找一种方式用通用的存储库,并避免其通过实例的整个对象树管理每个对象的“安装/拆卸”状态运行做到这一点。我曾考虑可能使用ValueInjecter或AutoMapper上运行一个完全水化的变化,为了跟踪“老”状态的实例上下文以皮卡的变化。另外,Nhibernate将如何处理这种情况?

预先感谢您的输入!

UPDATE(7/31/2012):我更新了处理通用键的代码,以及EF Proxies的一些键入问题。处理IEntity类型时还添加了一些辅助扩展。这种实现并不完美,但功能非常强大。

UPDATE(3/13/2012):我已经在EF中添加了一个更清晰的合并功能请求。请求位于:http://data.uservoice.com/forums/72025-ado-net-entity-framework-ef-feature-suggestions/suggestions/2679160-better-merging-change-tracking

UPDATE(3/12/2012):我在下面发布了我的解决方案。它采用FubuCoreValueInjecter,并需要实体被打上两个一个接口,无论是IEntity,或IRecursiveEntity递归类。该解决方案将处理递归的自连接实体。

此外,我引用一个通用的存储库(库),让我去的IDbSet的引用EF暴露。这可以替换为任何其他通用或特定的存储库。最后,IEntity接口使用int?身份证,但是你可以定义,但是你想(Guid/Guid?)。这个解决方案本身并不像我想的那么优雅,但是它在物理WCF服务边界后面允许更优雅的数据访问代码。

public class DomainMergeInjection : ConventionInjection 
{ 
    private readonly Repository _repository; 
    private readonly Dictionary<string, object> _potentialParentObjectDump; 
    private readonly Cache<Type, Type> _entityTypesAndKeysCache; 

    public DomainMergeInjection(Repository repository) 
    { 
     _repository = repository; 
     _potentialParentObjectDump = new Dictionary<string, object>(); 
     _entityTypesAndKeysCache = new Cache<Type, Type>(); 
    } 

    protected override bool Match(ConventionInfo c) 
    { 
     return c.SourceProp.Name == c.TargetProp.Name; 
    } 

    protected override object SetValue(ConventionInfo c) 
    { 
     if(c.SourceProp.Value == null) 
      return null; 

     //for value types and string just return the value as is 
     if(c.SourceProp.Type.IsSimple()) 
      return c.SourceProp.Value; 

     //TODO: Expand on this to handle IList/IEnumerable (i.e. the non-generic collections and arrays). 
     //handle arrays 
     if(c.SourceProp.Type.IsArray) 
     { 
      var sourceArray = c.SourceProp.Value as Array; 
      // ReSharper disable PossibleNullReferenceException 
      var clonedArray = sourceArray.Clone() as Array; 
      // ReSharper restore PossibleNullReferenceException 

      for(int index = 0; index < sourceArray.Length; index++) 
      { 
       var sourceValueAtIndex = sourceArray.GetValue(index); 

       //Skip null and simple values that would have already been moved in the clone. 
       if(sourceValueAtIndex == null || sourceValueAtIndex.GetType().IsSimple()) 
        continue; 

       // ReSharper disable PossibleNullReferenceException 
       clonedArray.SetValue(RetrieveComplexSourceValue(sourceValueAtIndex), index); 
       // ReSharper restore PossibleNullReferenceException 
      } 

      return clonedArray; 
     } 

     //handle IEnumerable<> also ICollection<> IList<> List<> 
     if(c.SourceProp.Type.IsGenericEnumerable()) 
     { 
      var t = c.SourceProp.Type.GetGenericArguments()[0]; 

      if(t.IsSimple()) 
       return c.SourceProp.Value; 

      var tlist = typeof(List<>).MakeGenericType(t); 
      dynamic list = Activator.CreateInstance(tlist); 

      var addMethod = tlist.GetMethod("Add"); 

      foreach(var sourceItem in (IEnumerable)c.SourceProp.Value) 
      { 
       addMethod.Invoke(list, new[] { RetrieveComplexSourceValue(sourceItem) }); 
      } 

      return list; 
     } 

     //Get a source value that is in the right state and is tracked if needed. 
     var itemStateToInject = RetrieveComplexSourceValue(c.SourceProp.Value); 
     return itemStateToInject; 
    } 

    private object RetrieveComplexSourceValue(object source) 
    { 
     //If the source is a non-tracked type, or the source is a new value, then return its value. 
     if(!source.ImplementsIEntity(_entityTypesAndKeysCache) || source.IsEntityIdNull(_entityTypesAndKeysCache)) 
      return source; 

     object sourceItemFromContext; 

     //Handle recursive entities, this could probably be cleaned up. 
     if(source.ImplementsIRecursiveEntity()) 
     { 
      var itemKey = source.GetEntityIdString(_entityTypesAndKeysCache) + " " + ObjectContext.GetObjectType(source.GetType()); 

      //If we have a context item for this key already, just return it. This solves a recursion problem with self-linking items. 
      if(_potentialParentObjectDump.ContainsKey(itemKey)) 
       return _potentialParentObjectDump[itemKey]; 

      //Get the source from the context to ensure it is tracked. 
      sourceItemFromContext = GetSourceItemFromContext(source); 

      //Add the class into the object dump in order to avoid any infinite recursion issues with self-linked objects 
      _potentialParentObjectDump.Add(itemKey, sourceItemFromContext); 
     } 
     else 
      //Get the source from the context to ensure it is tracked. 
      sourceItemFromContext = GetSourceItemFromContext(source); 

     //Recursively use this injection class instance to inject the source state on to the context source state. 
     var itemStateToInject = sourceItemFromContext.InjectFrom(this, source); 

     return itemStateToInject; 
    } 

    private object GetSourceItemFromContext(object source) 
    { 
     if(source == null) 
      return null; 

     //Using dynamic here to "AutoCast" to an IEntity<>. We should have one, but it's important to note just in case. 
     dynamic sourceEntityValue = source; 
     var sourceEntityType = ObjectContext.GetObjectType(source.GetType()); 
     var sourceKeyType = sourceEntityType.GetEntityKeyType(); 

     var method = typeof(DomainMergeInjection).GetMethod("GetFromContext", BindingFlags.Instance | BindingFlags.NonPublic); 
     var generic = method.MakeGenericMethod(sourceEntityType, sourceKeyType); 

     var sourceItemFromContext = generic.Invoke(this, new object[] { new object[] { sourceEntityValue.Id } }); 
     return sourceItemFromContext; 
    } 

    // ReSharper disable UnusedMember.Local 
    private TItem GetFromContext<TItem, TKey>(object[] keys) where TItem : class, IEntity<TKey> 
    // ReSharper restore UnusedMember.Local 
    { 
     var foundItem = _repository.GetDbSet<TItem>().Find(keys); 

     return foundItem; 
    } 
} 

public static class EntityTypeExtensions 
{ 
    /// <summary> 
    /// Determines if an object instance implements IEntity. 
    /// </summary> 
    /// <param name="entity"></param> 
    /// <param name="entityCache">A cache to hold types that do implement IEntity. If the cache does not have the Type and the Type does implement IEntity, it will add the type to the cache along with the </param> 
    /// <returns></returns> 
    public static bool ImplementsIEntity(this object entity, Cache<Type, Type> entityCache = null) 
    { 
     //We need to handle getting the proxy type if this is an EF Code-First proxy. 
     //Please see for more info: http://msdn.microsoft.com/en-us/library/dd456853.aspx 
     var entityType = ObjectContext.GetObjectType(entity.GetType()); 

     if(entityCache != null && entityCache.Has(entityType)) 
      return true; 

     var implementationOfIEntity = entityType.GetInterfaces().FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof (IEntity<>)); 

     if(implementationOfIEntity == null) 
      return false; 

     if(entityCache != null) 
     { 
      var keyType = implementationOfIEntity.GetGenericArguments()[0]; 
      entityCache.Fill(entityType, keyType); 
     } 

     return true; 
    } 

    /// <summary> 
    /// Determines if an object instances implements IRecurisveEntity 
    /// </summary> 
    /// <param name="entity"></param> 
    /// <returns></returns> 
    public static bool ImplementsIRecursiveEntity(this object entity) 
    { 
     //We need to handle getting the proxy type if this is an EF Code-First proxy. 
     //Please see for more info: http://msdn.microsoft.com/en-us/library/dd456853.aspx 
     var entityType = ObjectContext.GetObjectType(entity.GetType()); 

     var implementsIRecursiveEntity = entityType.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IRecursiveEntity<>)); 

     return implementsIRecursiveEntity; 
    } 

    /// <summary> 
    /// Determines whether or not an Entity's Id is null. Will throw an exception if a type that does not implement IEntity is passed through. 
    /// </summary> 
    /// <param name="entity"></param> 
    /// <param name="entityCache"></param> 
    /// <returns></returns> 
    public static bool IsEntityIdNull(this object entity, Cache<Type, Type> entityCache = null) 
    { 
     bool isEntityIdNull = ExecuteEntityIdMethod<bool>("IsEntityIdNull", entity, entityCache); 

     return isEntityIdNull; 
    } 

    /// <summary> 
    /// Determines whether or not an Entity's Id is null. Will throw an exception if a type that does not implement IEntity is passed through. 
    /// </summary> 
    /// <param name="entity"></param> 
    /// <param name="entityCache"></param> 
    /// <returns></returns> 
    public static string GetEntityIdString(this object entity, Cache<Type, Type> entityCache = null) 
    { 
     string entityIdString = ExecuteEntityIdMethod<string>("GetEntityIdString", entity, entityCache); 

     return entityIdString; 
    } 

    private static T ExecuteEntityIdMethod<T>(string methodName, object entityInstance, Cache<Type, Type> entityCache = null) 
    { 
     if(!entityInstance.ImplementsIEntity(entityCache)) 
      throw new ArgumentException(string.Format("Parameter entity of type {0} does not implement IEntity<>, and so ist not executable for {1}!", entityInstance.GetType(), methodName)); 

     //We need to handle getting the proxy type if this is an EF Code-First proxy. 
     //Please see for more info: http://msdn.microsoft.com/en-us/library/dd456853.aspx 
     var entityType = ObjectContext.GetObjectType(entityInstance.GetType()); 
     var keyType = entityCache != null ? entityCache[entityType] : entityType.GetEntityKeyType(); 

     var method = typeof(EntityTypeExtensions).GetMethod(methodName, BindingFlags.Static | BindingFlags.NonPublic); 
     var generic = method.MakeGenericMethod(keyType); 

     T returnValue = (T)generic.Invoke(null, new[] { entityInstance }); 

     return returnValue; 
    } 

    private static string GetEntityIdString<TKey>(IEntity<TKey> entity) 
    { 
     var entityIdString = entity.Id.ToString(); 

     return entityIdString; 
    } 

    private static bool IsEntityIdNull<TKey>(IEntity<TKey> entity) 
    { 
     //We need to handle getting the proxy type if this is an EF Code-First proxy. 
     //Please see for more info: http://msdn.microsoft.com/en-us/library/dd456853.aspx 
     var entityType = ObjectContext.GetObjectType(entity.GetType()); 

     if(entityType.IsPrimitive) 
      return false; 

     //NOTE: We know that this entity's type is NOT primitive, therefore we can cleanly test for null, and return properly. 
     // ReSharper disable CompareNonConstrainedGenericWithNull 
     var entityIdIsNull = entity.Id == null; 
     // ReSharper restore CompareNonConstrainedGenericWithNull 

     return entityIdIsNull; 
    } 

    public static Type GetEntityKeyType(this Type typeImplementingIEntity) 
    { 
     var implementationOfIEntity = typeImplementingIEntity.GetInterfaces().FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEntity<>)); 

     if(implementationOfIEntity == null) 
      throw new ArgumentException(string.Format("Type {0} does not implement IEntity<>", typeImplementingIEntity)); 

     var keyType = implementationOfIEntity.GetGenericArguments()[0]; 
     return keyType; 
    } 
} 

public interface IEntity<TKey> 
{ 
    TKey Id { get; set; } 
} 


public interface IRecursiveEntity<TKey> : IEntity<TKey> 
{ 
    IRecursiveEntity<TKey> Parent { get; } 
    IEnumerable<IRecursiveEntity<TKey>> Children { get; } 
} 

回答

2

您可以使用分离对象只能作为DTO,

和补充之后,从上下文从DTO

与ValueInjecter值的对象,这将是:

//manually 
conObj.InjectFrom(dto); 
conObj.RefTypeProp.InjectFrom(dto.RefTypeProp); 
... 

//or by writing a custom injection: 
conObj.InjectFrom<ApplyChangesInjection>(dto); 

这里的注射会自动完成,(我通过修改VI的主页中的DeepClone注入来实现)

这里的诀窍是,注射使用本身在SetValue方法

public class ApplyChangesInjection : ConventionInjection 
{ 
    protected override bool Match(ConventionInfo c) 
    { 
     return c.SourceProp.Name == c.TargetProp.Name; 
    } 

    protected override object SetValue(ConventionInfo c) 
    { 
     if (c.SourceProp.Value == null) return null; 

     //for value types and string just return the value as is 
     if (c.SourceProp.Type.IsValueType || c.SourceProp.Type == typeof(string)) 
      return c.SourceProp.Value; 

     //handle arrays - not impl 
     //handle IEnumerable<> also ICollection<> IList<> List<> - not impl 

     //for simple object types apply the inject using the corresponding source 

     return c.TargetProp.Value 
      .InjectFrom<ApplyChangesInjection>(c.SourceProp.Value); 
    } 
} 

//注:我没有处理的集合在此注射,我只是想让你明白一个道理, 你可以看看原始的http://valueinjecter.codeplex.com/wikipage?title=Deep%20Cloning&referringTitle=Home

+0

所以基本上采取的DTO,然后“ApplyChanges”的DTO和它的所有子对象使用上面的代码跟踪对象?另外,是否需要为每个复杂属性和集合手动完成RefTypeProp注入?假设它,它可能不适用于我的方案,因为我想自动执行整个过程,所以代码只需要是_myRepository.Save(myEntityObject)。 – jdscolam 2012-03-09 20:41:57

+0

有可能编写一个自动执行此操作的注入,它可能类似于VI主页中的CloneInjection,除非您不需要创建新对象 – Omu 2012-03-09 22:50:28

+0

我能够找出一种使用VI实现的方法来实现我认为这是一个合理的合并,然而它最终需要使用反思。 – jdscolam 2012-03-12 22:57:18