gpt4 book ai didi

entity-framework - Entity Framework 分离对象合并

转载 作者:行者123 更新时间:2023-12-04 07:24:50 25 4
gpt4 key购买 nike

我有一个场景,我在 WCF 服务中使用 Entity Framework,并且在通过代码优先映射回数据库的类型的非跟踪实例上发生更改(在整个过程中进行非平凡的更新和删除)实例的对象树)。当我尝试将非跟踪实例附加到上下文中时,EF 仅识别对根对象上的简单值类型的更改。

有人知道这种情况的优雅解决方案吗?我正在寻找一种通过使用通用存储库来执行此操作的方法,并避免必须遍历实例的整个对象树来管理每个对象的“附加/分离”状态。我考虑过可能使用 ValueInjecter 或 AutoMapper 在“旧”状态的完全水化和跟踪实例上运行更改,以便上下文获取更改。另外,Nhibernate 将如何处理这种情况?

预先感谢您的输入!

更新(2012 年 7 月 31 日):我更新了代码以处理通用类型的 key ,以及 EF 代理的一些类型问题。在处理 IEntity 类型时还添加了一些辅助扩展。此实现并不完美,但非常实用。

更新(2012 年 3 月 13 日):我添加了一个功能请求,以便在 EF 中进行更清晰的合并。请求位于此处:http://data.uservoice.com/forums/72025-ado-net-entity-framework-ef-feature-suggestions/suggestions/2679160-better-merging-change-tracking

更新(2012 年 3 月 12 日):我已经在下面发布了我的解决方案。它使用 FubuCoreValueInjecter,并且要求实体标记为两个接口(interface)之一IEntity,或IRecursiveEntity 用于递归类。该解决方案将处理递归的、自链接的实体。

此外,我正在引用一个通用存储库 (Repository),它允许我获取对 EF 公开的 IDbSet 的引用。这可以用任何其他通用或特定存储库代替。最后,IEntity 接口(interface)使用 int? id,但是您可以根据需要定义它(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; }
}

最佳答案

您只能将分离的对象用作 DTO,

并在使用 DTO 中的值重新填充上下文中的对象之后

对于 ValueInjecter 这将是:

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

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

这是自动执行此操作的注入(inject),(我通过修改 VI 主页上的 DeepClone Injection 来做到这一点)

这里的技巧是 Injection 在 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);
}
}

//注意:我这次注入(inject)不是处理集合,只是想让你明白其中的原理,可以看原文http://valueinjecter.codeplex.com/wikipage?title=Deep%20Cloning&referringTitle=Home

关于entity-framework - Entity Framework 分离对象合并,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9636249/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com