通过 EntityFrameworkCore 中的 ID 删除加载和卸载的对象 [英] Delete loaded and unloaded objects by ID in EntityFrameworkCore

查看:10
本文介绍了通过 EntityFrameworkCore 中的 ID 删除加载和卸载的对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个方法可以接收我想要删除的对象的 ID 的 IEnumerable.一种建议的方法如下

I have a method that receives an IEnumerable<Guid> of IDs to objects I want to delete. One suggested method is as follows

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

如果对象尚未加载到内存中,这可以正常工作.但我的问题是,当它们已经加载时,Attach 方法会抛出一个 InvalidOperationException - 无法跟踪实体类型MyEntity"的实例,因为另一个具有键值Id:"的实例...' 已经被跟踪.如果我使用 DataContext.Remove 而不调用 Attach,也会发生同样的情况.

This works fine if the objects aren't already loaded into memory. But my problem is that when they are already loaded then the Attach method throws an InvalidOperationException - The instance of entity type 'MyEntity' cannot be tracked because another instance with the key value 'Id:...' is already being tracked. The same happens if I use DataContext.Remove without calling Attach.

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

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

I don't want to use DataContext.Find to grab the instance of an already loaded object because that will load the object into memory if it isn't already loaded.

我无法使用 DataContext.ChangeTracker 来查找已加载的对象,因为只有状态已修改的对象才会出现在那里,而我的对象可能已加载且未修改.

I cannot use DataContext.ChangeTracker to find already loaded objects because only objects with modified state appear in there and my objects might be loaded and unmodified.

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

The following approach throws the same InvalidOperationException when setting EntityEntry.State, even when I override GetHashCode and Equals on MyEntity to ensure dictionary lookups see them as the same object.

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

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

The only way so far I have found that I can achieve deleting objects by ID without knowing if the object is the following:

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 时,异常并从数据库中删除正确的行.

It's odd that I am able to call DataContext.Remove(tempInstance) without error after experiencing an exception trying to Attach it, but at this point it does work without an exception and also deletes the correct rows from the database when DataContext.SaveChanges is executed.

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

I don't like catching the exception. Is there a "good" way of achieving what I want?

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

Note: If the class has a self-reference then you need to load the objects into memory so EntityFrameworkCore can determine in which order to delete the objects.

推荐答案

奇怪的是,虽然这是 EF6 和 EF Core 中相当常见的异常,但它们都没有公开公开一种以编程方式检测已跟踪实体实例的方法钥匙.请注意,覆盖 GetHashCodeEquals 没有帮助,因为 EF 使用引用相等性来跟踪实体实例.

Strangely, although this is a quite common exception in EF6 and EF Core, neither of them expose publicly a method for programmatically detecting the already tracked entity instance with the same key. Note that overriding GetHashCode and Equals doesn't help since EF is using reference equality for tracking entity instances.

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

Of course it can be obtained from the DbSet<T>.Local property, but it would not be as efficient as the internal EF mechanism used by Find and the methods throwing the aforementioned exception. All we need is the first part of the Find method and returning null when not found instead of loading from the database.

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

Luckily, for EF Core the method that we need can be implemented relatively easily by using some of the EF Core internals (under the standard This API supports the Entity Framework Core infrastructure and is not intended to be used directly from your code. This API may change or be removed in future releases. policy). Here is the sample implementation, tested on 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;
        }
    }
}

现在您可以简单地使用:

Now you can use simply:

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

这篇关于通过 EntityFrameworkCore 中的 ID 删除加载和卸载的对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆