【问题标题】:Delete loaded and unloaded objects by ID in EntityFrameworkCore在EntityFrameworkCore中按ID删除加载和卸载的对象
【发布时间】:2017-12-14 12:31:27
【问题描述】:

我有一个方法可以接收我要删除的对象的 ID IEnumerable<Guid>。一种建议的方法如下

foreach(Guid id in ids)
{
  var tempInstance = new MyEntity { Id = id };
  DataContext.Attach(tempInstance); // Exception here
  DataContext.Remove(tempInstance);
}

如果对象尚未加载到内存中,这可以正常工作。但我的问题是,当它们已经加载时,Attach 方法会抛出InvalidOperationException - The instance of entity type 'MyEntity' cannot be tracked because another instance with the key value 'Id:...' is already being tracked。如果我使用DataContext.Remove 而不调用Attach,也会发生同样的情况。

foreach(Guid id in ids)
{
  var tempInstance = new MyEntity { Id = id };
  DataContext.Remove(tempInstance); // Exception here
}

我不想使用DataContext.Find 来获取已加载对象的实例,因为如果对象尚未加载,这会将其加载到内存中。

我无法使用DataContext.ChangeTracker 来查找已加载的对象,因为那里只显示状态已修改的对象,并且我的对象可能已加载和未修改。

以下方法在设置EntityEntry.State 时会抛出相同的InvalidOperationException,即使我在MyEntity 上覆盖GetHashCodeEquals 以确保字典查找将它们视为同一个对象。

foreach(Guid id in ids)
{
  var tempInstance = new MyEntity { Id = id };
  EntityEntry entry = DataContext.Entry(tempInstance);
  entry.State == EntityState.Deleted; // Exception here
}

目前我发现的唯一方法是可以在不知道对象是否如下的情况下实现按ID删除对象:

foreach(Guid id in ids)
{
  var tempInstance = new MyEntity { Id = id };
  try
  {
    DataContext.Attach(tempInstance); // Exception here
  }
  catch (InvalidOperationException)
  {
  }
  DataContext.Remove(tempInstance);
}

奇怪的是,在尝试Attach 它遇到异常后,我能够毫无错误地调用DataContext.Remove(tempInstance),但此时它确实可以正常工作,并且当DataContext.SaveChanges 时,它还会从数据库中删除正确的行被执行。

我不喜欢捕捉异常。有没有实现我想要的“好”方法?

注意:如果类有自引用,那么您需要将对象加载到内存中,以便 EntityFrameworkCore 可以确定删除对象的顺序。

【问题讨论】:

    标签: entity-framework-core


    【解决方案1】:

    奇怪的是,尽管这在 EF6 和 EF Core 中是一个非常常见的例外,但它们都没有公开公开一种方法,用于以编程方式检测具有相同密钥的已跟踪实体实例。请注意,覆盖 GetHashCodeEquals 没有帮助,因为 EF 使用引用相等来跟踪实体实例。

    当然可以从DbSet<T>.Local属性中获取,但不如Find使用的内部EF机制和上述抛出异常的方法那么高效。我们只需要Find 方法的第一部分,并在找不到时返回null,而不是从数据库中加载。

    幸运的是,对于 EF Core,我们需要的方法可以通过使用一些 EF Core 内部相对容易地实现(在标准下此 API 支持 Entity Framework Core 基础结构,并不打算直接从您的代码。此 API 可能会在未来的版本中更改或删除。 政策)。这是在 EF Core 2.0.1 上测试的示例实现:

    using Microsoft.EntityFrameworkCore.Internal;
    
    namespace Microsoft.EntityFrameworkCore
    {
        public static partial class CustomExtensions
        {
            public static TEntity FindTracked<TEntity>(this DbContext context, params object[] keyValues)
                where TEntity : class
            {
                var entityType = context.Model.FindEntityType(typeof(TEntity));
                var key = entityType.FindPrimaryKey();
                var stateManager = context.GetDependencies().StateManager;
                var entry = stateManager.TryGetEntry(key, keyValues);
                return entry?.Entity as TEntity;
            }
        }
    }
    

    现在你可以简单地使用:

    foreach (var id in ids)
        DataContext.Remove(DataContext.FindTracked<MyEntity>(id) ?? new MyEntity { Id = id }));
    

    DataContext.RemoveRange(ids.Select(id => 
        DataContext.FindTracked<MyEntity>(id) ?? new MyEntity { Id = id }));
    

    【讨论】:

    • 很久以前我为此提交了更改请求。响应:“可能是一个有用的功能”。
    • 像魅力一样工作,谢谢!虽然我现在不能使用它,因为删除自引用类实例时需要将它们加载到内存中,但这将是一个非常有用的答案!
    猜你喜欢
    • 1970-01-01
    • 2019-06-02
    • 1970-01-01
    • 2020-08-13
    • 1970-01-01
    • 2016-05-08
    • 2014-10-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多