覆盖 SaveChanges 并删除 EF 关系时为 null
Posted
技术标签:
【中文标题】覆盖 SaveChanges 并删除 EF 关系时为 null【英文标题】:When overriding SaveChanges and removing the EF relationship is null 【发布时间】:2016-11-11 10:00:42 【问题描述】:在我的多对一关系中,我试图删除其中一个子对象并在我覆盖的 SaveChanges 中保留审计跟踪。
执行 EntityState.Modified 或 EntityState.Added 时,file.Entity.Product 不为空,但执行 .Deleted 时,EF 似乎甚至在调用 base.Savechanges() 之前就积极地从实体中删除了关系。
当我在文件(子)上调用 .Remove 后,有什么方法可以在我的 SaveChanges 覆盖中检索与此文件关联的产品?
var files = from e in ChangeTracker.Entries<SavedFile>()
where e.State != EntityState.Unchanged
select e;
if (file.State == EntityState.Deleted)
var text = "File Deleted: " + file.Entity.FriendlyFileName;
// file.Entity.Product is null
var updatedProduct = new Update Product = file.Entity.Product, UpdateDateTime = DateTime.Now, UpdateText = text, User = HttpContext.Current.Request.LogonUserIdentity.Name ;
Updates.Add(updatedProduct);
【问题讨论】:
我猜有一个foreach
循环在files
上分配file
的值,对吧?
如果您使用流体映射,您可以将必填字段存储在属性中并将其标记为 NotMapped 或 Ingnore
【参考方案1】:
正如您在ChangeTracker
的条目中找到文件一样,您可以找到它的产品,假设File
与Product
有一个外键关联,即它有一个ProductID
除了Product
导航属性之外的属性:
if (file.State == EntityState.Deleted)
var text = "File Deleted: " + file.Entity.FriendlyFileName;
var productEntry = ChangeTracker.Entries<Product>()
.FirstOrDefault(p => p.Entity.ID == file.ProductID);
if (productEntry != null)
var updatedProduct = new Update Product = productEntry.Entity, UpdateDateTime = DateTime.Now, UpdateText = text, User = HttpContext.Current.Request.LogonUserIdentity.Name ;
Updates.Add(updatedProduct);
当实体是来自本地缓存的查询时(例如DbSet.Local
),EF 不会显示已删除的实体是预期行为。因此它还必须打破关联以防止这些实体出现在导航属性中。如果你删除一个文件,EF 不会在context.Files.Local
中显示它,但它在product.Files
之类的导航属性中也不会显示(关联的另一端file.Product
也不会显示)。
【讨论】:
这样你又在查询数据库了! 一点也不。我正在查询ChangeTracker.Entries<Product>()
。
否决票已锁定,请编辑此答案以便能够投票
我有效地使用了你的方法,但我必须做一些调整才能让它发挥作用。我必须打开预先加载,设置外键关联,并且我必须加载所有条目以尝试将它们转换为“产品”,因为调用 .EntriesEntries<Product>()
没有按预期工作。这意味着不存在未更改的Product
s (?)。但是他们不应该需要审计——好吧,我会看到......【参考方案2】:
我认为删除后没有办法访问关系
但是,你为什么不使用软删除,我认为这是最好的做法,如果被错误删除,你可以随时返回删除的记录(通过标记 IsDeleted = false,由具有权限的管理员)
希望对你有帮助
【讨论】:
【参考方案3】:如果您只需要 Product
属性,您可以在其定义中使用一些自定义逻辑来捕获它:
class File
...
private Product product;
private Product lastProduct;
public Product Product
get return product;
set
if (product == value) return;
lastProduct = Product;
product = value;
[NotMapped]
public Product LastProduct
get return lastProduct;
...
【讨论】:
【参考方案4】:Gert Arnold 提供的答案应该有效 - 但不确定检查是否有意义 FirstOrDefault(p => p.State != EntityState.Unchanged 因为相关实体可能仍处于未更改状态。
如果没有任何效果,尝试在重写的 SaveChanges 方法中重新加载实体。
this.Entry((file.Entity as <<Your Entity Type>>)).Reload();
//do your operation/audit log
//then reset the Entity State to Deleted
this.Entry((file.Entity)).State = EntityState.Deleted;
编辑:或仅显式加载所需的相关实体
this.Entry(file.Entity as <Type>).Reference(e=> e.Product).Load();
这会在重新加载时导致额外的数据库命中。
编辑:另一个选项,如果没有找到其他好的选项,请再次选择
如何在这种情况下执行原始 SQL 插入查询来填充审计表?
【讨论】:
加载实体会产生各种副作用。在保存更改的同时执行此操作不是一个好主意。如果这无关紧要,这也会使SaveChanges
成为一项潜在的昂贵操作。
@GertArnold- 没错,我的意思是只对已删除的实体执行此操作..这就是为什么我有点指定 if nothing works;正在考虑一个最坏的情况,即延迟加载被禁用并且相关实体没有急切/显式加载;那么在这种情况下,可能击中 db 是唯一的选择。正如我所提到的,您的答案应该有效;尝试对您的答案进行投票,但在这里作为新手无法做到这一点......似乎我需要一些分数才能投票:)
我认为查询 ChangeTracker 会失败的另一种情况是断开连接。获取详细信息的上下文可能在 get 操作之后被释放,并且在执行删除操作时,它可能位于不同的上下文中。那么如果我们只做 context.Entry(以上是关于覆盖 SaveChanges 并删除 EF 关系时为 null的主要内容,如果未能解决你的问题,请参考以下文章
自跟踪实体 SaveChanges() 在多对多关系中添加实体时出现异常
entity framework 使用存储过程删除数据后,还用再使用SaveChanges()保存吗?