Search code examples
c#linqlinq-to-sqlentity-framework-core

How can I combine two LINQ select expressions?


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!


Solution

  • 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();