准备好进行良好的大脑锻炼吗?
背景:我已经编写了一个通用"查询生成器"框架使用C#Entity Framework 5,它允许用户查询" root"基于复合查询的表,使用如下所示的控件:
在上图中,根表将是" 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
字段只是指示其直接子项是否与"和"聚合在一起。或者"或"。请注意,ParentQueryClauseID
上的QueryClause
是CompoundQueryClause
的FK,因为SimpleQueryClause
不能是父级。QueryRoot
是CompoundQueryClause
的一个特例,正如其名称所暗示的那样,是查询的根。所以无论如何,这个结构运行良好,我有代码将所有这些转换为Expression<>
树,可以为一组预定义过滤器输出SQL(每个过滤条件由Expression<Func<T, bool>>
表示( T
在我们的示例中是City
,并且EF将它全部转换为SQL。也许不是最优的SQL,我会授予您,并且我遇到了链接太多的限制嵌套表达式,但就我们的目的而言,它就像一个梦想。
问题:用户非常喜欢这个框架,并且它的工作非常好,以至于他们认为它必须易于开发,因此他们只需要一个额外的功能。他们想知道在查询返回的记录中,SimpleQueryClauses
导致每行返回。
因此,例如,让我们说我们示例中的查询返回(以及其他)以下记录:
我们希望向用户显示以下满意条款:
所以我想到了这个并提出了一个或多或少运作的概念证明,通过遍历结果集,根据逻辑和/或规则遍历查询树,编译每个简单查询子句以查看如果该个别条款是真或假,并采取相应行动。
这种方法的问题在于它非常慢,因为它充满了&#34;选择n + 1&#34;问题:我的结果集以IQueryable<City>
开头,但为了找到有关该大陆的详细信息,我必须为每个城市加载相关的Country
对象。好吧,将.Include(ci=>ci.Country)
打到IQueryable
上可能不是一个巨大的惩罚,但如果我的一个可能的过滤条款对一对多关系进行聚合,例如&#34;客户数量&#34;?在我的记录集中包含city.Customers
是不可想象的,但我需要能够计算它们。
那么,你能想到任何优化这个过程的聪明方法,要么将它全部翻译成SQL,要么以不会创建&#34;选择n + 1&的方式在代码中进行。 #34;图案?或许还有第三种方式更聪明?
答案 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查询