首页 > 解决方案 > 将 IQueryables 的“列表”应用于目标?

问题描述

我有这个想法来创建一个执行不同类型操作的 IQueryables 的“列表”。

所以基本上:

var query1 = Enumerable.Empty<Person>().AsQueryable().Where(e => e.Name == "Ronald");
var query2 = Enumerable.Empty<Person>().AsQueryable().Where(e => e.Age == 43);
var query3 = Enumerable.Empty<Person>().AsQueryable().Select(e => e.EyeColor);

var listOfQueries = new List<IQueryable<Person>
{
    query1,
    query2,
    query3
};

现在,我也有这个充满“Persons”的 DbSet,我想“应用”我对那个 DbSet 的所有查询。我该怎么做?甚至可能吗?

一个更新的例子:

        var personQueryFactory = new PersonQueryFactory();
        var personQueryByFirstname = personQueryFactory.CreateQueryByFirstname("Ronald");           //Query by Firstname.
        var personQueryByAge = personQueryFactory.CreateQueryByAge(42);                             //Query by age.
        var personQueryByHasChildWithAgeOver = personQueryFactory.CreateQueryByChildAgeOver(25);    //Query using a "join" to the child-relationship.
        var personQuerySkip = personQueryFactory.Take(5);                                           //Only get the 5 first matching the queries.

        var personQuery = personQueryFactory.AggregateQueries                                       //Aggragate all the queries into one single query.
        (
            personQueryByFirstname,
            personQueryByAge,
            personQueryByHasChildWithAgeOver,
            personQuerySkip
        );

        var personSurnames = personsService.Query(personQuery, e => new { Surname = e.Surname });          //Get only the surname of the first 5 persons with a firstname of "Ronald" with the age 42 and that has a child thats over 25 years old.
        var personDomainObjects = personsService.Query<DomainPerson>(personQuery);          //Get the first 5 persons as a domain-object (mapping behind the "scenes") with a firstname of "Ronald" with the age 42 and that has a child thats over 25 years old.
        var personDaos = personsService.Query(personQuery);          //Get the first 5 persons as a DAO-objects/entityframework-entities with a firstname of "Ronald" with the age 42 and that has a child thats over 25 years old.

这样做的原因是创建一种更“统一”的方式来创建和重用预定义查询,然后能够针对 DbSet 执行它们并将结果作为域对象而不是“实体”返回框架模型/对象"

标签: c#entity-frameworkiqueryable

解决方案


This is possible, but you need to reframe your approach slightly.


Storing the filters

var query1 = Enumerable.Empty<Person>().AsQueryable().Where(e => e.Name == "Ronald");
var query2 = Enumerable.Empty<Person>().AsQueryable().Where(e => e.Age == 43);
var query3 = Enumerable.Empty<Person>().AsQueryable().Select(e => e.EyeColor);

Reading your intention, you don't actually want to handle IQueryable objects, but rather the parameter that you supply to the Where method.

Edit: I missed that the third one was a Select(), not a Where(). Ive adjusted the rest of the answer as if this had also been a Where(). See the comments below for my response as to why you can't mix Select() with Where() easily.

Func<Person,bool> filter1  = (e => e.Name == "Ronald");
Func<Person,bool> filter2  = (e => e.Age == 43);
Func<Person,bool> filter3  = (e => e.EyeColor == "Blue");

This stores the same information (filter criteria), but it doesn't wrap each filter in an IQueryable of its own.

A short explanation

Notice the Func<A,B> notation. In this case, A is the input type (Person), and B is the output type (bool).

This can be extended further. A Func<string,Person,bool> has two input parameters (string, Person) and one output parameter (bool). A usage example:

Func<string, Person, bool> filter = (inputString, inputPerson) => inputString == "TEST" && inputPerson.Age > 35;

There is always one output parameter (the last type). Every other mentioned type is an input parameter.


Putting the filters in a list

Intuitively, since Func<Person,bool> represents a single filter; you can represent a list of filters by using a List<Func<Person,bool>>.

Nesting generic types get a bit hard to read, but it does work just like any other List<T>.

List<Func<Person,bool>> listOfFilters = new List<Func<Person,bool>>()
{
    (e => e.Name == "Ronald"),
    (e => e.Age == 43),
    (e => e.EyeColor == "Blue")
};

Executing the filters

You're in luck. Because you want to apply all the filters (logical AND), you can do this very easily by stacking them:

var myFilteredData = myContext.Set<Person>()
                                 .Where(filter1)
                                 .Where(filter2)
                                 .Where(filter3)
                                 .ToList();

Or, if you're using a List<Func<Person,bool>>:

var myFilteredData = myContext.Set<Person>().AsQueryable();

foreach(var filter in listOfFilters)
{
    myFilteredData = myFilteredData.Where(filter);
}

Fringe cases

However, if you were trying to look for all items which fit one or more filters (logical OR), it becomes slightly more difficult.

The full answer is quite complicated.. You can check it here.

However, assuming you have the filters set in known variables, there is a simpler method:

Func<Person, bool> filterCombined = 
              e => filter1(e) || filter2(e) || filter3(e);

var myFilteredData = myContext.Set<Person>()
                                 .Where(filterCombined)
                                 .ToList();

推荐阅读