0

In my c# EF Core 6 application I am looking for a solution to dynamically choose the fetched columns in the SELECT statement. I found an interesting approach here enter link description here that allows to dynamically generate an object[] array with the columns to be fetched. Based on the linked question, the code looks for instance like this:

List<Expression> initializers = new List<Expression>();
var topicExprParam = Expression.Parameter(typeof(Topic), "topic");
if (addComp1)
{
    Expression<Func<Topic, object>> comp1Expr = topic => topic.Comp1;
    initializers.Add(new ParameterSubstituter(topicExprParam).Visit(comp1Expr.Body));
}
if (addComp2)
{
    Expression<Func<Topic, object>> comp2Expr = topic => topic.Comp2;
    initializers.Add(new ParameterSubstituter(topicExprParam).Visit(comp2Expr.Body));
}
if (addComp3)
{
    Expression<Func<Topic, object>> comp3Expr = topic => topic.Comp3;
    initializers.Add(new ParameterSubstituter(topicExprParam).Visit(comp3Expr.Body));
}

// eventually some more elements ...
NewArrayExpression newArrExpr = Expression.NewArrayInit(typeof(object), initializers);
Expression<Func<Topic, object[]>> lambda = Expression.Lambda<Func<Topic, object[]>>(newArrayExpr, topicExprParam);
using (var dbContext = _contextFactory.CreateDbContext())
{
    var myDynamicList = await dbContext.Topic
        .Where(x => ...) // some filters
        .Select(lambda)
        .ToListAsync<dynamic>();

    // not dynamic, hard-coded selected properties and object-array indexes ....
    var myTopicRecordsList = myDynamicList.Select(x => new Topic { Comp1 = x[0], Comp2 = x[1], Comp3 = x[2] }).ToList();
}

The problem of the above code is that the result list needs hard-coded selected properties and object-array indexes. Maybe somebody finds the possibility to have also this part generated dynamically. Alternatively instead of generating a dynamic expression corresponding to this:

Query.Select(topic => new object[] { topic.Comp1, topic.Comp2, topic.Comp3 });

I am looking for a preferred solution generating a dynamic expression corresponding to this:

Query.Select(topic => new TopicDTO { TheComp1 = topic.Comp1, TheComp2 = topic.Comp2, TheComp3 = topic.Comp3 });

where the DTO class looks like this:

public class TopicDTO
{
public int TheComp1 { get; set; }
public int TheComp2 { get; set; }
public int TheComp3 { get; set; }
public int TheComp4 { get; set; }
public int TheComp5 { get; set; }
// and so on
}

Does anyone know how to adapt the above code to construct dynamically a LINQ expression corresponding to this query based on a DTO class? The idea is to decide dynamically which DTO elements should be filled from the corresponding database table columns. The solution should not be based on strings containing the names of the columns

RickyTad
  • 281
  • 1
  • 3
  • 15
  • A Raw SQL uses a string for the Command Text. So you can create a dynamic string and then use linq on the Raw SQL. See : https://learn.microsoft.com/en-us/ef/core/querying/sql-queries – jdweng Aug 13 '23 at 23:30
  • 1
    It's common to use Automapper for this: https://automapper.org/ – David Browne - Microsoft Aug 13 '23 at 23:48
  • 1
    What do you mean by "dynamically choose the fetched columns". Do you want the user to choose which columns to query? Or is this a problem automapper can solve, where the EF Core properties will always map to the same DTO properties every time it is executed? – Jeremy Lakeman Aug 14 '23 at 02:36
  • There is an extension with Automapper called: ``ProjectTo`` and can be used on ``IQueryable``. – sa-es-ir Aug 14 '23 at 03:33
  • The DB table has 25 columns and 10 columns are relevant for the application. The DTO contains the 10 columns. Which of the 10 are to be fetched is decided over bool variables at runtime (means lot of combinations are possible). For performance reasons only the absolutely necessary columns should be fetched from the database, no fetch everything and filter on client side. Building a dynamic string with raw sql is not an option. I need a code sample to generate dynamically a expression for the SELECT statement depending on the bool flags as in the code I posted, but with DTO instead of object[] – RickyTad Aug 14 '23 at 04:30
  • Do you want a method that return DTOs, where only selected properties are loaded? Then the noseleted properties will have the default value. – vernou Aug 14 '23 at 07:39
  • How to know the selected properties? `string[]`? – vernou Aug 14 '23 at 07:39
  • There is no problem regarding the default values for unselected properties, it is important not to fetch them from the database. There is a boolean flag for the "validity" of each property from the DTO. I know the selected properties from the DTO by checking the boolean flag for each property – RickyTad Aug 14 '23 at 07:50
  • Get [BuildSelector](https://stackoverflow.com/a/66334073/10646316) from my answer and use in the following way: `Query.Select(ExpressionHelpers.BuildSelector("Comp1, Comp2, Comp3"))` – Svyatoslav Danyliv Aug 14 '23 at 08:29
  • Thx, I will check the BuildSelector. What if the property names in Topic and TopicDTO are not identical? Can the hard-coded strings be avoided? – RickyTad Aug 14 '23 at 08:38
  • It will be a problem. I can create any projection function but specify all requirements. – Svyatoslav Danyliv Aug 14 '23 at 08:40
  • I will think about. At first approach I could use only the "Topic" class: BuildSelector("Comp1, Comp2, Comp3")) , as the properties set to default values dont bother me. Is it possible to cache the Type information for the Topic class, to avoid using reflection at each call? – RickyTad Aug 14 '23 at 08:50
  • You can cache projection by key `(TSource, TTarget, propNames)`, other has no sense. – Svyatoslav Danyliv Aug 14 '23 at 09:27
  • Would be possible to use an expression instead of propNames as part of the key ? Something like (TSource, Expression> ? For instance a dictionary with the value (typeof(Topic) , Expression> propSelector = x => x.Comp1) as key? I would prefer to avoid using hard-coded strings for the names of the properties. Instead of that a solution based on caching the PropertyInfo extracted from the MemberExpression would be preferred – RickyTad Aug 14 '23 at 20:02
  • 1
    Add function definition to question and usage example. – Svyatoslav Danyliv Aug 15 '23 at 01:02

0 Answers0