0

I am trying to have a select expression that can be incrementally updated depending on what I receive from the input, something like this:

    // Init expression
    Expression<Func<Order, object>> selectExpression = x => new
    {
        x.Id
    };

    if(selectInput.Title){
        // something like this
        selectExpression = selectExpression.Add(x => new
        {
            x.Title
        });
    }
    if(selectInput.ClientFullName){
        // something like this
        selectExpression = selectExpression.Add(x => new
        {
            ClientFullname = x.Client.Fullname
        });
    }

    // Use expression in a LINQ query (for an EF Core query)
    var result = await queryable.Select(selectExpression).ToListAsync();

Then I would expect to have a result like this

    {
        "Id": 568,
        "Title": "Order 567",
        "ClientFullname": "John Smith"
    }

Is this something that is possible? The only examples I found online are about .Where(). Am I going into the wrong direction?

Thank you!

J Flex
  • 312
  • 1
  • 3
  • 11
  • This is a lot harder than it appears. Those anonymous types are generated by the compiler at compile-time: you can't just dynamically add a `ClientFullname` field onto a type at runtime. It would be easier (but still a little bit fiddly) to use a Dictionary – canton7 Jul 15 '20 at 09:52
  • Does this answer your question? [How to create LINQ Expression Tree to select an anonymous type](https://stackoverflow.com/questions/606104/how-to-create-linq-expression-tree-to-select-an-anonymous-type) – Eldar Jul 15 '20 at 09:57
  • You just have a Dictionary – jdweng Jul 15 '20 at 10:11
  • The whole point of LINQ is to have the structure of the query known at compile time. If that isn't the case, you're way better off using older tools for querying databases that, by their very nature, don't statically define the schema of the results of the query. You're just creating more work for yourself than you're saving trying to use LINQ for this. – Servy Jul 15 '20 at 16:10

2 Answers2

0

If you would do this, then the problem is, that your compiler would not know whether property ClientFullName is a property in your result or not, thus your compiler can't use it.

Of course you can use the name of the property as an identifier, and put everything in a Dictionary. like @Canton7 suggested.

If you don't expect null values, you can return a class with all properties. The properties that have value null will not have been selected:

class MyItem
{
    public int Id {get; set;}              // Id always fetched
    public string Title {get; set;}
    public DateTime? Date {get; set;}
    ... // etc: all nullable properties
}

As an extension method:

public static IQueryable<MyItem> ToSelectedItems(
    this IQueryable<Order> orders,
    SelectedInput selectedInput)
{
    // TODO: exception if orders or selectedInput null

    return orders.Select(order => new MyItem
    {
        Id = order.Id,

        // properties null if not in selectedInput
        Title = selectedInput.Title ? order.Title : null,
        ClientFullName = selectedInput.ClientFullName ? order.ClientFullName : null,
        Date = selectedInput.Date ? order.Date : null,
     })

Usage would be:

// We only want the Title and the Date (for example)
SelectedInput propertiesToFetch = new SelectedInput
{
    Title = true,
    Date = true,
    // all other properties are automatically false
}

// the query to fetch the orders that you want (not executed yet!)
IQueryable<Order> myOrders = dbContext.Orders
    .Where(order => ...)
    ...;


// Select only the properties that you want
IQueryable<MyItem> myQuery = myOrders.ToSelectedItems(propertiesToFetch);

The query is still not executed. To execute the query:

List<MyItem> myFetchedData = myQuery.ToList();

Of course you can put the complete query in one big LINQ statement. This does hardly influence efficiency. However it will deteriorate readability:

var myFetchedData = dbContext.Orders
    .Where(order => ...)
    .ToSelectedData(new SelectedInput
    {
        Title = true,
        Date = true,
    })
    .ToList();
Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116
  • Wow, this is a very clever way to do it! I will try it and report back here with the results. Thanks! – J Flex Jul 15 '20 at 10:17
-1

You might find LinqKit useful: (http://www.albahari.com/nutshell/linqkit.aspx) I have used this many times when trying to write variable where clauses against IQueryable data sources. The PredicateBuilder (http://www.albahari.com/nutshell/predicatebuilder.aspx) is really useful for constructing dynamic query expressions.

Sample code would be something like this...

    public void QueryItems(string id, string name, string fullname)
    {
        // this would be the collection od data to be filtered
        var items = new List<Item>();

        // initialise predicate of for querying objects of type Item
        var predicate = PredicateBuilder.New<Item>();

        // dynamically add clauses dependent on available filter values
        if (!string.IsNullOrEmpty(id))
        {
            predicate = predicate.And(x => x.Id == id);
        }
        if (!string.IsNullOrEmpty(name))
        {
            predicate = predicate.And(x => x.Name == name);
        }
        if(!string.IsNullOrEmpty(fullname))
        {
            predicate = predicate.And(x => x.FullName == fullname);
        }

        // evaluate the result of the dynamic query
        var result = items.Where(predicate).ToList();
    }
Ben Wesson
  • 589
  • 6
  • 16