我有一些代码生成表达式作为数据库中的“where”语句读取,我正试图加快速度。
下面的示例使where语句与传入值的表的PK匹配:
private Expression MakeWhereForPK(int id)
{
var paramExp = Expression.Parameter(typeof(Brand),"b");
//Expression to get value from the entity
var leftExp = Expression.Property(paramExp,"ID");
//Expression to state the value to match (from the passed in variable)
var rightExp = Expression.Constant(id,typeof(int));
//Expression to compare the two
var whereExp = Expression.Equal(leftExp,rightExp);
return Expression.Lambda<Func<Brand,bool>>(whereExp,paramExp);
}
以上是对问题的简化 - 真实的东西包括让表格查询并找到它的PK等代码。它实际上与你在代码中做的一样有效:
ctx.Brands.Where(b => b.ID = id);
这样做没问题,但是,在进行测试以优化某些事情时,我发现它相当慢 - 做上述1000000次需要大约25秒。如果我省略上面的最后一行(但很明显它没用!)它会更好,所以看起来Expression.Lamba占用了大约2/3的时间,但其余的也不是很好。
如果所有查询都会立即发生,我可以将其转换为IN
样式表达式并生成一次,但不幸的是,这是不可能的,所以我希望的是节省大部分上面的代,只是重用生成的表达式,但传入不同的id
值。
请注意,由于这将传递给Linq,我无法编译表达式以获得我可以在调用时传入的整数参数 - 它必须保留为表达式树。
因此,以下可能是一个简单版本,用于进行计时练习:
Expression<Func<Brand,bool>> savedExp;
private Expression MakeWhereForPKWithCache(int id)
{
if (savedExp == null)
{
savedExp = MakeWhereForPK(id);
}
else
{
var body = (BinaryExpression)savedExp.Body;
var rightExp = (ConstantExpression)body.Right;
//At this point, value is readonly, so is there some otherway to "inject" id,
//and save on compilation?
rightExp.Value = id;
}
return savedExp;
}
我如何重复使用表达式,只是使用不同的id值?
答案 0 :(得分:6)
您可以使用表达式树不必仅包含简单常量的事实,它还可以包含访问的属性。那么,你要做的是创建一个访问某个属性值的表达式,每次只更改该属性,而不是表达式树。
类似的东西:
class ExpressionHolder
{
public int Value { get; set; }
public Expression<Func<Brand, bool>> Expr { get; private set; }
public ExpressionHolder()
{
Expr = MakeWhereForPK();
}
private Expression<Func<Brand, bool>> MakeWhereForPK()
{
var paramExp = Expression.Parameter(typeof(Brand), "b");
var leftExp = Expression.Property(paramExp, "ID");
var rightExp = Expression.Property(Expression.Constant(this), "Value");
var whereExp = Expression.Equal(leftExp, rightExp);
return Expression.Lambda<Func<Brand, bool>>(whereExp, paramExp);
}
}
这比您的代码快500倍:使用Dennis的测量代码,我得到以下结果:
Make expression: 00:00:02.9869921
Replace constant expression: 00:00:02.3332857
Set property: 00:00:00.0056485
答案 1 :(得分:4)
您无法更改表达式树 - 它们是不可变的。但是你可以通过建立自己的访问者来替换常量表达式:
class MyVisitor : ExpressionVisitor
{
private readonly ConstantExpression newIdExpression;
public MyVisitor(int newId)
{
this.newIdExpression = Expression.Constant(newId);
}
public Expression ReplaceId(Expression sourceExpression)
{
return Visit(sourceExpression);
}
protected override Expression VisitConstant(ConstantExpression node)
{
return newIdExpression;
}
}
用法:
var expr = MakeWhereForPK(0); // p => p.ID == 0
var visitor = new MyVisitor(1);
var newExpr = visitor.ReplaceId(expr); p => p.ID == 1
请注意,这会生成现有树的副本。,我没有对此进行性能测试。我不确定,这会更快还是没有。
此代码:
// warming up
var visitor = new MyVisitor(1);
var expr = MakeWhereForPK(0);
visitor.ReplaceId(MakeWhereForPK(0));
var sw = new System.Diagnostics.Stopwatch();
sw.Start();
for (var i = 0; i < 1000000; i++)
{
MakeWhereForPK(i);
}
sw.Stop();
Console.WriteLine("Make expression: {0}", sw.Elapsed);
sw.Restart();
for (var i = 0; i < 1000000; i++)
{
visitor.Visit(expr);
}
sw.Stop();
Console.WriteLine("Replace constant expression: {0}", sw.Elapsed);
Console.WriteLine("Done.");
在我的机器上生成这些结果:
表达:00:00:04.1714254
替换常量表达式:00:00:02.3644953
完成。
看起来访问者比创建新表达式更快。</ p>