在EntityFrameworkCore中按ID删除已加载和已卸载的对象

Pet*_*ris 3 entity-framework-core

我有一个方法,它接收IEnumerable<Guid>我想要删除的对象的ID.一种建议的方法如下

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

如果对象尚未加载到内存中,这可以正常工作.但我的问题是,当它们已经加载时,该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
}
Run Code Online (Sandbox Code Playgroud)

我不想用来DataContext.Find抓取已经加载的对象的实例,因为如果它尚未加载,它会将对象加载到内存中.

我不能DataContext.ChangeTracker用来查找已经加载的对象,因为只有那些具有修改状态的对象出现在那里,我的对象可能被加载和未修改.

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

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

到目前为止,唯一的方法是我发现我可以通过ID删除对象,而不知道对象是否如下:

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

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

我不喜欢捕捉异常.是否有"好"的方式来实现我想要的?

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

Iva*_*oev 6

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

当然,它可以从DbSet<T>.Local属性中获得,但它不会像Find抛出上述异常的方法所使用的内部EF机制那样有效.我们需要的只是方法的第一部分,当找不到而不是从数据库加载时Find返回null.

幸运的是,对于EF Core,我们需要的方法可以通过使用一些EF Core内部相对容易地实现(在标准下,此API支持实体框架核心基础结构,并不打算直接在您的代码中使用.此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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在你可以简单地使用:

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

要么

DataContext.RemoveRange(ids.Select(id => 
    DataContext.FindTracked<MyEntity>(id) ?? new MyEntity { Id = id }));
Run Code Online (Sandbox Code Playgroud)