使用EF Core更新通用存储库上的父级和子级集合 [英] Update parent and child collections on generic repository with EF Core
问题描述
说我有一个销售
课:
public class Sale : BaseEntity //BaseEntity only has an Id
{
public ICollection<Item> Items { get; set; }
}
还有一个项目
类:
public class Item : BaseEntity //BaseEntity only has an Id
{
public int SaleId { get; set; }
public Sale Sale { get; set; }
}
和通用存储库(更新方法):
And a Generic Repository (update method):
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();
}
我在更新 Item <时遇到困难/ code>销售
类中的集合。有了这段代码,我成功地添加
或修改
一个项目
。但是,当我 UI
层上的删除
某项时,什么也不会被删除。
I'm having difficulties to update the Item
collection on the Sale
class. With this code I managed to add
or modify
an Item
. But, when I delete
some item on the UI
layer, nothing gets deleted.
使用通用存储库模式时, EF Core
是否可以解决这种情况?
Does EF Core
have something to deal with this situation, while using a generic repository patter?
更新
似乎丢失了项目
跟踪。这是我的包含的通用检索方法。
Seems to be that Items
tracking is lost. Here is my generic retrieve method with includes.
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);
}
推荐答案
显然,问题是在申请断开连接的实体的修改(否则,您不会除了调用包含集合导航属性的 SaveChanges
以外,还需要执行其他任何操作,这些集合导航属性需要反映从传递的对象中添加/删除/更新的项目。
Apparently the question is for applying modifications of disconnected entity (otherwise you won't need to do anything else than calling SaveChanges
) containing collection navigation properties which need to reflect the added/removed/update items from the passed object.
EF Core不提供这种即装即用的功能。它支持简单的 Update 方法> upsert (插入或更新),但是它不会检测和删除已删除的项。
EF Core does not provide such out of the box capability. It supports simple upsert (insert or update) through Update
method for entities with auto-generated keys, but it doesn't detect and delete the removed items.
所以您需要自己进行检测。加载现有项目是朝正确方向迈出的一步。您的代码的问题在于,它不考虑新项目,而是对从数据库中检索到的现有项目进行了无用的状态操纵。
So you need to do that detection yourself. Loading the existing items is a step in the right direction. The problem with your code is that it doesn't account the new items, but instead is doing some useless state manipulation of the existing items retrieved from the database.
以下是正确的实现同样的想法。它使用一些EF Core内部构件( GetCollectionAccessor()
方法返回的 IClrCollectionAccessor
-都需要使用Microsoft.EntityFrameworkCore.Metadata.Internal;
)来操作集合,但是您的代码已经在使用内部 GetPropertyAccess()
方法,因此我猜想这应该不成问题-如果将来的EF Core版本中进行了更改,则应该相应地更新代码。之所以需要集合访问器,是因为由于协方差,虽然 IEnumerable< BaseEntity>
可以用于一般性地访问集合,但是对于 ICollection< BaseEntity>
,因为它是不变的,我们需要一种方法来访问添加
/ 删除
方法。内部访问器提供了该功能以及从传递的实体中一般检索属性值的方法。
Following is the correct implementation of the same idea. It uses some EF Core internals (IClrCollectionAccessor
returned by the GetCollectionAccessor()
method - both require using Microsoft.EntityFrameworkCore.Metadata.Internal;
) to manipulate the collection, but your code already is using the internal GetPropertyAccess()
method, so I guess that shouldn't be a problem - in case something is changed in some future EF Core version, the code should be updated accordingly. The collection accessor is needed because while IEnumerable<BaseEntity>
can be used for generically accessing the collections due to covariance, the same cannot be said about ICollection<BaseEntity>
because it's invariant, and we need a way to access Add
/ Remove
methods. The internal accessor provides that capability as well as a way to generically retrieve the property value from the passed entity.
更新:从EF Core 3.0开始, GetCollectionAccessor 和 IClrCollectionAccessor 的一部分
Update: Starting from EF Core 3.0, GetCollectionAccessor and IClrCollectionAccessor are part of the 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键入的现有项(用于快速查找)。然后,我们对新项目进行一次传递。我们使用字典查找相应的现有项目。如果未找到匹配项,则该项目被视为新项目,并且仅添加到目标(跟踪)集合中。否则,找到的项目将从源中更新,并从字典中删除。这样,在完成循环之后,词典中包含需要删除的项目,因此我们所需要做的就是将它们从目标(跟踪)集合中删除。
The algorithm is pretty standard. After loading the collection from the database, we create a dictionary containing the existing items keyed by Id (for fast lookup). Then we do a single pass over the new items. We use the dictionary to find the corresponding existing item. If no match is found, the item is considered new and is simply added to the target (tracked) collection. Otherwise the found item is updated from the source, and removed from the dictionary. This way, after finishing the loop, the dictionary contains the items that needs to be deleted, so all we need is remove them from the target (tracked) collection.
仅此而已。剩下的工作将由EF Core更改跟踪器完成-添加到目标集合的项目将标记为已添加
,已更新的项目将标记为未更改
或已修改
,并且已删除的项目将根据删除级联行为被标记为删除或更新(与父级取消关联) 。如果要强制删除,只需替换
And that's all. The rest of the work will be done by the EF Core change tracker - the added items to the target collection will be marked as Added
, the updated - either Unchanged
or Modified
, and the removed items, depending of the delete cascade behavior will be either be marked for deletion or update (disassociate from parent). If you want to force deletion, simply replace
accessor.Remove(dbEntity, oldItem);
with
_dbContext.Remove(oldItem);
这篇关于使用EF Core更新通用存储库上的父级和子级集合的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!