【发布时间】:2014-04-30 10:07:22
【问题描述】:
准备好进行精彩的大脑锻炼了吗?
背景:我使用 C# Entity Framework 5 编写了一个通用的“查询生成器”框架,它允许用户使用一个看起来像控件的复合查询来查询一个基于复合查询的“根”表像这样:
在上图中,根表为“City”,Sql 查询将自动包含所有必要的相关表,编译为:
select ci.* from City ci
join Country cr on ci.CountryID = cr.ID
where cr.ContinentID = 2 -- 2=Europe
or (cr.Name like '%z%'
and ci.Population > 10000000)
代码架构反映了我为对此建模而创建的表结构(我已经混淆了一些与我们的讨论无关的字段):
在 EF 中,这用继承术语表示如下:
- 一切都源自
QueryClause,在模型中标记为抽象。QueryClause有两种:-
SimpleQueryClause,其中有一个MetricType,您可以将其设置为适合您的QueryBuilder实现的枚举值(例如,Continent、CountryName、CityPopulation在这种情况下)、Comparator(=、>=、contains等)和Value(表示比较器右侧值的序列化值)。暂时不用担心MetricExternalID;它适用于更复杂的子句类型。 -
CompoundQueryClause只是一个父QueryClause,AnyConditionSufficient字段只是表明它的直接子是用“And”还是“Or”聚合的。请注意,QueryClause上的ParentQueryClauseID是CompoundQueryClause的 FK,因为SimpleQueryClause不能是父级。
-
-
QueryRoot是CompoundQueryClause的一个特例,顾名思义,是查询的根。
所以无论如何,这个结构运行得很好,我有代码可以将所有这些转换成 Expression<> 树,可以为一组预定义的过滤器输出 SQL(每个过滤条件由 Expression<Func<T, bool>> 表示(T 是City,在我们的示例中),并且 EF 将其全部转换为 SQL。也许不是最优化的 SQL,我承认,我遇到了链接太多嵌套表达式的限制,但就我们的目的而言,它的工作方式类似于一个梦。
问题:用户非常喜欢这个框架,而且它运行得非常好,以至于他们认为它一定很容易开发,所以他们只想要一点额外的功能。他们想知道,在查询返回的记录中,SimpleQueryClauses 导致了每一行的返回。
因此,例如,假设我们示例中的查询返回(以及其他)以下记录:
- 瑞士日内瓦(人口 200,000)
- 英国伦敦(人口 15,000,000)
- 巴西里约热内卢(人口 11,800,000)
我们希望向用户展示以下满意的条款:
- 日内瓦:“大陆 = 欧洲”。我们不会显示“Country Name contains 'z'”,因为我们只对人口超过 1000 万的情况感兴趣,而事实并非如此。
- 伦敦:“大陆 = 欧洲”。日内瓦的反面;人口超过 10,000,000,但我们不显示它,因为国家名称不包含“z”
- 巴西:“国家名称包含 'z'”、“人口 > 10,000,000”。由于这两个“和”条件都满足,我们将它们都返回。
- 我想不出满足所有 3 个条件的任何欧洲城市,但如果有,我们会全部显示。换句话说,我们不会简化“或”条件,但我们可以简化“和”条件。
所以我考虑到这一点,并通过迭代结果集、根据逻辑和/或规则遍历查询树、编译每个简单的查询子句以查看或多或少地提出了概念验证如果那个单独的子句是真还是假,并采取相应的行动。
这种方法的问题是它非常慢,因为它充满了“选择 n+1”的问题:我的结果集一开始是 IQueryable<City>,但为了找出有关大陆的详细信息,我必须为每个城市加载相关的Country 对象。好的,将.Include(ci=>ci.Country) 打到IQueryable 上可能不会造成巨大的惩罚,但是如果我的一个可能的过滤子句对一对多关系(例如“客户数量”)进行聚合怎么办?在我的记录集中包含city.Customers 是不可想象的,但我需要能够计算它们。
那么,您能想出任何优化此过程的聪明方法吗?要么将其全部转换为 SQL,要么以不会创建“选择 n+1”模式的方式在代码中执行它?或者也许还有第三种更聪明的方法?
【问题讨论】:
-
只是一个小提示,仅仅因为你在一个表上查询并不意味着你必须
.Include它 -
@Thewads - 当然,当然不是......我有没有表明我认为你必须这样做?
-
就在你说要统计城市的时候。客户你需要把他们包括在你的记录集中
-
@Thewads 如果我想在通过预加载所有相关记录来遍历结果行时消除 select n+1 问题,那么我会的。但在这种情况下,这两种选择都不是特别美味。
标签: c# sql-server-2012 entity-framework-5 expression-trees