Search code examples
c#.netlinqframeworksentity

Dynamically select LINQ SELECT fields at runtime


I have a LINQ query to a database, with multiple linked tables, and need to return (SELECT) different fields depending on inputs.

ClassA has ParamA, ParamB, and ICollection<ClassB>ClassBs
ClassB has ParamD, ParamE

Portion of the Linq query:

.Select(c => new ClassA()
    {
    ParamA = c.ParamA,
    ParamB = c.ParamB,
    ClassBs = c.ClassBs.Select(p => new ClassB()
        {
        ParamD = p.ParamD,
        ParamE = p.ParamE
        }).ToList()
    }).ToList();

On some calls, I'll want ParamA and ParamE only. On other calls, perhaps ParamB and ParamE. I've been able to make this work using Expressions but not for the ICollection. I attempted using Dynamic.Linq.Core, but couldn't find any examples for newing up in the SELECT. I would prefer to do this with MemberExpressions...

[UPDATE] A bit more context: ClassA and ClassB are essentially EF Models pointing to SQL tables. ClassA has a one-to-many relationship to ClassB, thus, why I query them in this fashion. ClassA would be a Student record (name, address, home, etc), ClassB would be a Test record, where student may have more than one test, each test has date taken, grade, highest_score_of_class, lowest_score_of_class, and many more.

I don't always want all the fields of both tables, as there may be 1 million records, thus, why I prefer to SELECT only what is needed for the specific query and operation requested.


Solution

  • This may be a bit verbose, but Expressions can get that way, for example:

    var typeClassA = typeof(ClassA);
    var typeClassB = typeof(ClassB);
    
    var parameterExpressionP = Expression.Parameter(typeClassB, "p");
    var newExpression = Expression.New(typeClassB);
    
    var paramDPropertyExpression = Expression.Property(parameterExpressionP, "ParamD");
    var paramDMemberBinding = Expression.Bind(typeClassB.GetProperty("ParamD"), paramDPropertyExpression);
    
    var paramEPropertyExpression = Expression.Property(parameterExpressionP, "ParamE");
    var paramEMemberBinding = Expression.Bind(typeClassB.GetProperty("ParamE"), paramEPropertyExpression);
    
    var memberInitExpression = Expression.MemberInit(
        newExpression,
        paramDMemberBinding, paramEMemberBinding);
    
    var projectionExpression = Expression.Lambda<Func<ClassB, ClassB>>(memberInitExpression, parameterExpressionP);
    
    var parameterExpressionC = Expression.Parameter(typeClassA, "c");
    var selectParamExpression = Expression.Property(parameterExpressionC, "ClassBs");
    
    var selectExpression = Expression.Call(
        typeof(Enumerable),
        nameof(Enumerable.Select),
        new[] { typeClassB, typeClassB },
        selectParamExpression, projectionExpression);
    
    var toListExpression = Expression.Call(
        typeof(Enumerable),
        nameof(Enumerable.ToList),
        new[] { typeClassB },
        selectExpression);
    

    This will create an expression something like:

    c.ClassBs.Select(p => new ClassB() {ParamD = p.ParamD, ParamE = p.ParamE}).ToList()