问题描述
我的基本存储库类
public class Repository<TEntity,TId> : IRepository<TEntity,TId> where TEntity : class,IEntity<TId>
{
protected readonly CBSContext _context;
private DbSet<TEntity> _entities;
public Repository(CBSContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_entities = _context.Set<TEntity>();
}
public async Task UpdateAsync(TEntity entity)
{
await Task.Run(() => _context.Entry(entity).State = EntityState.Modified);
}
//Update child enitity code added below
}
和我的实体接口
public interface IEntity<TId>
{
TId Id { get; set; }
}
public class Customer : IEntity<int>
{
public int Id { get; set; }
public string CustomerNo { get; set; }
public ICollection<Address> Addresses { get; set; } = new List<Address>();
}
我需要在断开连接的方案中添加/更新/删除子实体。
AM引用此答案Add/update/delete child entities
那个分析器基于自定义BaseEnity,但是我使用的是IEnity。
已完成的工作:
我用Tentity代替了baseentity。但显示错误
下面是保存子元素的新代码
public async Task UpdateAsync(TEntity entity,params Expression<Func<TEntity,object>>[] navigations)
{
var dbEntity = await _context.FindAsync<TEntity>(entity.Id);
var dbEntry = _context.Entry(dbEntity);
dbEntry.CurrentValues.SetValues(entity);
foreach (Expression<Func<TEntity,object>> property in navigations)
{
var propertyName = property.GetPropertyAccess().Name;
var dbItemsEntry = dbEntry.Collection(propertyName);
var accessor = dbItemsEntry.Metadata.GetCollectionAccessor();
await dbItemsEntry.LoadAsync();
var dbItemsMap = ((IEnumerable<TEntity>)dbItemsEntry.CurrentValue)
.ToDictionary(e => e.Id);
var items = (IEnumerable<object>) accessor.GetOrCreate(entity);
foreach (var item in items)
{
if (!dbItemsMap.TryGetValue(item.Id,out var oldItem))
accessor.Add(dbEntity,item);
else
{
_context.Entry(oldItem).CurrentValues.SetValues(item);
dbItemsMap.Remove(item.Id);
}
}
foreach (var oldItem in dbItemsMap.Values)
accessor.Remove(dbEntity,oldItem);
}
await Task.Run(() => _context.SaveChangesAsync());
}
以下显示错误:
.net核心是否有其他替代方法。.am请提出任何替代方法。
解决方法
更新:
评论中还提出了一些其他问题。引用导航属性该怎么办,如果相关实体未实现此类通用接口该怎么办,使用此类通用方法签名时,编译器也无法推断出通用类型参数。
经过一番思考,我得出的结论是,根本不需要基类/接口(甚至通用实体类型),因为EF Core元数据包含与PK配合使用的所有信息({{1}使用} / Find
方法和更改跟踪器)。
以下是仅使用EF Core元数据信息/服务来递归应用断开连接的实体图修改的方法。如果需要,可以修改它以接收“排除”过滤器,以防某些实体/集合被跳过:
FindAsync
请注意,类似于EF Core方法,public static class EntityGraphUpdateHelper
{
public static async ValueTask<object> UpdateGraphAsync(this DbContext context,object entity) =>
await context.UpdateGraphAsync(await context.FindEntityAsync(entity),entity,new HashSet<IForeignKey>());
private static async ValueTask<object> UpdateGraphAsync(this DbContext context,object dbEntity,object entity,HashSet<IForeignKey> visited)
{
bool isNew = dbEntity == null;
if (isNew) dbEntity = entity;
var dbEntry = context.Entry(dbEntity);
if (isNew)
dbEntry.State = EntityState.Added;
else
{
// ensure is attached (tracked)
if (dbEntry.State == EntityState.Detached)
dbEntry.State = EntityState.Unchanged;
// update primitive values
dbEntry.CurrentValues.SetValues(entity);
}
// process navigations
foreach (var navEntry in dbEntry.Navigations)
{
if (!visited.Add(navEntry.Metadata.ForeignKey)) continue; // already processed
await navEntry.LoadAsync();
if (!navEntry.Metadata.IsCollection())
{
// reference type navigation property
var refValue = navEntry.Metadata.GetGetter().GetClrValue(entity);
navEntry.CurrentValue = refValue == null ? null :
await context.UpdateGraphAsync(navEntry.CurrentValue,refValue,visited);
}
else
{
// collection type navigation property
var accessor = navEntry.Metadata.GetCollectionAccessor();
var items = (IEnumerable<object>)accessor.GetOrCreate(entity,false);
var dbItems = (IEnumerable<object>)accessor.GetOrCreate(dbEntity,false);
var itemType = navEntry.Metadata.GetTargetType();
var keyProperties = itemType.FindPrimaryKey().Properties
.Select((p,i) => (Index: i,Getter: p.GetGetter(),Comparer: p.GetKeyValueComparer()))
.ToList();
var keyValues = new object[keyProperties.Count];
void GetKeyValues(object sourceItem)
{
foreach (var p in keyProperties)
keyValues[p.Index] = p.Getter.GetClrValue(sourceItem);
}
object FindItem(IEnumerable<object> targetCollection,object sourceItem)
{
GetKeyValues(sourceItem);
foreach (var targetItem in targetCollection)
{
bool keyMatch = true;
foreach (var p in keyProperties)
{
(var keyA,var keyB) = (p.Getter.GetClrValue(targetItem),keyValues[p.Index]);
keyMatch = p.Comparer?.Equals(keyA,keyB) ?? object.Equals(keyA,keyB);
if (!keyMatch) break;
}
if (keyMatch) return targetItem;
}
return null;
}
// Remove db items missing in the current list
foreach (var dbItem in dbItems.ToList())
if (FindItem(items,dbItem) == null) accessor.Remove(dbEntity,dbItem);
// Add current items missing in the db list,update others
var existingItems = dbItems.ToList();
foreach (var item in items)
{
var dbItem = FindItem(existingItems,item);
if (dbItem == null)
accessor.Add(dbEntity,item,false);
await context.UpdateGraphAsync(dbItem,visited);
}
}
}
return dbEntity;
}
public static ValueTask<object> FindEntityAsync(this DbContext context,object entity)
{
var entityType = context.Model.FindRuntimeEntityType(entity.GetType());
var keyProperties = entityType.FindPrimaryKey().Properties;
var keyValues = new object[keyProperties.Count];
for (int i = 0; i < keyValues.Length; i++)
keyValues[i] = keyProperties[i].GetGetter().GetClrValue(entity);
return context.FindAsync(entityType.ClrType,keyValues);
}
}
调用不是上述方法的一部分,应在以后单独调用。
原始:
处理实现此类 generic 接口的实体的集合需要略有不同的方法,因为没有 nongeneric 基类/接口可用于提取{{1} }。
一种可能的解决方案是将集合处理代码移至单独的 generic 方法,并动态或通过反射对其进行调用。
例如(使用VS确定必要的SaveChangesAsync
s)
Id
并从using
存储库中调用它:
public static class EntityUpdateHelper
{
public static async Task UpdateEntityAsync<TEntity,TId>(this DbContext context,TEntity entity,params Expression<Func<TEntity,object>>[] navigations)
where TEntity : class,IEntity<TId>
{
var dbEntity = await context.FindAsync<TEntity>(entity.Id);
var dbEntry = context.Entry(dbEntity);
dbEntry.CurrentValues.SetValues(entity);
foreach (var property in navigations)
{
var propertyName = property.GetPropertyAccess().Name;
var dbItemsEntry = dbEntry.Collection(propertyName);
var dbItems = dbItemsEntry.CurrentValue;
var items = dbItemsEntry.Metadata.GetGetter().GetClrValue(entity);
// Determine TEntity and TId,and call UpdateCollection<TEntity,TId>
// via reflection
var itemType = dbItemsEntry.Metadata.GetTargetType().ClrType;
var idType = itemType.GetInterfaces()
.Single(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEntity<>))
.GetGenericArguments().Single();
var updateMethod = typeof(EntityUpdateHelper).GetMethod(nameof(UpdateCollection))
.MakeGenericMethod(itemType,idType);
updateMethod.Invoke(null,new[] { dbItems,items });
}
await context.SaveChangesAsync();
}
public static void UpdateCollection<TEntity,ICollection<TEntity> dbItems,ICollection<TEntity> items)
where TEntity : class,IEntity<TId>
{
var dbItemsMap = dbItems.ToDictionary(e => e.Id);
foreach (var item in items)
{
if (!dbItemsMap.TryGetValue(item.Id,out var oldItem))
dbItems.Add(item);
else
{
context.Entry(oldItem).CurrentValues.SetValues(item);
dbItemsMap.Remove(item.Id);
}
}
foreach (var oldItem in dbItemsMap.Values)
dbItems.Remove(oldItem);
}
}
如果是通用存储库(没有导航参数),并且所有子集合实体都实现了该接口,则可以简单地迭代Customer
属性,例如
return await _context.UpdateEntityAsync(entity,e => e.Addresses);