0

I found below piece of code in one of our project. I am stuck up for two days :-( trying to understand Aggregate & LinQKit Expand.

Can you help in transforming below LINQ to normal foreach operation?

public Expression<Func<Purchase, bool>> forTarget(List<string> idList)
{
    Expression<Func<Purchase, string>> fc = p => p.ClientId;
    Expression<Func<Purchase, bool>> predicate = m => false;

    return idList.Aggregate(predicate, (p, id) => p.Or(m => fc.Invoke(m) == id), p => p.Expand());
}

internal class Purchase
{
    public int Price { get; set; }
    public string Description { get; set; }
    public string ClientId { get; set; }
}

public class Client
{
    public string Id { get; set; }
}   

Or atleast, any pointer on what this LINQ expression is doing on the list, would be very much helpful.

return idList.Aggregate(predicate,
        (p, id) => p.Or(m => fc.Invoke(m) == id),
        p => p.Expand());
Abhijeet
  • 13,562
  • 26
  • 94
  • 175

1 Answers1

2

The function iterates over a collection of items and builds a predicate by adding an or condition for each ClientId property value.

In the early versions of Linq2SQL there was no support for method Contains so you weren't able to perform a query like this:

IEnumerable<Purchase> purchases = LoadSelectedItems();
var clientIds = purchases.Select( p => p.ClientId ).ToArray();
var results = db.Clients.Where( c => clientIds.Contains( c.Id )); // Did not work.

The workaround for this problem is to create a predicate that would check using or whether an Id would match a specific value. So, for the above example, if clientIds = {1, 2, 3} the Where clause would be written as:

var results = db.Clients.Where( c => c.Id == 1 || c.Id == 2 || c.Id == 3);

As you can see, this kind of statement is not very elegant, becomes unreadable when the collection of values to check against (i. e. clientIds) is very large and, most important, you cannot know a priori what the values will be to hardcode them. So, to overcome this problem, the solution is to generalize the above predicate with a variable collection of values. And this is done simply with the following algorithm:

  1. Create an Expression that returns false; if we return true the compiler will short-circuit the evaluation (because we're using or) and will return true for all the items;
  2. For each item in the collection of values add an or clause with the value of the item.

Now, your example can be converted to a foreach this way:

// start with a predicate returning false
// this is the seed of the Aggregate method
Expression<Func<Purchase, bool>> predicate = m => false;
// Now, iterate the collection and build the full predicate
foreach( var id in idList)
{
    // Build the predicate by invoking a function which returns the client id of the 
    // purchase and comparing it with the value of the current id from the idList
    predicate = predicate.Or(item => item.ClientId == id);
}

Hope this helps.

RePierre
  • 9,358
  • 2
  • 20
  • 37
  • Thanks! it is really helpful. Any pointers on LinQKit Expand() please? – Abhijeet Oct 21 '14 at 11:12
  • @Abhijeet, unfortunately, there's not much I can say... `Expand()` method tries to "optimize" the lambda expression so that it can be transformed (efficiently) into a SQL query. – RePierre Oct 21 '14 at 11:25