Search code examples
c#.netentity-frameworklinqexpression-trees

How can I use Expression.Lambda for SELECT LINQ operation


I'll start from afar a little. My task is to copy all tables from legacy database to antoher db. The tables of this database were not created by me and it was a long time ago. That's why paginations I use to copy these tables differ a little. First pagination I use works fine. I filter entities using Expression trees and it works with good performance. The code is below.

var parameter = Expression.Parameter(typeof(TDbSet));
var property = Expression.Property(parameter, primaryKeyProperty.Name);
var body = Expression.GreaterThan(property,Expression.Constant(id, typeof(TId)));

entities = await _dbFirst.Set<TDbSet>().AsNoTracking()
      .Where(Expression.Lambda<Func<TDbSet, bool>>(body, parameter))
      .Take(PAGE_SIZE)
      .ToListAsync()
      .ConfigureAwait(false);

Now is the problem. Class for copying table works with generic type parameter TId. For tables with composite PK I take one PK type, and then Select all its Ids through TDbSet (you better look the code below and you'll understand what I am talking about). By these distinct list of id's I take entities from one DB and copy them to another DB. I don't know how to use SELECT operation with Expression trees. That's why I take Id's value by type. It takes 5seconds every time I take them, it is very bad performance because table can contain millions of rows.

var allPkIds = _dbFirst.Set<TDbSet>().AsNoTracking()
         .Select(e => e.GetType().GetProperty(primaryKeyProperty.Name).GetValue(e))
         .Distinct()
         .OrderBy(o => o)
         .Skip(alreadyTakenIdsCounter)
         .Take(COMMIT_SIZE_FOR_COMPOSITE_PK)
         .ToArray();
alreadyTakenIdsCounter += COMMIT_SIZE_FOR_COMPOSITE_PK;

foreach (var entityId in allPkIds)
{
  var body = Expression.Equal(property, Expression.Constant(entityId));

  var entities = await _dbFirst.Set<TDbSet>().AsNoTracking()
                             .Where(Expression.Lambda<Func<TDbSet, bool>>(body, parameter))
                             .ToListAsync()
                             .ConfigureAwait(false);

Now is the question. How can I use Expression.Lambda for such select operation? I googled a lot and read Microsoft documentation but I can't find answer.


Solution

  • Try the following:

    var param = Expression.Parameter(typeof(TDbSet), "e");
    var selectLambda = Expression.Lambda<Func<TDbSet, TId>>(
        Expression.MakeMemberAccess(param, primaryKeyProperty), param);
    
    var allPkIds = _dbFirst.Set<TDbSet>().AsNoTracking()
             .Select(selectLambda)
             .Distinct()
             .OrderBy(o => o)
             .Skip(alreadyTakenIdsCounter)
             .Take(COMMIT_SIZE_FOR_COMPOSITE_PK)
             .ToArray();