在TEntity通用存储库上更新父集合和子集合

问题描述

我的基本存储库类

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

以下显示错误:

enter image description here

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

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...