Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reusing Lambda Select Fragments in LINQ

I would like to be able to reuse fragments of my select lambda expressions in my Entity Framework Core 2.0 queries.

For example:

var result = await ctx.Customers
  .Select(cust => new CustomerDto {
    CustomerId = cust.Id,
    CustomerName = cust.Name,
    CurrentValue = cust.Orders
      .Where(order => order.OrderDate >= DateTime.Now.AddDays(-30)
      .Sum(order => order.TotalValue)
    })
    .ToListAsync();

Since I might want to calculate the CurrentValue property in other queries (in practice the sub-query is more complex than this), I would ideally like to refactor the above code to something like:

var result = await ctx.Customers
  .Select(cust => new CustomerDto {
    CustomerId = cust.Id,
    CustomerName = cust.Name,
    CurrentValue = CalculateCustomerCurrentValueExpr(cust)
  })
  .ToListAsync();

I have created Linq predicates using a Linq.Expression, but I have been unable to find a way to use an Expression as an element of the select statement.

Any help would be much appreciated.

Update - Performance with .AsExpandable()/.Invoke()

For anyone interested, I ran some test code ten times which produced the following result:

Standard Inline Code: 17ms (58,609 ticks) With .AsExpandable() and inline code 16ms (58,029 ticks) With .AsExpandable() and .Invoke() 16ms (58,224 ticks)

I suspect that if more test cycles had been run, the average processing time for all three scenarios would have been the same - at least with the level of accuracy I could measure at (simple StopWatch()).

Thanks to all contributors, particularly SergeyA for the solution and Ivan Stoev for the simple explanation of .AsExpandable()

like image 548
Neilski Avatar asked Feb 01 '18 17:02

Neilski


1 Answers

You can reuse expressions with AsExpandable extension from LinqKit liblary (http://www.albahari.com/nutshell/linqkit.aspx).

Example:

Expression<Func<Customer,long>> func = c => c.Orders
  .Where(order => order.OrderDate >= DateTime.Now.AddDays(-30)
  .Sum(order => order.TotalValue);

var result = await ctx.Customers
  .AsExpandable() // this allow to unwrap injected expression
  .Select(cust => new CustomerDto {
    CustomerId = cust.Id,
    CustomerName = cust.Name,
    CurrentValue = func.Invoke(cust) // this inject predefined expression
  })
  .ToListAsync(); 
like image 108
SergeyA Avatar answered Sep 26 '22 01:09

SergeyA