Conditionally ignore fields with LINQ select

dynamic-linq entity-framework-core expression-trees linq linqkit

Question

I've been unable to find any examples where you can conditionally exclude fields based off a variable within a select projection in LINQ, see also LINQ: Select an object and change some properties without creating a new object.

Let me give some background on what I'm trying to achieve. I want to limit some fields in the DTO being set from the model based on if a user can edit data (i.e. a comment field). For example, the following select with a delegate named CustomerView.

var qry = _ctx.Customer.Select(CustomerView(User.IsInRole("Editor")));

The Customer model has an Orders navigation property and the following function transforms the data into the CusomerViewModel DTO.

private Expression<Func<Customer, CustomerViewModel>> CustomerView(bool isEditor) {
    return c => new CustomerViewModel
    {
        Id = c.Id,
        Name = c.Name,
        Comment = isEditor ? c.Comment : null,
        OrderCount = c.Orders.Count()
    };
}

This will generate SQL like CASE WHEN @__isEditor_0 = TRUE THEN Comment ELSE NULL which works, but I'd prefer the expression not even be generated, i.e. field left as it's default. That is a simple use case, but if I wanted to do the same with the OrderCount field a SQL subquery would still get included.

Of course I could create a another function for non-editor users that excludes certain fields, but I'd rather not have separate projections to maintain especially when they are more complex.

I see questions where dynamic LINQ is used for where clauses but not that many for select. Is this approach feasible?

Edit: Is there anyway to manually remove fields from an expression tree after a select has been used, maybe through an extension method?

1
0
3/12/2020 3:46:46 AM

Accepted Answer

Using LINQKit I was able to achieve the desired result by adding AsExpandable() to select.

var qry = _ctx.Customer.AsExpandable().Select(CustomerView(User.IsInRole("Editor")));

Then adding an expression for comment field and calling Invoke() on the field assignment.

private Expression<Func<Customer, CustomerViewModel>> CustomerView(bool isEditor) {
    Expression<Func<Customer, string>> exprComment;
    if (isEditor)
        exprComment = c => c.Comment;
    else
        exprComment = c => null;

    return c => new CustomerViewModel
    {
        Id = c.Id,
        Name = c.Name,
        Comment = exprComment.Invoke(c),
        OrderCount = c.Orders.Count()
    };
}

It does appear to work, but I would still be interested to hear of any alternative approaches.

0
3/12/2020 4:03:28 AM


Related Questions





Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow