如何用表达式树编写linq .join来进行动态查询

问题描述

我似乎在任何地方都找不到答案...

目标是找到一种使用表达式树语句执行linq .join()的方法。

所以...使用Microsoft example

class Person
{
    public string Name { get; set; }
}

class Pet
{
    public string Name { get; set; }
    public Person Owner { get; set; }
}

public static void JoinEx1()
{
    Person magnus = new Person { Name = "Hedlund,Magnus" };
    Person terry = new Person { Name = "Adams,Terry" };
    Person charlotte = new Person { Name = "Weiss,Charlotte" };

 

   Pet barley = new Pet { Name = "Barley",Owner = terry };
    Pet boots = new Pet { Name = "Boots",Owner = terry };
    Pet whiskers = new Pet { Name = "Whiskers",Owner = charlotte };
    Pet daisy = new Pet { Name = "Daisy",Owner = magnus };


List<Person> people = new List<Person> { magnus,terry,charlotte };
List<Pet> pets = new List<Pet> { barley,boots,whiskers,daisy };

// Join the list of Person objects and the list of Pet objects
// to create a list of person-pet pairs where each element is
// an anonymous type that contains the name of pet and the name
// of the person that owns the pet.
var query = people.AsQueryable().Join(pets,person => person,pet => pet.Owner,(person,pet) =>
                    new { OwnerName = person.Name,Pet = pet.Name });

有人可以帮助我如何使用Expression trees进行.join()吗?

解决方法

由于Queryable.Join是一种通用的静态方法,因此最简单的方法是使用扩展方法来查找MethodInfo以查找所需的对象:

public static class TypeExt {
    public static MethodInfo GetMethod(this Type t,string methodName,int paramCount) =>
        t.GetMethods().Where(mi => mi.Name == methodName && mi.GetParameters().Length == paramCount).Single();
}

Join方法有五个参数(扩展方法将它们所应用的对象作为第一个参数传递),因此我们将一次将它们构建为Expression。这五个参数是IQueryable<>,要连接的IEnumerable<>,外键选择器lambda,内键选择器lambda和结果选择器lambda。

// Build Queryable.Join<TOuter,TInner,TKey,TResult> and use as query expression

// IQueryable<TOuter>
var arg0 = Expression.Constant(people.AsQueryable());

// IEnumerable<TInner>
var arg1 = Expression.Constant(pets);

// TOuter person
var arg2p = Expression.Parameter(people.GetType().GetGenericArguments()[0],"person");
// also TKey person
// Expression<Func<TOuter,TKey>>: person => person
var arg2 = Expression.Quote(Expression.Lambda(arg2p,arg2p));

// TInner pet
var arg3p = Expression.Parameter(pets.GetType().GetGenericArguments()[0],"pet");
// TKey pet.Owner
var arg3body = Expression.Property(arg3p,"Owner");
// Expression<Func<TInner,TKey>>: pet => pet.Owner
var arg3 = Expression.Quote(Expression.Lambda(arg3body,arg3p));

// TResult = typeof(new { string OwnerName,string Pet })
var anonymousType = (new { OwnerName = default(string),Pet = default(string) }).GetType();
// .ctor
var arg4Constructor = anonymousType.GetConstructors()[0];
// person.Name
var arg4PersonName = Expression.Property(arg2p,"Name");
// pet.Name
var arg4PetName = Expression.Property(arg3p,"Name");
var arg4Args = new[] { arg4PersonName,arg4PetName };
// new[] { .OwnerName,.Pet }
var arg4Members = anonymousType.GetProperties();
// new { OwnerName = person.Name,Pet = pet.Name }
var arg4body = Expression.New(arg4Constructor,arg4Args,arg4Members);
// Expression<Func<TOuter,TResult>>: (person,pet) => new { OwnerName = person.Name,Pet = pet.Name }
var arg4 = Expression.Quote(Expression.Lambda(arg4body,arg2p,arg3p));

注意:出于复杂的嵌套lambda关闭原因,每个lambda都被Expression.Quote包围,因此Expression编译器将知道返回Expression树而不是委托。在此示例中,没有区别。

现在,使用扩展方法,您可以查找所需的Join,将其从通用方法实例化为要查询的类型的特定方法,并创建Join方法调用表达式:

var joinGenericMI = typeof(Queryable).GetMethod("Join",5);
var joinMI = joinGenericMI.MakeGenericMethod(new[] { arg2p.Type,arg3p.Type,arg2.ReturnType,anonymousType });
var qExpr = Expression.Call(joinMI,arg0,arg1,arg2,arg3,arg4);

最后,您可以使用IQueryable<>创建一个Expression

var q2 = people.AsQueryable().Provider.CreateQuery(qExpr);

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...