我有一个通用的方法,我想指定要检索的IQueryable
,要用作ID的字段,以及要返回的字段的名称。
但我收到错误:
方法''没有支持的SQL转换。
如何正确指定下面的valueExpression
以便它知道如何将表达式转换为SQL?我在这里做错了什么?
public void RunTest()
{
Test<DocumentType>(ctx.Query<DocumentType>(), x => x.DocTypeID, x => x.DocType);
}
public void Test<TTable>(IQueryable<TTable> table, Func<TTable, int> idFunc, Expression<Func<TTable, string>> nameExpr)
{
var intVal = 1;
Expression<Func<TTable, bool>> valueExpression = item => idFunc(item) == intVal;
//errors on the Where() here.
var dbName = table.Where(valueExpression).Select(nameExpr).SingleOrDefault();
//make assertions
}
注意:intVal
方法中的Test<>()
将在循环中更改。我在这里简化了这个问题。
答案 0 :(得分:1)
idFunc
必须是Expression
,而不是Func
,以便查询提供程序能够将其转换为SQL。
完成后,您可以使用下面的Compose
方法将id选择器转换为谓词,将该值与通过将表达式与另一个表达式组合而获得的ID进行比较:
public static Expression<Func<TFirstParam, TResult>>
Compose<TFirstParam, TIntermediate, TResult>(
this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], newFirst);
return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
您现在可以写:
public void Test<TTable>(IQueryable<TTable> table,
Expression<Func<TTable, int>> idSelector,
Expression<Func<TTable, string>> nameSelector)
{
int idValue = 1;
var filter = idSelector.Compose(id => id == idValue);
var dbName = table.Where(filter)
.Select(nameSelector)
.SingleOrDefault();
//make assertions
}
答案 1 :(得分:0)
以上稍微简单的变化是
public static void Test<TTable>(IQueryable<TTable> table, Expression<Func<TTable, int>> idFunc, Expression<Func<TTable, string>>> nameExpr)
{
var intVal = 1;
var constant = Expression.Constant(intVal, typeof(int));
var equalExpr = Expression.Equal(idFunc.Body, constant);
var lambaWrap = Expression.Lambda<Func<TTable, bool>>(equalExpr, (ParameterExpression)((MemberExpression)idFunc.Body).Expression);
var dbName = table.Where(lambaWrap).Select(nameExpr).SingleOrDefault();
//assert
}
由于:
这个问题是每次更改变量intVal时,都需要重新计算表达式。要解决这个问题,你需要“抓住”外部变量。由于我不知道如何仅使用表达式,我通常将所有内容包装在另一个lambda表达式中,该表达式返回表达式本身并处理抓取的内部:
public static void Test2<TTable>(IQueryable<TTable> table, Expression<Func<TTable, int>> idFunc, Expression<Func<TTable, string>>> nameExpr)
{
var intVal = 1;
var variableParam = Expression.Parameter(typeof(int));
var equalExpr = Expression.Equal(idFunc.Body, variableParam);
var lambaWrap = Expression.Lambda<Func<TTable, bool>>(equalExpr, (ParameterExpression)((MemberExpression)idFunc.Body).Expression);
var lambdaDoubleWrap = Expression.Lambda<Func<int, Expression<Func<TTable, bool>>>>(lambaWrap, variableParam).Compile();
var dbName = table.Where(lambdaDoubleWrap(intVal)).Select(nameExpr).SingleOrDefault();
//assert
}
先前恒定的lambda包裹现在变为: 问题是“Param_0”来自何处。这是第二个包装发挥作用的地方:
简单阅读:双重包装是一个创建我的func表达式的函数。