确定" Where"条款满意

时间:2014-04-30 10:07:23

标签: c# sql-server-2012 entity-framework-5 expression-trees

准备好进行良好的大脑锻炼吗?

背景:我已经编写了一个通用"查询生成器"框架使用C#Entity Framework 5,它允许用户查询" root"基于复合查询的表,使用如下所示的控件:

enter image description here

在上图中,根表将是" 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)

代码体系结构反映了我为模拟它而创建的表结构(我已经混淆了一些与我们的讨论无关的字段):

enter image description here

在EF中,这用继承术语表示如下:

  • 一切都来自QueryClause,在模型中标记为抽象。 QueryClause有两种:
    1. SimpleQueryClause,其MetricType设置为适合QueryBuilder实施的枚举值(例如ContinentCountryName },{在这种情况下为CityPopulation),Comparator=>=contains等)和Value(表示比较器右侧的值)。现在不要担心MetricExternalID;它是一种更复杂的条款。
    2. CompoundQueryClause只是一个父QueryClauseAnyConditionSufficient字段只是指示其直接子项是否与"和"聚合在一起。或者"或"。请注意,ParentQueryClauseID上的QueryClauseCompoundQueryClause的FK,因为SimpleQueryClause不能是父级。
  • QueryRootCompoundQueryClause的一个特例,正如其名称所暗示的那样,是查询的根。

所以无论如何,这个结构运行良好,我有代码将所有这些转换为Expression<>树,可以为一组预定义过滤器输出SQL(每个过滤条件由Expression<Func<T, bool>>表示( T在我们的示例中是City,并且EF将它全部转换为SQL。也许不是最优的SQL,我会授予您,并且我遇到了链接太多的限制嵌套表达式,但就我们的目的而言,它就像一个梦想。

问题:用户非常喜欢这个框架,并且它的工作非常好,以至于他们认为它必须易于开发,因此他们只需要一个额外的功能。他们想知道在查询返回的记录中,SimpleQueryClauses导致每行返回。

因此,例如,让我们说我们示例中的查询返回(以及其他)以下记录:

  • 瑞士日内瓦(人口200,000)
  • 英国伦敦(人口15,000,000)
  • 巴西里约热内卢(人口11,800,000)

我们希望向用户显示以下满意条款:

  • 日内瓦:&#34;大陆=欧洲&#34;。我们没有显示&#34;国家/地区名称包含&#39;&#39;&#34;,因为如果人口超过1000万,我们只对此类案件感兴趣,而不是。< / LI>
  • 伦敦:&#34;大陆=欧洲&#34;。与日内瓦相反;人口超过10,000,000,但我们无法显示,因为国名不包含&#39;
  • 巴西:&#34;国家/地区名称包含&#39;&#39;&#34;,&#34;人口&gt; 10,000,000&#34 ;.既然那些&#34;和&#34;条件得到满足,我们将它们都归还。
  • 我无法想象任何满足所有三个条件的欧洲城市,但如果有的话,我们会全部展示它们。换句话说,我们不会做短路&#34;或者#34;条件,但我们可以捷径&#34;和&#34;条件。

所以我想到了这个并提出了一个或多或少运作的概念证明,通过遍历结果集,根据逻辑和/或规则遍历查询树,编译每个简单查询子句以查看如果该个别条款是真或假,并采取相应行动。

这种方法的问题在于它非常慢,因为它充满了&#34;选择n + 1&#34;问题:我的结果集以IQueryable<City>开头,但为了找到有关该大陆的详细信息,我必须为每个城市加载相关的Country对象。好吧,将.Include(ci=>ci.Country)打到IQueryable上可能不是一个巨大的惩罚,但如果我的一个可能的过滤条款对一对多关系进行聚合,例如&#34;客户数量&#34;?在我的记录集中包含city.Customers是不可想象的,但我需要能够计算它们。

那么,你能想到任何优化这个过程的聪明方法,要么将它全部翻译成SQL,要么以不会创建&#34;选择n + 1&的方式在代码中进行。 #34;图案?或许还有第三种方式更聪明?

1 个答案:

答案 0 :(得分:2)

如果能够为每个查询条件提供拉伸结果列数。 您可以尝试将生成的sql更改为:

case when Condition1 then 1 else 0 end as Condition

使用OP中的示例:

select ci.*,
case when cr.ContinentID = 2 then 1 else 0 end as ContinentIsEurope,
case when cr.Name like '%z%' 
    and ci.Population > 10000000 then 1 else 0 end as InventEasilyIdentifiableCondionName 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)

不确定这是否具有高性能,并且可能会生成非常长的SQL查询