EF是否可以自动删除孤立的数据,而不删除父数据?
对于使用Code First EF 5 beta的应用程序,我有:
public class ParentObject { public int Id {get; set;} public virtual List ChildObjects {get; set;} //Other members }
和
public class ChildObject { public int Id {get; set;} public int ParentObjectId {get; set;} //Other members }
必要时,相关的CRUD操作由存储库执行。
在
OnModelCreating(DbModelBuilder modelBuilder)
我已经设置了它们:
modelBuilder.Entity().HasMany(p => p.ChildObjects) .WithOptional() .HasForeignKey(c => c.ParentObjectId) .WillCascadeOnDelete();
因此,如果删除ParentObject,它的ChildObjects也是如此。
但是,如果我跑:
parentObject.ChildObjects.Clear(); _parentObjectRepository.SaveChanges(); //this repository uses the context
我得到了例外:
操作失败:无法更改关系,因为一个或多个外键属性不可为空。 当对关系进行更改时,相关的外键属性将设置为空值。 如果外键不支持空值,则必须定义新关系,必须为外键属性分配另一个非空值,或者必须删除不相关的对象。
这是有道理的,因为实体的定义包括正在被破坏的外键约束。
我可以将实体配置为在孤立时“清除自己”,或者我必须从上下文中手动删除这些ChildObject
(在本例中使用ChildObjectRepository)。
它实际上是受支持的,但仅限于使用识别关系时 。 它首先与代码一起使用。 您只需要为包含Id
和ParentObjectId
定义复杂键:
modelBuilder.Entity() .HasKey(c => new {c.Id, c.ParentObjectId});
因为定义此类键将删除自动递增的Id的默认约定,您必须手动重新定义它:
modelBuilder.Entity() .Property(c => c.Id) .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
现在调用parentObject.ChildObjects.Clear()删除依赖对象。
顺便说一句。 您的关系映射应使用WithRequired
来跟踪您的真实类,因为如果FK不可为空,则它不是可选的:
modelBuilder.Entity().HasMany(p => p.ChildObjects) .WithRequired() .HasForeignKey(c => c.ParentObjectId) .WillCascadeOnDelete();
更新:
我发现了一种不需要从子项添加导航属性到父实体或设置复杂键的方法。
它基于这篇文章 ,它使用ObjectStateManager
来查找已删除的实体。
使用列表ObjectStateEntry
,我们可以从每个中找到一对EntityKey
,表示已删除的关系。
此时,我找不到任何必须删除的迹象。 与文章的示例相反,只是选择第二个将在父项具有导航属性的情况下删除父项。 因此,为了解决这个问题,我会跟踪应该使用OrphansToHandle
类处理哪些类型。
该模型:
public class ParentObject { public int Id { get; set; } public virtual ICollection ChildObjects { get; set; } public ParentObject() { ChildObjects = new List (); } } public class ChildObject { public int Id { get; set; } }
其他课程:
public class MyContext : DbContext { private readonly OrphansToHandle OrphansToHandle; public DbSet ParentObject { get; set; } public MyContext() { OrphansToHandle = new OrphansToHandle(); OrphansToHandle.Add(); } 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 List { get; private set; } public OrphansToHandle() { List = new List (); } public void Add() { List.Add(new EntityPairDto() { ChildToDelete = typeof(TChildObjectToDelete), Parent = typeof(TParentObject) }); } } public class EntityPairDto { public Type ChildToDelete { get; set; } public Type Parent { get; set; } }
原始答案
要在不设置复杂密钥的情况下解决此问题,可以覆盖DbContext
的SaveChanges
,但随后使用ChangeTracker
来避免访问数据库以查找孤立对象。
首先向ChildObject
添加一个导航属性(如果需要,可以保留int ParentObjectId
属性,它可以以任何方式工作):
public class ParentObject { public int Id { get; set; } public virtual List 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().RemoveRange(orphanedEntities); } }
您的配置变为:
modelBuilder.Entity().HasMany(p => p.ChildObjects) .WithRequired(c => c.ParentObject) .WillCascadeOnDelete();
我做了一次简单的速度测试迭代10.000次。 启用HandleOrphans()
,完成时间为1:01.443分钟,禁用时为0:59.326分钟(均为三次运行的平均值)。 测试代码如下。
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(); }
这不是EF现在自动支持的。 您可以通过在上下文中覆盖SaveChanges并手动删除不再具有父级的子对象来完成此操作。 代码将是这样的:
上述就是C#学习教程:EF是否可以自动删除孤立的数据,而不删除父数据?分享的全部内容,如果对大家有所用处且需要了解更多关于C#学习教程,希望大家多多关注—计算机技术网(www.ctvol.com)!
public override int SaveChanges() { foreach (var bar in Bars.Local.ToList()) { if (bar.Foo == null) { Bars.Remove(bar); } } return base.SaveChanges(); }
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/cdevelopment/956258.html