在EntityFrameworkCore

时间:2017-12-14 12:31:28

标签: 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);
}

如果对象尚未加载到内存中,则此方法可以正常工作。但我的问题是,当它们已经加载时,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查找已加载的对象,因为只有那些具有已修改状态的对象出现在那里,我的对象可能已加载和未修改。

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

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);
}

奇怪的是,在遇到尝试DataContext.Remove(tempInstance)的异常后,我能够毫无错误地调用Attach,但此时它确实可以正常运行而且还删除了正确的执行DataContext.SaveChanges时来自数据库的行。

我不喜欢捕捉异常。有没有好的&#34;实现我想要的方式?

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

1 个答案:

答案 0 :(得分:5)

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

当然它可以从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;
        }
    }
}

现在你可以简单地使用:

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 }));