Home > OS >  Passing lists of properties of generic type to function
Passing lists of properties of generic type to function

Time:09-25

I am working on an extension to Entity Framework Core for bulk operations. I have created a BulkInsert method that works well:

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

Now, I'm working on a method for BulkInsertOrUpdate. I would like the method signature to look something like this:

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

I made up the "TProperty" - not sure that even exists. I've done some research and found a number of solutions for passing properties using lamda expressions - typically in some usage such as specifying an "order by" column for sorting a list of generic typed objects. Maybe I'm not being creative enough, but I haven't come up with a solution that would allow me to apply that approach here. I could just pass lists of strings and then check the lists against the properties of the generic class at runtime, but that isn't very developer-friendly. Any ideas?

Sample Usage: Suppose I have a database called FooDB. Inside of FooDB I have a table called Person. I am merging a list of Person records from some source. Some of the records may be new, others may be existing records that need to be updated. In this case, I want to look up the record by combination of FirstName, LastName, DateOfBirth. If a record exists with the same FirstName, LastName, DateOfBirth combination, I will update the Address, City, State, Zip on the existing record. Otherwise, I will insert a new record. I would want to call my function something like this...

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

Note: In creating this example, I came to the realization that I am probably not looking for TProperty, but for IProperty... but, how do I specify that the IProperty values need to be properties of class T?

CodePudding user response:

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

    }

Is this what you are looking for?

You can have more than one generic type in a function (T and TProperty in this case).

While we are at it, why not add a generic type constraint like so:

    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

CodePudding user response:

I propose the following signature:

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

And usage:

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