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

2023-11-27

我的基本存储库类

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 参考这个答案添加/更新/删除子实体

该 anser 基于自定义 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 core 的新手..请建议是否有任何替代方法。


更新(EF Core 5.0):由于元数据接口更改和跳过导航的引入,更新了部分代码。注意代码不处理多对多跳过导航属性。

// process navigations
foreach (var navEntry in dbEntry.Navigations)
{
    if (navEntry.Metadata is not INavigation navigation) continue; // skip navigation pproperty         
    if (!visited.Add(navigation.ForeignKey)) continue; // already processed
    await navEntry.LoadAsync();
    if (!navigation.IsCollection)
    {
        // reference type navigation property
        var refValue = navigation.GetGetter().GetClrValue(entity);
        navEntry.CurrentValue = refValue == null ? null :
            await context.UpdateGraphAsync(navEntry.CurrentValue, refValue, visited);
    }
    else
    {
        // collection type navigation property
        var accessor = navigation.GetCollectionAccessor();
        var items = (IEnumerable<object>)accessor.GetOrCreate(entity, false);
        var dbItems = (IEnumerable<object>)accessor.GetOrCreate(dbEntity, false);
        var itemType = navigation.TargetEntityType;
        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, item, visited);
        }
    }
}

Update:

评论中还提出了一些其他问题。如何处理引用导航属性以及如果相关实体未实现此类通用接口该怎么办,以及编译器在使用此类通用方法签名时无法推断通用类型参数。

经过一番思考后,我得出的结论是,根本不需要基类/接口(甚至通用实体类型),因为 EF Core 元数据包含使用 PK 所需的所有信息(由Find / 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, item, 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);
    }
}

请注意,与 EF Core 方法类似,SaveChangesAsynccall 不是上述方法的一部分,应该在后面单独调用。

原来的:

处理实现此类的实体的集合generic接口需要稍微不同的方法,因为没有非通用的用于提取的基类/接口Id.

一种可能的解决方案是将集合处理代码移至单独的generic方法并动态或通过反射调用它。

例如(使用VS来确定必要的usings):

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, TId>(this DbContext context, 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);

在通用存储库(无导航参数)和实现该接口的所有子集合实体的情况下,简单地迭代dbEntry.Collections财产,例如

//foreach (var property in navigations)
foreach (var dbItemsEntry in dbEntry.Collections)
{
    //var propertyName = property.GetPropertyAccess().Name;
    //var dbItemsEntry = dbEntry.Collection(propertyName);
    var dbItems = dbItemsEntry.CurrentValue;
    var items = dbItemsEntry.Metadata.GetGetter().GetClrValue(entity);
    // ...
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

更新 TEntity 通用存储库上的父集合和子集合 的相关文章

  • C++ 中本地类中的静态成员变量?

    我知道我们不能宣布static本地类中的成员变量 但其原因尚不清楚 那么请问有人可以解释一下吗 另外 为什么我们不能访问非static函数内部定义的变量 内部已经定义了局部类 直接在局部类成员函数中 在下面给出的代码中 int main i
  • 如何修复此错误“GDI+ 中发生一般错误”?

    从默认名称打开图像并以默认名称保存 覆盖它 我需要从 Image Default jpg 制作图形 将其放在 picturebox1 image 上并在 picurebox1 上绘制一些图形 它有效 这不是我的问题 但我无法保存 pictu
  • 在新的浏览器进程中打开 URL

    我需要在新的浏览器进程中打开 URL 当浏览器进程退出时我需要收到通知 我当前使用的代码如下 Process browser new Process browser EnableRaisingEvents true browser Star
  • XamlReader.Load 在后台线程中。是否可以?

    WPF 应用程序具有从单独的文件加载用户控件的操作 使用XamlReader Load method StreamReader mysr new StreamReader pathToFile DependencyObject rootOb
  • 存储来自其他程序的事件

    我想将其他应用程序的事件存储在我自己的应用程序中 事件示例 打开 最小化 Word 或打开文件时 这样的事可能吗 运行程序 http msdn microsoft com en us library ms813609 aspx and 打开
  • 用于检查项目文件中的项目变量和引用路径的 api

    我正在研究一个 net application VS2010 与 x 没有 解和变量号这些解决方案中的项目数量 我需要检查项目属性 特定于一定数量的项目 是否同质 并且检查 验证构建期间的参考路径 有没有一个API是这样的吗 如果没有 我该
  • 获取 WPF 控件的所有附加事件处理程序

    我正在开发一个应用程序 在其中动态分配按钮的事件 现在的问题是 我希望获取按钮单击事件的所有事件 因为我希望删除以前的处理程序 我尝试将事件处理程序设置为 null 如下所示 Button Click null 但是我收到了一个无法分配 n
  • ASP.NET:获取自 1970 年 1 月 1 日以来的毫秒数

    我有一个 ASP NET VB NET 日期 我试图获取自 1970 年 1 月 1 日以来的毫秒数 我尝试在 MSDN 中寻找方法 但找不到任何东西 有谁知道如何做到这一点 从 NET 4 6 开始 该方法ToUnixTimeMillis
  • 关于在 Windows 上使用 WiFi Direct Api?

    我目前正在开发一个应用程序 我需要在其中创建链接 阅读 无线网络连接 在桌面应用程序 在 Windows 10 上 和平板电脑 Android 但无关紧要 之间 工作流程 按钮 gt 如果需要提升权限 gt 创建类似托管网络的 WiFi 网
  • 单击 form2 上的按钮触发 form 1 中的方法

    我对 Windows 窗体很陌生 我想知道是否可以通过单击表单 2 中的按钮来触发表单 1 中的方法 我的表格 1 有一个组合框 我的 Form 2 有一个 保存 按钮 我想要实现的是 当用户单击表单 2 中的 保存 时 我需要检查表单 1
  • Rx 中是否有与 Task.ContinueWith 运算符等效的操作?

    Rx 中是否有与 Task ContinueWith 运算符等效的操作 我正在将 Rx 与 Silverlight 一起使用 我正在使用 FromAsyncPattern 方法进行两个 Web 服务调用 并且我想这样做同步地 var o1
  • 如何将整数转换为 void 指针?

    在 C 中使用线程时 我面临警告 警告 从不同大小的整数转换为指针 代码如下 include
  • 批量更新 SQL Server C#

    我有一个 270k 行的数据库 带有主键mid和一个名为value 我有一个包含中值和值的文本文件 现在我想更新表格 以便将每个值分配给正确的中间值 我当前的方法是从 C 读取文本文件 并为我读取的每一行更新表中的一行 必须有更快的方法来做
  • 使用 Moq 使用内部构造函数模拟类型

    我正在尝试模拟 Microsoft Sync Framework 中的一个类 它只有一个内部构造函数 当我尝试以下操作时 var fullEnumerationContextMock new Mock
  • 私有模板函数

    我有一堂课 C h class C private template
  • C++ 密码屏蔽

    我正在编写一个代码来接收密码输入 下面是我的代码 程序运行良好 但问题是除了数字和字母字符之外的其他键也被读取 例如删除 插入等 我知道如何避免它吗 特q string pw char c while c 13 Loop until Ent
  • 用于 C# 的 TripleDES IV?

    所以当我说这样的话 TripleDES tripledes TripleDES Create Rfc2898DeriveBytes pdb new Rfc2898DeriveBytes password plain tripledes Ke
  • 有没有办法强制显示工具提示?

    我有一个验证字段的方法 如果无法验证 该字段将被清除并标记为红色 我还希望在框上方弹出一个工具提示 并向用户显示该值无效的消息 有没有办法做到这一点 并且可以控制工具提示显示的时间 我怎样才能让它自己弹出而不是鼠标悬停时弹出 If the
  • Linq-to-entities,在一个查询中获取结果+行数

    我已经看到了有关此事的多个问题 但它们已经有 2 年 或更长 的历史了 所以我想知道这方面是否有任何变化 基本思想是填充网格视图并创建自定义分页 所以 我还需要结果和行数 在 SQL 中 这将类似于 SELECT COUNT id Id N
  • 使用 GROUP 和 SUM 的 LINQ 查询

    请帮助我了解如何使用带有 GROUP 和 SUM 的 LINQ 进行查询 Query the database IEnumerable

随机推荐