覆盖 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 的条目中找到文件一样,您可以找到它的产品,假设FileProduct 有一个外键关联,即它有一个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&lt;Product&gt;() 否决票已锁定,请编辑此答案以便能够投票 我有效地使用了你的方法,但我必须做一些调整才能让它发挥作用。我必须打开预先加载,设置外键关联,并且我必须加载所有条目以尝试将它们转换为“产品”,因为调用 .Entries() 总是会导致 null。我将在星期一发布我的完整解决方案。 好的,谢谢。我很想知道为什么Entries&lt;Product&gt;() 没有按预期工作。这意味着不存在未更改的Products (?)。但是他们不应该需要审计——好吧,我会看到......【参考方案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() 会怎样。状态 = EntityState.Deleted?不确定相关的实体/导航属性详细信息是否会出现在更改跟踪器中......只是大声思考...... 如果 OP 谈到 积极地从实体中删除关系,我假设关系首先存在。如果没有,他们无论如何都必须从数据库中获取实体才能删除它。

以上是关于覆盖 SaveChanges 并删除 EF 关系时为 null的主要内容,如果未能解决你的问题,请参考以下文章

使用关联覆盖实体删除

自跟踪实体 SaveChanges() 在多对多关系中添加实体时出现异常

entity framework 使用存储过程删除数据后,还用再使用SaveChanges()保存吗?

如果在 Db 上修改了先前加载的实体,如何将 EF 设置为不让 saveChanges?

关于EF Core对中间表的先删除在增加错误

在 EF 中调用 SaveChanges() 后将获取的旧记录保留在变量中