0

下面的代码块回答了这个问题:“如何使用 linq 扩展方法执行左外连接?

var qry = Foo.GroupJoin(
      Bar, 
      foo => foo.Foo_Id,
      bar => bar.Foo_Id,
      (x,y) => new { Foo = x, Bars = y })
.SelectMany(
      x => x.Bars.DefaultIfEmpty(),
      (x,y) => new { Foo = x, Bar = y});

您如何将此 GroupJoin 和 SelectMany 编写为 MethodCallExpressions? 我发现的所有示例都是使用 DynamicExpressions 将字符串转换为 lambdas 编写的(另一个示例)。如果可能的话,我想避免依赖该库。

上面的查询可以用表达式和相关方法编写吗?

我知道如何构造基本的 lambda 表达式,例如foo => foo.Foo_Id使用 ParameterExpressions MemberExpressions 和 Expression.Lambda() ,但是你如何构造(x,y) => new { Foo = x, Bars = y })??? 能够构造必要的参数来创建两个调用?

MethodCallExpression groupJoinCall =
         Expression.Call(
           typeof(Queryable),
           "GroupJoin",
           new Type[] { 
                  typeof(Customers), 
                  typeof(Purchases), 
                  outerSelectorLambda.Body.Type, 
                  resultsSelectorLambda.Body.Type 
               },
                           c.Expression,
                           p.Expression,
                           Expression.Quote(outerSelectorLambda),
                           Expression.Quote(innerSelectorLambda),
                           Expression.Quote(resultsSelectorLambda)
                        );
MethodCallExpression selectManyCall =
   Expression.Call(typeof(Queryable), 
    "SelectMany", new Type[] { 
        groupJoinCall.ElementType, 
        resultType, 
        resultsSelectorLambda.Body.Type 
    }, groupJoinCall.Expression, Expression.Quote(lambda), 
    Expression.Quote(resultsSelectorLambda))); 

最终,我需要创建一个可重复的过程,将 join n Bars 留给 Foo。因为我们有一个垂直数据结构,所以需要一个左连接查询来返回表示为 Bars 的内容,以允许用户对 Foo 进行排序。要求是允许用户按 10 个条进行排序,但我不希望他们使用超过三个。我尝试编写一个将上面第一个块中的代码链接多达 10 次的过程,但是一旦我通过了 5 Visual Studio 2012 开始变慢并且大约 7 它被锁定。

因此,我现在正在尝试编写一个方法,该方法返回 selectManyCall 并根据用户的请求多次递归调用自身。

根据下面在 LinqPad 中工作的查询,需要重复的过程只需要手动处理 Expression 对象中的透明标识符。查询 sorts 返回按 Bars 排序的 Foos(在本例中为 3 个 Bars)。

一个旁注。这个过程在 OrderBy 委托中进行连接要容易得多,但是,它产生的查询包括 T-SQL “OUTER APPLY”,Oracle 不支持它,这是必需的。

我很感激关于如何将投影写入匿名类型或任何其他开箱即用的想法的任何想法。谢谢你。

var q = Foos
        .GroupJoin (
            Bars, 
            g => g.FooID, 
            sv => sv.FooID, 
            (g, v) => 
                new  
                {
                    g = g, 
                    v = v
                }
        )
        .SelectMany (
            s => s.v.DefaultIfEmpty (), 
            (s, v) => 
                new  
                {
                    s = s, 
                    v = v
                }
        )
        .GroupJoin (
            Bars, 
            g => g.s.g.FooID, 
            sv => sv.FooID, 
            (g, v) => 
                new  
                {
                    g = g, 
                    v = v
                }
        )
        .SelectMany (
            s => s.v.DefaultIfEmpty (), 
            (s, v) => 
                new  
                {
                    s = s, 
                    v = v
                }
        )
        .GroupJoin (
            Bars,  
            g => g.s.g.s.g.FooID, 
            sv => sv.FooID, 
            (g, v) => 
                new  
                {
                    g = g, 
                    v = v
                }
        )
        .SelectMany (
            s => s.v.DefaultIfEmpty (),  
            (s, v) => 
                new  
                {
                    s = s, 
                    v = v
                }
        )
        .OrderBy (a => a.s.g.s.g.v.Text)
        .ThenBy (a => a.s.g.v.Text)
        .ThenByDescending (a => a.v.Date)
        .Select (a => a.s.g.s.g.s.g);
4

1 回答 1

1

如果您在弄清楚如何生成表达式时遇到问题,您总是可以从编译器那里获得帮助。你可以做的是用你要查询的类型声明一个 lambda 表达式并编写 lambda。编译器将为您生成表达式,您可以检查它以查看哪些表达式构成了表达式树。

例如,您的表达式等价于使用查询语法(或者如果您愿意,您可以使用方法调用语法)

Expression<Func<IQueryable<Foo>, IQueryable<Bar>, IQueryable>> expr =
    (Foo, Bar) =>
        from foo in Foo
        join bar in Bar on foo.Foo_Id equals bar.Foo_Id into bars
        from bar in bars.DefaultIfEmpty()
        select new
        {
            Foo = foo,
            Bar = bar,
        };

要回答您的问题,您不能真正生成创建匿名对象的表达式,实际类型在编译时是未知的。你可以通过创建一个虚拟对象来作弊,并使用GetType()它来获取它的类型,然后你可以使用它来创建适当的新表达式,但这更像是一种肮脏的黑客行为,我不建议这样做。这样做,您将无法生成强类型表达式,因为您不知道匿名类型的类型。

例如,

var dummyType = new
{
    foo = default(Foo),
    bars = default(IQueryable<Bar>),
}.GetType();

var fooExpr = Expression.Parameter(typeof(Foo), "foo");
var barsExpr = Expression.Parameter(typeof(IQueryable<Bar>), "bars");
var fooProp = dummyType.GetProperty("foo");
var barsProp = dummyType.GetProperty("bars");
var ctor = dummyType.GetConstructor(new Type[]
{
    fooProp.PropertyType,
    barsProp.PropertyType,
});
var newExpr = Expression.New(
    ctor,
    new Expression[] { fooExpr, barsExpr },
    new MemberInfo[] { fooProp, barsProp }
);
// the expression type is unknown, just some lambda
var lambda = Expression.Lambda(newExpr, fooExpr, barsExpr);

每当您需要生成一个涉及匿名对象的表达式时,正确的做法是创建一个已知类型并使用它来代替匿名类型。是的,它的用途有限,但它是处理这种情况的一种更清洁的方法。然后至少您将能够在编译时获取类型。

// use this type instead of the anonymous one
public class Dummy
{
    public Foo foo { get; set; }
    public IQueryable<Bar> bars { get; set; }
}
var dummyType = typeof(Dummy);

var fooExpr = Expression.Parameter(typeof(Foo), "foo");
var barsExpr = Expression.Parameter(typeof(IQueryable<Bar>), "bars");
var fooProp = dummyType.GetProperty("foo");
var barsProp = dummyType.GetProperty("bars");
var ctor = dummyType.GetConstructor(Type.EmptyTypes);

var newExpr = Expression.MemberInit(
    Expression.New(ctor),
    Expression.Bind(fooProp, fooExpr),
    Expression.Bind(barsProp, barsExpr)
);
// lambda's type is known at compile time now
var lambda = Expression.Lambda<Func<Foo, IQueryable<Bar>, Dummy>>(
    newExpr,
    fooExpr,
    barsExpr);

或者,您可以在表达式中使用元组,而不是创建和使用虚拟类型。

static Expression<Func<T1, T2, Tuple<T1, T2>>> GetExpression<T1, T2>()
{
    var type1 = typeof(T1);
    var type2 = typeof(T2);
    var tupleType = typeof(Tuple<T1, T2>);

    var arg1Expr = Expression.Parameter(type1, "arg1");
    var arg2Expr = Expression.Parameter(type2, "arg2");
    var arg1Prop = tupleType.GetProperty("Item1");
    var arg2Prop = tupleType.GetProperty("Item2");
    var ctor = tupleType.GetConstructor(new Type[]
    {
        arg1Prop.PropertyType,
        arg2Prop.PropertyType,
    });

    var newExpr = Expression.New(
        ctor,
        new Expression[] { arg1Expr, arg2Expr },
        new MemberInfo[] { arg1Prop, arg2Prop }
    );
    // lambda's type is known at compile time now
    var lambda = Expression.Lambda<Func<T1, T2, Tuple<T1, T2>>>(
        newExpr,
        arg1Expr,
        arg2Expr);
    return lambda;
}

然后使用它:

var expr = GetExpression<Foo, IQueryable<Bar>>();
于 2013-02-24T05:53:05.130 回答