具有子查询的表达式树

时间:2018-04-14 04:24:58

标签: c# linq expression-trees

我的目标是为动态全文搜索创建子查询表达式树。在SQL中,它将等同于

SELECT * 
FROM MV 
WHERE MV.ID IN (SELECT ID  
                FROM MVF 
                WHERE title = "foo" OR Description = "foo")

因此,基本思想是创建FTS子查询,从中获取ID并将其用于In谓词。我的问题在于第二部分。

// Get subquery for FTS tables
ParameterExpression ftsParam = Expression.Parameter(typeof(MVF), "mvfts");
var wphrase = Expression.Constant("foo");
var methodInfo = typeof(string).GetMethod("Equals", new Type[] { typeof(string) });

var ftsID = Expression.Property(ftsParam, "ID");
var ftsTitle = Expression.Property(ftsParam, "Title");
var ftsDescrip = Expression.Property(ftsParam, "Description");

var texp = Expression.Call(ftsTitle, methodInfo, wphrase);
var dexp = Expression.Call(ftsDescrip, methodInfo, wphrase);
var ftsExp = Expression.Or(texp, dexp);


// Now get ids from the above fts resultset
// THE ASSIGNMENT BELOW THROWS
var selectExp = Expression.Call(typeof(IEnumerable<MVF>), "Select", new Type[]
        {
           typeof(long)
        },
        ftsExp,
        Expression.Lambda<Func<MFV, long>>(
            ftsID,
            ftsParam
        )
);

// Now set up MV table reference
ParameterExpression vParam = Expression.Parameter(typeof(MV), "mv");
var mvID = Expression.Property(vParam, "MVID");
var containsInfo = typeof(IEnumerable<long>).GetMethod("Contains", new Type[] { typeof(long) });

// Now combine expression to get those mvs with ids in the result set of fts query
var containsExp = Expression.Call(selectExp, containsInfo, mvID);
return Expression.Lambda<Func<MV, bool>>(containsExp, vParam);

例外是:

  

没有通用方法'选择'类型   'System.Collections.Generic.IEnumerable`1 [MVF]'与。兼容   提供的类型参数和参数。不应该有类型参数   如果方法是非通用的,则提供。

2 个答案:

答案 0 :(得分:2)

所讨论的表达式所需的两种方法都是Enumerable类的静态通用扩展方法(最重要的是静态泛型):< / p>

Enumerable.Select

public static IEnumerable<TResult> Select<TSource, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, TResult> selector
)

Enumerable.Contains

public static bool Contains<TSource>(
    this IEnumerable<TSource> source,
    TSource value
)

“调用”此类方法最方便的方法是以下Expression.Call方法重载:

public static MethodCallExpression Call(
    Type type,
    string methodName,
    Type[] typeArguments,
    params Expression[] arguments
)

Type type参数是定义被调用方法的类的类型(在本例中为typeof(Enumerable)),Type[] typeArguments是具有泛型类型参数类型的数组(对于非泛型方法为空,对于{ typeof(TSource), typeof(TResult) }应为Select,对{ typeof(TSource) }Contains

将其应用于您的场景:

var selectExp = Expression.Call(
    typeof(Enumerable), 
    "Select",
    new { typeof(MFV), typeof(long) },
    ftsExp,
    Expression.Lambda<Func<MFV, long>>(ftsID, ftsParam)
);

var containsExp = Expression.Call(
    typeof(Enumerable),
    "Contains",
    new [] { typeof(long) },
    selectExp,
    mvID
);

答案 1 :(得分:1)

typeof(IEnumerable<long>)未定义select方法。 Linq Select方法是通过反射不能直接看到的扩展方法。定义它们的类是Enumerable。但是typeof(Enumerable)不起作用,因为该方法是通用的。您必须首先从类枚举中获取泛型方法,然后使用MethodInfo.MakeGenericMethod创建一个采用长参数的方法。

var method = typeof(Enumerable).GetMethods().First(m => m.Name == "Select" &&
                                    m.GetParameters().Last().ParameterType.GetGenericArguments().Length == 2);
var genericSelectFromLongToLong = method.MakeGenericMethod(new Type[] {typeof(long), typeof(long)});

请注意,检查泛型参数而不是匹配的参数计数可能会更好。