繁体   English   中英

EF Core 通过完全替换断开集合导航属性的更新

[英]EF Core disconnected update of collection navigation properties via full replacement

使用 EF Core 5.0。 我有一个 SPA 页面,它从 API 中加载了一个Group实体及其Employee实体集合:

var groupToUpdate = await context.Groups 
                                 .Include(g => g.Employees)
                                 .FirstOrDefaultAsync(...);

//Used for UI, list of additional employees for selective adding
var employeeList = await context.Employees.
                                .Where(...)
                                .ToListAsync();

然后,用户通过 Javascript UI 修改groupToUpdate实体,包括一些非导航属性,例如名称/注释。

在同一屏幕上,用户将一些员工添加到组中,从组中删除一些员工,并保留组中的一些现有员工。 所有员工都是数据库中具有现有主键的现有实体。 到目前为止所做的所有更改都只是针对内存中断开连接的实体。

当用户单击保存时, groupToUpdate实体将发送到我的后端代码。 请注意,我们没有跟踪添加/删除/留下哪些员工,我们只是想让这个groupToUpdate完全覆盖旧实体,特别是用新的Employees替换旧的集合。

为了实现这一点,后端代码首先从数据库中再次加载组以开始在上下文中跟踪它。 然后我尝试更新实体,包括用新集合替换旧集合:

public async Task UpdateGroupAsync(Group groupToUpdate)
{
    var groupFromDb = await context.Groups
                                   .Include(g => g.Employees)
                                   .FirstOrDefaultAsync(...);
    
    // Update non-navigation properties such as groupFromDb.Note = groupToUpdate.Note...

    groupFromDb.Employees = groupToUpdate.Employees;

    await context.SaveChangesAsync();
}

现在,如果对Employees集合的更改是完全替换(删除所有旧的,添加所有新的),则此方法成功。 但是只要有一些现有的Employees被搁置,EF 核心就会抛出异常:

无法跟踪实体类型 'Employee' 的实例,因为另一个具有键值 ... 的实例已被跟踪

因此,EF Core 似乎试图同时跟踪从数据库中使用groupFromDb新加载的Employee实体和来自groupToUpdateEmployee实体,即使后者只是从断开连接状态作为参数传入。

我的问题是如何以最少的并发症处理这种更新? 是否有必要手动跟踪添加/删除的实体并添加/删除它们而不是尝试替换整个集合?

您必须指示 ChangeTracker 更新导航集合需要哪些操作。 只是替换集合不是正确的方法。

这是有助于自动执行此操作的扩展:

context.MergeCollections(groupFromDb.Employees, groupToUpdate.Employees, x => x.Id);

执行:

public static void MergeCollections<T, TKey>(this DbContext context, ICollection<T> currentItems, ICollection<T> newItems, Func<T, TKey> keyFunc) 
    where T : class
{
    List<T> toRemove = null;
    foreach (var item in currentItems)
    {
        var currentKey = keyFunc(item);
        var found = newItems.FirstOrDefault(x => currentKey.Equals(keyFunc(x)));
        if (found == null)
        {
            toRemove ??= new List<T>();
            toRemove.Add(item);
        }
        else
        {
            if (!ReferenceEquals(found, item))
                context.Entry(item).CurrentValues.SetValues(found);
        }
    }

    if (toRemove != null)
    {
        foreach (var item in toRemove)
        {
            currentItems.Remove(item);
            // If the item should be deleted from Db: context.Set<T>().Remove(item);
        }
    }

    foreach (var newItem in newItems)
    {
        var newKey = keyFunc(newItem);
        var found = currentItems.FirstOrDefault(x => newKey.Equals(keyFunc(x)));
        if (found == null)
        {
            currentItems.Add(newItem);
        }
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM