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