gpt4 book ai didi

c# - 使用 EF Core 更新通用存储库上的父集合和子集合

转载 作者:太空狗 更新时间:2023-10-29 22:29:37 25 4
gpt4 key购买 nike

假设我有一个 Sale 类:

public class Sale : BaseEntity //BaseEntity only has an Id  
{
public ICollection<Item> Items { get; set; }
}

还有一个 Item 类:

public class Item : BaseEntity //BaseEntity only has an Id  
{
public int SaleId { get; set; }
public Sale Sale { get; set; }
}

还有一个通用存储库(更新方法):

    public async Task<int> UpdateAsync<T>(T entity, params Expression<Func<T, object>>[] navigations) where T : BaseEntity
{
var dbEntity = _dbContext.Set<T>().Find(entity.Id);

var dbEntry = _dbContext.Entry(dbEntity);

dbEntry.CurrentValues.SetValues(entity);

foreach (var property in navigations)
{
var propertyName = property.GetPropertyAccess().Name;

await dbEntry.Collection(propertyName).LoadAsync();

List<BaseEntity> dbChilds = dbEntry.Collection(propertyName).CurrentValue.Cast<BaseEntity>().ToList();

foreach (BaseEntity child in dbChilds)
{
if (child.Id == 0)
{
_dbContext.Entry(child).State = EntityState.Added;
}
else
{
_dbContext.Entry(child).State = EntityState.Modified;
}
}
}

return await _dbContext.SaveChangesAsync();
}

我在更新 Sale 类的 Item 集合时遇到困难。通过这段代码,我设法添加修改 一个Item。但是,当我删除 UI 层上的某些项目时,没有任何内容被删除。

在使用通用存储库模式时,EF Core 是否有办法处理这种情况?

更新

似乎是 Items 跟踪丢失了。这是我的通用检索方法,其中包含。

    public async Task<T> GetByIdAsync<T>(int id, params Expression<Func<T, object>>[] includes) where T : BaseEntity
{
var query = _dbContext.Set<T>().AsQueryable();

if (includes != null)
{
query = includes.Aggregate(query,
(current, include) => current.Include(include));
}

return await query.SingleOrDefaultAsync(e => e.Id == id);
}

最佳答案

显然问题是应用 disconnected entity 的修改(否则除了调用 SaveChanges 之外,您不需要做任何其他事情)包含集合导航属性,这些属性需要从传递的对象中反射(reflect)添加/删除/更新项目。

EF Core 不提供这种开箱即用的功能。它支持简单的 upsert (插入或更新)通过 Update具有自动生成键的实体的方法,但它不会检测和删除已删除的项目。

因此您需要自己进行检测。加载现有项目是朝着正确方向迈出的一步。您的代码的问题在于它不考虑新项目,而是对从数据库中检索到的现有项目进行一些无用的状态操作。

以下是相同思路的正确实现。它使用一些 EF Core 内部构件( IClrCollectionAccessorGetCollectionAccessor() 方法返回 - 两者都需要 using Microsoft.EntityFrameworkCore.Metadata.Internal; )来操作集合,但您的代码已经在使用内部 GetPropertyAccess()方法,所以我想这应该不是问题——如果某些 future 的 EF Core 版本发生了变化,代码应该相应地更新。需要集合访问器,因为 while IEnumerable<BaseEntity>由于协方差,可用于一般访问集合,关于 ICollection<BaseEntity> 则不能这样说因为它是不变的,我们需要一种方法来访问 Add/Remove方法。内部访问器提供了该功能以及一种从传递的实体中一般检索属性值的方法。

更新:从 EF Core 3.0 开始,GetCollectionAccessorIClrCollectionAccessor是公共(public) API 的一部分。

代码如下:

public async Task<int> UpdateAsync<T>(T entity, params Expression<Func<T, object>>[] navigations) where T : BaseEntity
{
var dbEntity = await _dbContext.FindAsync<T>(entity.Id);

var dbEntry = _dbContext.Entry(dbEntity);
dbEntry.CurrentValues.SetValues(entity);

foreach (var property in navigations)
{
var propertyName = property.GetPropertyAccess().Name;
var dbItemsEntry = dbEntry.Collection(propertyName);
var accessor = dbItemsEntry.Metadata.GetCollectionAccessor();

await dbItemsEntry.LoadAsync();
var dbItemsMap = ((IEnumerable<BaseEntity>)dbItemsEntry.CurrentValue)
.ToDictionary(e => e.Id);

var items = (IEnumerable<BaseEntity>)accessor.GetOrCreate(entity);

foreach (var item in items)
{
if (!dbItemsMap.TryGetValue(item.Id, out var oldItem))
accessor.Add(dbEntity, item);
else
{
_dbContext.Entry(oldItem).CurrentValues.SetValues(item);
dbItemsMap.Remove(item.Id);
}
}

foreach (var oldItem in dbItemsMap.Values)
accessor.Remove(dbEntity, oldItem);
}

return await _dbContext.SaveChangesAsync();
}

该算法非常标准。从数据库加载集合后,我们创建一个字典,其中包含由 Id 键控的现有项目(用于快速查找)。然后我们对新项目进行单次传递。我们使用字典来查找对应的现有项。如果未找到匹配项,则该项目将被视为新项目并简单地添加到目标(跟踪)集合中。否则,找到的项目将从源中更新,并从字典中删除。这样,在完成循环后,字典中包含需要删除的项目,因此我们只需要将它们从目标(跟踪)集合中删除即可。

仅此而已。其余工作将由 EF Core 更改跟踪器完成 - 添加到目标集合的项目将标记为 Added , 更新 - UnchangedModified ,并且删除的项目,取决于删除级联行为将被标记为删除或更新(与父级解除关联)。如果要强制删除,只需替换

accessor.Remove(dbEntity, oldItem);

_dbContext.Remove(oldItem);

关于c# - 使用 EF Core 更新通用存储库上的父集合和子集合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55088933/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com