这是我的场景。我们有一个 Customers 表,具有各种导航属性(1-1、1-many、many-many 等)来表示 Orders,其中包含 OrderItems 等。(阅读非常深入的对象图)
非常简单的例子: Customer1 有 Orders[1..5] 而 Customer2 有 Orders[6..9]
现在有一段时间我们发现这两个客户实际上是同一个客户,我们想合并这两个客户:Customer1.Merge(Customer2);
合并后,我们应该看到Customer1 具有 Orders[1..9] 以及 Customer2 的所有其他属性,并且 Customer2 为空并且可以安全地删除。
现在我遇到了一篇关于深度克隆的相当老的帖子(http://www.urmanet.ch/?p=11),它产生了一个新实体,但我有两个现有实体需要减少到一个现有实体添加了第二个实体的对象图,然后我可以在其中删除第二个实体并提交工作单元。 这个例子起源于 2008 年,并且现在有新版本的 EF,我想知道实现这一目标的正确方法是什么?
我喜欢一个扩展方法的想法,它可以通用地执行此任务(意味着反射),这样当/如果实体模型发生变化(添加新属性/关系等),这个通用的 Merge 方法就不需要改变......
这是我开始的基本代码,我想对其进行修改,以便它可以合并两个实体,而不是创建一个新实体(克隆)。
/// This class is used to store self references for
/// back tracking
public class SelfReferencesTracking
{
public string EntitySetName;
public EntityObject NewEntityObject;
public EntityKey OriginalKeys;
}
/// Extension method class for the EntityObject class
public static class EntityObjectExtension
{
//Enable tracking
private static readonly List<SelfReferencesTracking> Tracking =
new List<SelfReferencesTracking>();
/// These method makes a 1:1 copy of the original entity object
///
/// The original entity object /// The copied entity object
public static EntityObject Clone(this EntityObject entityObject)
{
//Get constructor for new object
object newEntityObject = entityObject.GetType().GetConstructor(new Type[0]).Invoke(new object[0]);
Tracking.Add(new SelfReferencesTracking
{
EntitySetName = entityObject.EntityKey.EntitySetName,
OriginalKeys = entityObject.EntityKey,
NewEntityObject = (EntityObject) newEntityObject
});
//Copy all properties and its values of the given type
PropertyInfo[] properties = entityObject.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
try
{
object propertyValue = property.GetValue(entityObject, null);
PropertyInfo myProperty = property;
if (!entityObject.EntityKey.EntityKeyValues.Any(x => x.Key == myProperty.Name))
{
//Ignore all properties of these types
if (property.PropertyType != typeof (EntityKey) &&
property.PropertyType != typeof (EntityState) &&
property.PropertyType != typeof (EntityReference<>))
{
//Check, if the property is a complex type (collection), in that
//case, some special calls are necessary
if (
property.GetCustomAttributes(typeof (EdmRelationshipNavigationPropertyAttribute), false)
.Count() == 1)
{
//Check for self referencing entities
if (propertyValue.GetType() == entityObject.GetType())
{
//Get the self referenced entity object
var selfRefrencedEntityObject = (EntityObject) property.GetValue(entityObject, null);
//This variable is used to store the new parent entity objects
EntityObject newParentEntityObject = null;
//This loops might be replaced by LINQ queries... I didn't try that
foreach (
SelfReferencesTracking tracking in
Tracking.Where(
x =>
x.EntitySetName == selfRefrencedEntityObject.EntityKey.EntitySetName))
{
//Check, if the key is in the tracking list
foreach (
EntityKeyMember newKeyValues in
selfRefrencedEntityObject.EntityKey.EntityKeyValues)
{
//Iterate trough the keys and values
foreach (
EntityKeyMember orgKeyValues in tracking.OriginalKeys.EntityKeyValues)
{
//The key is stored in the tracking list, which means, this is
//the foreign key used by the self referencing property
if (newParentEntityObject == null)
{
if (orgKeyValues.Key == newKeyValues.Key &&
orgKeyValues.Value == newKeyValues.Value)
{
//Store the parent entity object
newParentEntityObject = tracking.NewEntityObject;
}
}
else
{
break;
}
}
}
}
//Set the value to the new parent entity object
property.SetValue(newEntityObject, newParentEntityObject, null);
}
else
{
//Entity collections are always generic
if (propertyValue.GetType().IsGenericType)
{
//Don't include self references collection, e.g. Orders1, Orders2 etc.
//Check for equality of the types (string comparison)
if (
!propertyValue.GetType()
.GetGenericArguments()
.First()
.FullName.Equals(entityObject.GetType().FullName))
{
//Get the entities of the given property
var entities = (RelatedEnd) property.GetValue(entityObject, null);
//Load underlying collection, if not yet done...
if (!entities.IsLoaded) entities.Load();
//Create a generic instance of the entities collection object
Type t =
typeof (EntityCollection<>).MakeGenericType(new[]
{property.PropertyType.GetGenericArguments()[0]});
object newEntityCollection = Activator.CreateInstance(t);
//Iterate trough the entities collection
foreach (object entity in entities)
{
//Add the found entity to the dynamic generic collection
MethodInfo addToCollection =
newEntityCollection.GetType().GetMethod("Add");
addToCollection.Invoke(newEntityCollection,
new object[] {Clone((EntityObject) entity)});
}
//Set the property value
property.SetValue(newEntityObject, newEntityCollection, null);
}
}
}
}
else
{
var baseType = propertyValue.GetType().BaseType;
if (baseType != null && baseType.Name == "EntityReference")
{
//Simply copy the EntityKey to the new entity object’s EntityReference
((EntityReference) property.GetValue(newEntityObject, null)).EntityKey =
((EntityReference) property.GetValue(entityObject, null)).EntityKey;
}
else
{
//Common task, just copy the simple type property into the new entity object
property.SetValue(
newEntityObject,
property.GetValue(entityObject, null), null);
}
}
}
}
}
catch (InvalidCastException ie)
{
//Hmm, something happend...
Debug.WriteLine(ie.ToString());
continue;
}
catch (Exception ex)
{
//Hmm, something happend...
Debug.WriteLine(ex.ToString());
continue;
}
}
return (EntityObject) newEntityObject;
}
}
回顾一下:在将这个 Clone 扩展方法转换为 Merge 扩展方法时,我需要一些帮助,我希望专家可以介入并帮助完成图片。任何帮助是极大的赞赏!