Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Need to && together an indeterminate number of Func<TEntity, bool>

I'm trying to find a good way to cumulatively apply up to 5 Func's to the same IEnumerable. Here is what I came up with:

private Func<SurveyUserView,bool> _getFilterLambda(IDictionary<string, string> filters)
{
    Func<SurveyUserView, bool> invokeList = delegate(SurveyUserView surveyUserView)
    { 
        return surveyUserView.deleted != "deleted"; 
    };

    if (filters.ContainsKey("RegionFilter"))
    {
        invokeList += delegate(SurveyUserView surveyUserView)
        {
            return surveyUserView.Region == filters["RegionFilter"];
        };
    }

    if (filters.ContainsKey("LanguageFilter"))
    {
        invokeList += delegate(SurveyUserView surveyUserView)
        {
            return surveyUserView.Locale == filters["LanguageFilter"];
        };
    }

    if (filters.ContainsKey("StatusFilter"))
    {
        invokeList += delegate(SurveyUserView surveyUserView)
        { 
            return surveyUserView.Status == filters["StatusFilter"]; 
        };
    }

    if (filters.ContainsKey("DepartmentFilter"))
    {
        invokeList += delegate(SurveyUserView surveyUserView)
        {
            return surveyUserView.department == filters["DepartmentFilter"];
        };
    }

    return invokeList;
}

I thought that it would apply these in a cumulative fashion, however, I can see from the results that it's actually just applying the last one (DepartmentFilter).

There are 2^4 possible combinations so brute-force if/elses are not going to work. (I want to AND using a particular lambda only when the corresponding key is present in the Dictionary.)

EDIT: Here is the solution that I accepted, but it causes a StackOverflowException when it is evaluated. Anybody see why?

private Func<SurveyUserView,bool> _getFilterLambda(IDictionary<string, string> filters )
    {

        Func<SurveyUserView, bool> resultFilter = (suv) => suv.deleted != "deleted";                                                        

        if (filters.ContainsKey("RegionFilter"))
        {
            Func<SurveyUserView, bool> newFilter =
                (suv) => resultFilter(suv) && suv.Region == filters["RegionFilter"];
            resultFilter = newFilter;
        }

        if (filters.ContainsKey("LanguageFilter"))
        {
            Func<SurveyUserView, bool> newFilter =
                 (suv) => resultFilter(suv) && suv.Locale == filters["LanguageFilter"];
            resultFilter = newFilter;
        }

        if (filters.ContainsKey("StatusFilter"))
        {
            Func<SurveyUserView, bool> newFilter =
                (suv) => resultFilter(suv) && suv.Status == filters["StatusFilter"];
            resultFilter = newFilter;
        }

        if (filters.ContainsKey("DepartmentFilter"))
        {
            Func<SurveyUserView, bool> newFilter =
                (suv) => resultFilter(suv) && suv.department == filters["DepartmentFilter"];
            resultFilter = newFilter;
        }

        return resultFilter;
    }

EDIT: Here is the very nice explanation of why this resulted in a StackOverflowException from friend and mentor Chris Flather-

The important thing to understanding why the infinite recursion occurs is understanding when the symbols in a lambda are resolved (i.e. at runtime and not at definition).

Take this simplified example:

Func<int, int> demo = (x) => x * 2;
Func<int, int> demo2 = (y) => demo(y) + 1;
demo = demo2;
int count = demo(1);

If it were resolved statically at definition this would work and be the same as:

Func<int, int> demo2 = (y) => (y * 2) + 1;
Int count = demo2(1);

But it doesn’t actually attempt to figure out what the demo embedded in demo2 does until runtime – at which time demo2 has been redefined to demo. Essentially the code now reads:

Func<int, int> demo2 = (y) => demo2(y) + 1;
Int count = demo2(1);
like image 476
Trey Carroll Avatar asked Jun 01 '12 02:06

Trey Carroll


People also ask

What is the use of need to?

Use "need to" to express that something is important for you to do. This form is often used for something that is important one time, rather than referring to a responsibility or duty.

Which is correct need to or needs to?

“Need” occurs when it is stated for more than one people whereas “Needs” is used when the statement is formed for a single person. But the verb “need to” is written as “I need to”, “You need to”. The use of need can be alternatively “She needs/He needs/It needs” and the examples of needs are “We need/They need”.

Is it grammatically correct need to?

Semi-modal need and main verb need We can use main verb need as an alternative to semi-modal need. Main verb need is followed by to and it changes with person, number and tense (I, you, we, they need to; she, he, it needs to; I, you, she, he, it, we, they needed to).

What is the difference between must have to and need to?

We use 'need' to describe a necessity. It has a very similar meaning to obligation and can therefore be a more polite way of saying 'must' or 'have to'. Unusually, 'need' is both a normal verb and also a modal verb.


2 Answers

Instead of trying to combine the delegates this way, you could build new delegates that use the existing one with your AND condition:

Func<SurveyUserView, bool> resultFilter = (suv) => true;

if (filters.ContainsKey("RegionFilter"))
{
    var tmpFilter = resultFilter;
    // Create a new Func based on the old + new condition
    resultFilter = (suv) => tmpFilter(suv) && suv.Region == filters["RegionFilter"];
}

if (filters.ContainsKey("LanguageFilter"))
{
   // Same as above...

//... Continue, then:

return resultFilter;

That being said, it may be easier to pass your original IQueryable<SurveyUserView> or IEnumerable<SurveyUserView> into this method, and just add .Where clauses directly to filter. You could then return the final query without executing it, with the filters added on.

like image 155
Reed Copsey Avatar answered Sep 18 '22 10:09

Reed Copsey


I would think that using the Where(...) extension on what is, presumably, an IQueryable<SurveyUserView> and return a IQueryable<SurveyUserView> instead of a Func<...>:

// Assuming `q` is a `IQueryable<SurveyUserView>`

if(filters.ContainsKeys["Whatever"])
{
  q = q.Where(suv => suv.Status == filters["Whatever"];
}

The Anding is implicit.

like image 45
bluevector Avatar answered Sep 19 '22 10:09

bluevector