【问题标题】:Passing lists of properties of generic type to function将泛型类型的属性列表传递给函数
【发布时间】:2021-09-24 12:30:24
【问题描述】:

我正在开发 Entity Framework Core 的扩展以进行批量操作。我创建了一个运行良好的 BulkInsert 方法:

public static void BulkInsert<T>(this DbContext ctx, List<T> list, bool identityInsert = false, int batchSize=1000)

现在,我正在研究 BulkInsertOrUpdate 的方法。我希望方法签名看起来像这样:

public static void BulkInsertOrUpdate<T>(this DbContext ctx, List<T> list, List<TProperty> columnsToMatch, List<TProperty> columnsToUpdate, bool identityInsert = false, int batchSize=1000)

我编造了“TProperty”——甚至不确定它是否存在。我做了一些研究,发现了许多使用 lamda 表达式传递属性的解决方案——通常在某些用途中,例如指定一个“排序依据”列来对泛型类型对象列表进行排序。也许我没有足够的创造力,但我还没有想出一个可以让我在这里应用这种方法的解决方案。我可以只传递字符串列表,然后在运行时根据泛型类的属性检查列表,但这对开发人员不太友好。有什么想法吗?

示例用法:假设我有一个名为 FooDB 的数据库。在 FooDB 内部,我有一个名为 Person 的表。我正在合并来自某个来源的人员记录列表。一些记录可能是新的,另一些可能是需要更新的现有记录。在这种情况下,我想通过 FirstName、LastName、DateOfBirth 的组合来查找记录。如果存在具有相同 FirstName、LastName、DateOfBirth 组合的记录,我将更新现有记录上的 Address、City、State、Zip。否则,我将插入一条新记录。我想这样称呼我的函数...

fooDbContext.BulkInsertOrUpdate(listOfPersonRecords, new List<IProperty>(){Person.FirstName, Person.LastName, Person.DateOfBirth}, new List<IProperty>(){Person.Address, Person.City, Person.State, Person.Zip});

注意:在创建此示例时,我意识到我可能不是在寻找 TProperty,而是在寻找 IProperty...但是,我如何指定 IProperty 值需要是类 T 的属性?

【问题讨论】:

  • 强烈推荐使用第三方工具,如entityframework核心扩展。
  • 我假设您希望每列具有不同的类型,但是在 c# 中无法表示可变数量的泛型类型参数。所以你需要一些其他的解决方法。
  • 也许我的问题不够清楚。我将添加一个示例用法......另外,我目前正在使用 nuget 上当前可用的 efcore.bulkextensions 扩展,但它有一些我正在尝试解决的限制。

标签: c# entity-framework-core


【解决方案1】:

我提议以下签名:

public static void BulkInsertOrUpdate<T>(this DbContext ctx, List<T> list, 
    Expression<Func<T, object>> columnsToMatch, 
    Expression<Func<T, object>> columnsToUpdate, 
    bool identityInsert = false, int batchSize=1000)
{
    var toMatch = EnumProperties(columnsToMatch.Body).ToList();
    var toUpdate = EnumProperties(columnsToUpdate.Body).ToList();
}

private static IEnumerable<PropertyInfo> EnumProperties(Expression expr)
{
    while (expr.NodeType == ExpressionType.Convert)
    {
        expr = ((UnaryExpression)expr).Operand;
    }

    switch (expr.NodeType)
    {
        case ExpressionType.New:
        {
            var ne = (NewExpression)expr;
            foreach (var argument in ne.Arguments)
            foreach (var property in EnumProperties(argument))
            {
                yield return property;
            }

            break;
        }

        case ExpressionType.MemberAccess:
        {
            if (((MemberExpression)expr).Member is PropertyInfo property)
                yield return property;
            break;
        }
    }
}

及用法:

ctx.BulkInsertOrUpdate(someItems, x => new { x.Key1, x.Key2 }, x => new { x.Field1, x.Filed2 });

【讨论】:

  • 我相信这实际上会传递指定属性的值,不是吗?我想传递实际的属性 - 而不是属性的值。
  • 它会生成NewExpression,你可以分析。
  • 我已经用示例更新了答案。
  • 这似乎有效。我不太了解 EnumProperties 函数,但我会研究它以确保我了解。
  • 嗯,这只是linq2db开发过程中的经验。
【解决方案2】:
    public static void BulkInsertOrUpdate<T, TProperty>(this DbContext ctx,
        List<T> list, List<TProperty> columnsToMatch, List<TProperty> columnsToUpdate,
        bool identityInsert = false, int batchSize = 1000)
    {

    }

这是你要找的吗?

一个函数中可以有多个泛型类型(在本例中为TTProperty)。

既然这样,为什么不像这样添加generic type constraint

    public static void BulkInsertOrUpdate<T, TProperty>(this DbContext ctx,
        List<T> list, List<TProperty> columnsToMatch, List<TProperty> columnsToUpdate,
        bool identityInsert = false, int batchSize = 1000)
           where T : class where TProperty : class

【讨论】:

  • 也许......它至少让我有更多的研究空间。我知道我可以指定 T 是一个类。现在我需要弄清楚如何指定 TProperty 是 T 类的属性。
  • @CMGeek 如果您能详细解释一下,我或许可以帮助您...您要使用反射吗?你想达到什么目的?
  • 最终我想要实现的目标与 SQL“合并”语句相同。我会将数据批量加载到临时表中。使用我添加到问题中的示例,我将首先创建一个临时表(我们称之为 temp_Person)并将数据批量插入该表中。然后,我最终会生成一个合并语句,看起来像“MERGE Person as tgt USING temp_Person as src ON (tgt.FirstName = src.FirstName ...) WHEN MATCHED THEN UPDATE SET tgt.Address = ... WHEN NOT MATCHED THEN插入...”
  • 您提到的所有内容都在linq2db.EntityFrameworkCore 中实现,包括合并和临时表创建以及批量复制到其中。
  • 不寻找第三方库
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多