指定Func以发送到数据库

时间:2015-03-23 18:42:19

标签: c# linq generics

我有一个通用的方法,我想指定要检索的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<>()将在循环中更改。我在这里简化了这个问题。

2 个答案:

答案 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
 }

由于: enter image description here

这个问题是每次更改变量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包裹现在变为: enter image description here 问题是“Param_0”来自何处。这是第二个包装发挥作用的地方: enter image description here

简单阅读:双重包装是一个创建我的func表达式的函数。