指定 Func 发送到数据库
Specifying Func to send to database
我有一个通用方法,我想在其中指定要从中检索的 IQueryable
、用作 ID 的字段以及 return 的字段名称。
但我收到错误:
Method '' has no supported translation to 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<>()
方法中循环变化。我在这里简化了这个问题。
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
}
上面的一个稍微简单的变体是
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 时,都需要重新计算表达式。要解决这个问题,您需要 "grab" 外部变量。因为我不知道如何仅使用表达式来做到这一点,所以我通常将所有内容都包装在另一个 lambda 表达式中,returns 表达式本身并处理抓取的内部:
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" 从何而来。这是第二个包装发挥作用的地方:
简单阅读:双重换行是创建我的函数表达式的函数。
我有一个通用方法,我想在其中指定要从中检索的 IQueryable
、用作 ID 的字段以及 return 的字段名称。
但我收到错误:
Method '' has no supported translation to 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<>()
方法中循环变化。我在这里简化了这个问题。
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
}
上面的一个稍微简单的变体是
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 时,都需要重新计算表达式。要解决这个问题,您需要 "grab" 外部变量。因为我不知道如何仅使用表达式来做到这一点,所以我通常将所有内容都包装在另一个 lambda 表达式中,returns 表达式本身并处理抓取的内部:
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 换行现在变成了:
简单阅读:双重换行是创建我的函数表达式的函数。