有EF Linq Select语句选择常数或函数
本文关键字:常数 函数 选择 语句 EF Linq Select | 更新日期: 2023-09-27 18:08:27
我有一个Select语句,当前格式为
dbEntity
.GroupBy(x => x.date)
.Select(groupedDate => new {
Calculation1 = doCalculation1 ? x.Sum(groupedDate.Column1) : 0),
Calculation2 = doCalculation2 ? x.Count(groupedDate) : 0)
在查询中,doccalculation1和doccalculation2是前面设置的bool。这将在生成的Sql中创建一个case语句,如
DECLARE @p1 int = 1
DECLARE @p2 int = 0
DECLARE @p3 int = 1
DECLARE @p4 int = 0
SELECT (Case When @p1 = 1 THEN Sum(dbEntity.Column1)
Else @p2
End) as Calculation1,
(Case When @p3 = 1 THEN Count(*)
Else @p4
End) as Calculation2
我想要生成的sql是这样的当doccalculation1为真
SELECT SUM(Column1) as Calculation1, Count(*) as Calculation2
和这样当doCalculation2为false
SELECT 0 as Calculation1, Count(*) as Calculation2
是否有办法通过EF强制查询这样做?
编辑:bool doCalculation = true;
bool doCalculation2 = false;
dbEntity
.Where(x => x.FundType == "E")
.GroupBy(x => x.ReportDate)
.Select(dateGroup => new
{
ReportDate = dateGroup.Key,
CountInFlows = doCalculation2 ? dateGroup.Count(x => x.Flow > 0) : 0,
NetAssetEnd = doCalculation ? dateGroup.Sum(x => x.AssetsEnd) : 0
})
.ToList();
生成sql
-- Region Parameters
DECLARE @p0 VarChar(1000) = 'E'
DECLARE @p1 Int = 0
DECLARE @p2 Decimal(5,4) = 0
DECLARE @p3 Int = 0
DECLARE @p4 Int = 1
DECLARE @p5 Decimal(1,0) = 0
-- EndRegion
SELECT [t1].[ReportDate],
(CASE
WHEN @p1 = 1 THEN (
SELECT COUNT(*)
FROM [dbEntity] AS [t2]
WHERE ([t2].[Flow] > @p2) AND ([t1].[ReportDate] = [t2].[ReportDate]) AND ([t2].[FundType] = @p0)
)
ELSE @p3
END) AS [CountInFlows],
(CASE
WHEN @p4 = 1 THEN CONVERT(Decimal(33,4),[t1].[value])
ELSE CONVERT(Decimal(33,4),@p5)
END) AS [NetAssetEnd]
FROM (
SELECT SUM([t0].[AssetsEnd]) AS [value], [t0].[ReportDate]
FROM [dbEntity] AS [t0]
WHERE [t0].[FundType] = @p0
GROUP BY [t0].[ReportDate]
) AS [t1]
,它在执行计划中有许多索引扫描和一个假脱机和一个连接。在测试集上运行平均需要大约20秒,而生产集要大得多。
我希望它的运行速度与
等sql相同select reportdate, 1, sum(AssetsEnd)
from vwDailyFundFlowDetail
where fundtype = 'E'
group by reportdate
平均运行时间约为12秒,并且在执行计划中将大部分查询绑定在单个索引查找中。实际的sql输出是什么并不重要,但是case语句的性能似乎要差得多。
至于我为什么这样做,我需要像我在动态生成Linq选择中要求的那样生成一个动态选择语句。用户可以选择执行一组计算中的一个或多个,在请求到来之前,我不知道选择了什么。这些请求开销很大,所以除非必要,否则我们不希望运行它们。我正在根据用户请求设置doccalculation工具
这个查询应该替换一些代码,这些代码从存储为字符串的硬编码sql查询中插入或删除字符,然后执行。它运行得相当快,但是维护
从技术上讲,可以通过表达式树访问器在Select查询中传递表达式,该访问器检查三元操作符左侧的常量值,并用适当的子表达式替换三元表达式。
例如:public class Simplifier : ExpressionVisitor
{
public static Expression<T> Simplify<T>(Expression<T> expr)
{
return (Expression<T>) new Simplifier().Visit(expr);
}
protected override Expression VisitConditional(ConditionalExpression node)
{
var test = Visit(node.Test);
var ifTrue = Visit(node.IfTrue);
var ifFalse = Visit(node.IfFalse);
var testConst = test as ConstantExpression;
if(testConst != null)
{
var value = (bool) testConst.Value;
return value ? ifTrue : ifFalse;
}
return Expression.Condition(test, ifTrue, ifFalse);
}
protected override Expression VisitMember(MemberExpression node)
{
// Closed-over variables are represented as field accesses to fields on a constant object.
var field = (node.Member as FieldInfo);
var closure = (node.Expression as ConstantExpression);
if(closure != null)
{
var value = field.GetValue(closure.Value);
return VisitConstant(Expression.Constant(value));
}
return base.VisitMember(node);
}
}
使用例子:
void Main()
{
var b = true;
Expression<Func<int, object>> expr = i => b ? i.ToString() : "N/A";
Console.WriteLine(expr.ToString()); // i => IIF(value(UserQuery+<>c__DisplayClass0).b, i.ToString(), "N/A")
Console.WriteLine(Simplifier.Simplify(expr).ToString()); // i => i.ToString()
b = false;
Console.WriteLine(Simplifier.Simplify(expr).ToString()); // i => "N/A"
}
所以,你可以在你的代码中像这样使用它:
Expression<Func<IGrouping<DateTime, MyEntity>>, ClassYouWantToReturn> select =
groupedDate => new {
Calculation1 = doCalculation1 ? x.Sum(groupedDate.Column1) : 0),
Calculation2 = doCalculation2 ? x.Count(groupedDate) : 0
};
var q = dbEntity
.GroupBy(x => x.date)
.Select(Simplifier.Simplify(select))
然而,这可能比它的价值更多的麻烦。SQL Server将毫无疑问地优化"1 == 1"的情况,允许实体框架产生不太漂亮的查询不应该被证明是一个性能问题。
更新查看更新的问题,这似乎是少数几个产生正确查询的实例之一,在性能方面确实很重要。
除了我建议的解决方案之外,还有其他一些选择:您可以使用原始sql映射到您的返回类型,或者您可以使用LinqKit根据您想要的选择不同的表达式,然后在Select查询中"调用"该表达式。