【问题标题】:Apply "List" of IQueryables to a target?将 IQueryables 的“列表”应用于目标?
【发布时间】:2018-04-25 08:42:09
【问题描述】:

我的想法是创建一个执行不同类型操作的 IQueryable 的“列表”。

所以基本上:

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
};

现在,我也有这个充满“人员”的 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-framework iqueryable


【解决方案1】:

这是可能的,但您需要稍微重新构建您的方法。


存储过滤器

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);

读懂您的意图,您实际上并不想处理IQueryable 对象,而是您提供给Where 方法的参数。

编辑:我错过了第三个是Select(),而不是Where()。我已经调整了答案的其余部分,好像这也是Where()。请参阅下面的 cmets,了解我对您为什么不能轻松地将 Select()Where() 混合使用的回复。

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

这会存储相同的信息(过滤条件),但它不会将每个过滤器包装在自己的 IQueryable 中。

简短说明

注意Func&lt;A,B&gt; 符号。在这种情况下,A 是输入类型(Person),B 是输出类型(bool)。

这可以进一步扩展。 Func&lt;string,Person,bool&gt; 有两个输入参数(stringPerson)和一个输出参数(bool)。一个用法示例:

Func&lt;string, Person, bool&gt; filter = (inputString, inputPerson) =&gt; inputString == "TEST" &amp;&amp; inputPerson.Age &gt; 35;

总是有一个输出参数(最后一种)。所有其他提到的类型都是输入参数


将过滤器放入列表中

直观地说,因为Func&lt;Person,bool&gt; 表示单个过滤器;您可以使用List&lt;Func&lt;Person,bool&gt;&gt; 表示过滤器列表。

嵌套泛型类型有点难以阅读,但它确实像任何其他 List&lt;T&gt; 一样工作。

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

执行过滤器

你很幸运。因为您想应用所有过滤器(逻辑 AND),所以您可以通过堆叠它们来非常轻松地做到这一点:

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

或者,如果您使用的是List&lt;Func&lt;Person,bool&gt;&gt;

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

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

边缘案例

但是,如果您尝试查找符合一个或多个过滤器(逻辑 OR)的所有项目,则会变得稍微困难​​一些。

The full answer is quite complicated.。你可以在这里查看。

但是,假设您在已知变量中设置了过滤器,there is a simpler method

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

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

【讨论】:

【解决方案2】:

其中一个问题是 IQueryable 的集合仅在您的 DbSet 有效时才有效。一旦您的 DbContext 被 Disposed,您精心填充的集合就一文不值。

所以你必须想出另一种方法来重建查询而不是使用DbSet&lt;Person&gt;的方法

虽然乍一看它们看起来一样,但IEnumerableIQueryable 之间还是有区别的。一个 Enumerable 包含所有可以枚举结果序列的东西。

另一方面,Queryable 包含一个 Expression 和一个 Provider。 Provider 知道可以从哪里获取数据。这通常是一个数据库,但它也可以是一个 CSV 文件或其他可以获取序列的项目。 Provider 的任务是解释 Expression 并将其转换为数据库可以理解的格式,通常是 SQL。

将 linq 语句连接成一个大的 linq 语句时,不会访问数据库。只更改了表达式。

一旦您调用 GetEnumerator() 和 MoveNext()(通常通过执行 ForEach 或 ToList() 或类似方法),Expression 将发送给 Provider,后者会将其转换为数据库可以理解的查询格式并执行询问。查询结果是一个Enumerable序列,所以调用了提供者查询结果的Getenumerator()和MoveNext()。

因为你的 IQueryable 持有这个 Provider,所以在 Provider 被释放后你就不能再枚举了。

当使用实体框架时,DbSet 持有 Provider。 Provider 中是 DbContext 持有的数据库信息。处理 DbContext 后,您将无法再使用 IQueryable:

IQueryable<Person> query = null;
using (var dbContext = new MyDbcontext())
{
     query = dbContext.Persons.Where(person => person.Age > 20);
}
foreach (var person in query)
{
      // expect exception: the DbContext is already Disposed
}

因此,您不能将 Provider 放入您的集合或可能的查询中。但是,您可以记住表达式。您对表达式的唯一要求是它返回一个人。您还需要一个函数,该函数采用此表达式和 Person 的 QueryaProvider 将其转换为 IQueryable。

让我们为此创建一个通用函数,这样它就可以用于任何类型,而不仅仅是 Persons:

static IQueryable<TSource> ToQueryable<TSource>(this IQueryProvider provider, 
    Expression expression)
{
     return provider.CreateQuery(expression);
}

// 好吧,让我们也添加以下内容:

static IQueryable<Tsource> ToQueryable<TSource>(this DbContext dbContext, 
     Expression expression)
{
     return dbContext.Set<TSource>.Provider.ToQueryable<TSource>(expression);
}

有关扩展功能的帮助,请参阅Extension Functions Demystified

现在只有在您创建了表达式集合后。为了快速查找,请将其设为字典:

enum PersonQuery
{
    ByFirstname,
    ByAge,
    ByHasChildWithAgeOver,
    Skip,
}

public IReadOnlyDictionary<PersonQuery, Expression> CreateExpressions()
{
    Dictionary<PersonQuery, Expression> dict = new Dictionary<PersonQuery, Expression>();
    using (var dbContext = new MyDbContext())
    {
         IQueryable<Person> queryByFirstName = dbContext.Persons
             .Where(...);
         dict.Add(PersonQuery.ByfirstName, queryByFirstName.Expression);
         ... // etc for the other queries
    }
    return dict.
}

用法:

IReadOnlyCollection<Person> PerformQuery(PersonQuery queryId)
{
     using (var dbContext = new MyDbContext())
     {
          // get the Expression from the dictionary:
          var expression = this.QueryDictionary[queryId];
          // translate to IQueryable:
          var query = dbContext.ToQueryable<Person>(expression);
          // perform the query:
          return query.ToList();

          // because all items are fetched by now, the DbContext may be Disposed
     }
}

【讨论】:

  • 谢谢!对于我上面的示例,这应该是一个有效的“胚胎”:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-10
  • 2013-03-05
  • 1970-01-01
  • 2017-08-23
  • 2021-04-23
  • 1970-01-01
相关资源
最近更新 更多