更新:
我找到了一种不需要将导航属性从子实体添加到父实体或设置复杂键的方法。
它基于这篇文章,它使用ObjectStateManager
来查找已删除的实体。
手头有一个列表ObjectStateEntry
,我们可以EntityKey
从每个中找到一对,代表被删除的关系。
在这一点上,我找不到任何迹象表明必须删除哪个。与文章的示例相反,在子级具有返回父级的导航属性的情况下,简单地选择第二个会删除父级。因此,为了解决这个问题,我跟踪应该使用 class 处理哪些类型OrphansToHandle
。
该模型:
public class ParentObject
{
public int Id { get; set; }
public virtual ICollection<ChildObject> ChildObjects { get; set; }
public ParentObject()
{
ChildObjects = new List<ChildObject>();
}
}
public class ChildObject
{
public int Id { get; set; }
}
其他类:
public class MyContext : DbContext
{
private readonly OrphansToHandle OrphansToHandle;
public DbSet<ParentObject> ParentObject { get; set; }
public MyContext()
{
OrphansToHandle = new OrphansToHandle();
OrphansToHandle.Add<ChildObject, ParentObject>();
}
public override int SaveChanges()
{
HandleOrphans();
return base.SaveChanges();
}
private void HandleOrphans()
{
var objectContext = ((IObjectContextAdapter)this).ObjectContext;
objectContext.DetectChanges();
var deletedThings = objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Deleted).ToList();
foreach (var deletedThing in deletedThings)
{
if (deletedThing.IsRelationship)
{
var entityToDelete = IdentifyEntityToDelete(objectContext, deletedThing);
if (entityToDelete != null)
{
objectContext.DeleteObject(entityToDelete);
}
}
}
}
private object IdentifyEntityToDelete(ObjectContext objectContext, ObjectStateEntry deletedThing)
{
// The order is not guaranteed, we have to find which one has to be deleted
var entityKeyOne = objectContext.GetObjectByKey((EntityKey)deletedThing.OriginalValues[0]);
var entityKeyTwo = objectContext.GetObjectByKey((EntityKey)deletedThing.OriginalValues[1]);
foreach (var item in OrphansToHandle.List)
{
if (IsInstanceOf(entityKeyOne, item.ChildToDelete) && IsInstanceOf(entityKeyTwo, item.Parent))
{
return entityKeyOne;
}
if (IsInstanceOf(entityKeyOne, item.Parent) && IsInstanceOf(entityKeyTwo, item.ChildToDelete))
{
return entityKeyTwo;
}
}
return null;
}
private bool IsInstanceOf(object obj, Type type)
{
// Sometimes it's a plain class, sometimes it's a DynamicProxy, we check for both.
return
type == obj.GetType() ||
(
obj.GetType().Namespace == "System.Data.Entity.DynamicProxies" &&
type == obj.GetType().BaseType
);
}
}
public class OrphansToHandle
{
public IList<EntityPairDto> List { get; private set; }
public OrphansToHandle()
{
List = new List<EntityPairDto>();
}
public void Add<TChildObjectToDelete, TParentObject>()
{
List.Add(new EntityPairDto() { ChildToDelete = typeof(TChildObjectToDelete), Parent = typeof(TParentObject) });
}
}
public class EntityPairDto
{
public Type ChildToDelete { get; set; }
public Type Parent { get; set; }
}
原始答案
要在不设置复杂键的情况下解决此问题,您可以覆盖SaveChanges
您的DbContext
, 但随后使用ChangeTracker
以避免访问数据库以查找孤立对象。
首先添加一个导航属性(如果需要,ChildObject
您可以保留属性,它可以使用任何一种方式):int ParentObjectId
public class ParentObject
{
public int Id { get; set; }
public virtual List<ChildObject> ChildObjects { get; set; }
}
public class ChildObject
{
public int Id { get; set; }
public virtual ParentObject ParentObject { get; set; }
}
然后使用以下方法查找孤立对象ChangeTracker
:
public class MyContext : DbContext
{
//...
public override int SaveChanges()
{
HandleOrphans();
return base.SaveChanges();
}
private void HandleOrphans()
{
var orphanedEntities =
ChangeTracker.Entries()
.Where(x => x.Entity.GetType().BaseType == typeof(ChildObject))
.Select(x => ((ChildObject)x.Entity))
.Where(x => x.ParentObject == null)
.ToList();
Set<ChildObject>().RemoveRange(orphanedEntities);
}
}
您的配置变为:
modelBuilder.Entity<ParentObject>().HasMany(p => p.ChildObjects)
.WithRequired(c => c.ParentObject)
.WillCascadeOnDelete();
我做了一个简单的速度测试,迭代了 10.000 次。启用它需要 1: 01.443HandleOrphans()
分钟才能完成,禁用它是 0:59.326 分钟(两者都是平均 3 次运行)。测试代码如下。
using (var context = new MyContext())
{
var parentObject = context.ParentObject.Find(1);
parentObject.ChildObjects.Add(new ChildObject());
context.SaveChanges();
}
using (var context = new MyContext())
{
var parentObject = context.ParentObject.Find(1);
parentObject.ChildObjects.Clear();
context.SaveChanges();
}