Robin Maben
Robin Maben

Reputation: 23054

Linq Collection gets reset on next iteration of foreach

I have the following foreach expression within which I am building a predicate and then filtering the collection by executing .Where().

But what stumps me is, result.Count() gives me 0 even before I execute .Where() in the next iteration.

var result = SourceCollection;

foreach (var fieldName in FilterKeys)
{
    if (!conditions.ContainsKey(fieldName)) continue;
    if (!conditions[fieldName].IsNotNullOrEmpty()) continue;
    var param = conditions[fieldName];
    Func<BaseEntity, bool> predicate = (d) => fieldName != null && d.GetFieldValue(fieldName).ContainsIgnoreCase(param);
    result =  result.Where(predicate);
}

Does, anybody know of any LINQ behavior that I might have overlooked that is causing this?

Upvotes: 2

Views: 559

Answers (2)

Enigmativity
Enigmativity

Reputation: 117055

I agree that the issue comes from not capturing the foreach variable, but there is a deeper issue and that is the combination of LINQ with imperative control flow structures - i.e. mixing LINQ & foreach in the first place.

Try this instead:

var predicates =
    from fieldName in FilterKeys
    where conditions.ContainsKey(fieldName)
    let param = conditions[fieldName]
    where param.IsNotNullOrEmpty()
    select (Func<BaseEntity, bool>)
        (d => fieldName != null
              && d.GetFieldValue(fieldName).ContainsIgnoreCase(param));

var result = predicates.Aggregate(
    SourceCollection as IEnumerable<BaseEntity>,
    (xs, p) => xs.Where(p));

No foreach loop needed and the code stays purely in LINQ. Friends shouldn't let friends mix imperative and functional... :-)

Upvotes: 3

Henrik
Henrik

Reputation: 23324

I think, you want this:

var result = SourceCollection;

foreach (var fieldName in FilterKeys)
{
    if (!conditions.ContainsKey(fieldName)) continue;
    if (!conditions[fieldName].IsNotNullOrEmpty()) continue;
    var param = conditions[fieldName];
    var f = fieldName
    Func<BaseEntity, bool> predicate = (d) => f != null && d.GetFieldValue(f).ContainsIgnoreCase(param);
    result =  result.Where(predicate);
}

Notice the use of f in the predicate. You don't want to capture the foreach variable. In your original code, when the second iteration starts, param is still the captured value from the first iteration, but fieldName has changed.

Upvotes: 6

Related Questions