执行延迟的 IQueryable<T>来自动态 Linq? [英] Execution-Deferred IQueryable&lt;T&gt; from Dynamic Linq?

查看:27
本文介绍了执行延迟的 IQueryable<T>来自动态 Linq?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 Dynamic Linq 来执行一些查询(抱歉,这是我唯一的选择).结果,我得到了 IQueryable 而不是 IQueryable.就我而言,我想要一个 IQueryable,其中 Thing 是一个具体类型.

I am using Dynamic Linq to perform some queries (sorry but it's my only option). As a result, I am getting an IQueryable instead of an IQueryable<T>. In my case, I want an IQueryable<Thing> where Thing is a concrete type.

我的查询是这样的:

public IQueryable<Thing> Foo(MyContext db)
{
    var rootQuery = db.People.Where(x => x.City != null && x.State != null);
    var groupedQuery = rootQuery.GroupBy("new ( it.City, it.State )", "it", new []{"City", "State"});
    var finalLogicalQuery = groupedQuery.Select("new ( Count() as TotalNumber, Key.City as City, Key.State as State )");
    var executionDeferredResults = finalLogicalQuery.Take(10); // IQueryable

    IQueryable<Thing> executionDeferredTypedThings = ??; // <--- Help here!!!!

    return executionDeferredTypedThings;
}

我的东西.cs:

public class Thing
{
    public int TotalNumber { get; set; }
    public string City { get; set; }
    public string State { get; set; }
}

是的,我知道可以在没有 Dynamic Linq 的情况下完成上述的事情,但是我有一些可变性,我已经简化了这里.如果我的返回类型只是 IQueryable,我可以让它与我的可变性一起工作,但我无法弄清楚如何在保持它执行的同时转换为 IQueryable-推迟,同时也让实体框架满意.我确实有动态 Select 总是返回一些看起来像Thing的东西(带有正确的数据).但我根本无法弄清楚如何返回 IQueryable 并且可以在那里使用一些帮助.谢谢!!

Yes, I know the exact above thing can be done without Dynamic Linq but I have some variableness going on that I've simplified out of here. I can get it to work with my variableness if my return type is simply IQueryable but I can't figure out how to convert to IQueryable<Thing> while keeping it execution-deferred and while also keeping Entity Framework happy. I do have the dynamic Select always returning something (with the correct data) that looks like a Thing. But I simply can't figure how to return the IQueryable<Thing> and could use some help there. Thanks!!

基于Rex M的建议,我现在正在尝试使用AutoMapper来解决这个问题(虽然我并不致力于这种方法,也愿意尝试其他方法).对于 AutoMapper 方法,我是这样做的:

Based on Rex M's suggestion, I am now trying to use AutoMapper to solve this problem (although I am not committed to this approach and am willing to try other approaches). For the AutoMapper approach, I am doing it as such:

IQueryable<Thing> executionDeferredTypedThings = executionDeferredResults.ProjectTo<Thing>(); // <--- Help here!!!!

但这会导致 InvalidOperationException:

But this results in an InvalidOperationException:

缺少从 DynamicClass2 到 Thing 的映射.使用 Mapper.CreateMap 创建.

Missing map from DynamicClass2 to Thing. Create using Mapper.CreateMap.

问题是,虽然我已经定义了 Thing,但我还没有定义 DynamicClass2,因此,我无法映射它.

The thing is, while I have defined Thing, I have not defined DynamicClass2 and as such, I cannot map it.

IQueryable<Thing> executionDeferredTypedThings = db.People.Provider.CreateQuery<Thing>(executionDeferredResults.Expression);

这给出了一个 InvalidCastException 并且似乎与上述 AutoMapper 失败命中相同的潜在问题:

This gives an InvalidCastException and seems to be the same underlying problem that the above AutoMapper fail hits:

无法将System.Data.Entity.Infrastructure.DbQuery"1[DynamicClass2]"类型的对象转换为System.Linq.IQueryable"1[MyDtos.Thing]"类型.

Unable to cast object of type 'System.Data.Entity.Infrastructure.DbQuery'1[DynamicClass2]' to type 'System.Linq.IQueryable'1[MyDtos.Thing]'.

推荐答案

如果我理解正确的话,下面的扩展方法应该可以帮到你

If I understand correctly, the following extension method should do the job for you

public static class DynamicQueryableEx
{
    public static IQueryable<TResult> Select<TResult>(this IQueryable source, string selector, params object[] values)
    {
        if (source == null) throw new ArgumentNullException("source");
        if (selector == null) throw new ArgumentNullException("selector");
        var dynamicLambda = System.Linq.Dynamic.DynamicExpression.ParseLambda(source.ElementType, null, selector, values);
        var memberInit = dynamicLambda.Body as MemberInitExpression;
        if (memberInit == null) throw new NotSupportedException();
        var resultType = typeof(TResult);
        var bindings = memberInit.Bindings.Cast<MemberAssignment>()
            .Select(mb => Expression.Bind(
                (MemberInfo)resultType.GetProperty(mb.Member.Name) ?? resultType.GetField(mb.Member.Name),
                mb.Expression));
        var body = Expression.MemberInit(Expression.New(resultType), bindings);
        var lambda = Expression.Lambda(body, dynamicLambda.Parameters);
        return source.Provider.CreateQuery<TResult>(
            Expression.Call(
                typeof(Queryable), "Select",
                new Type[] { source.ElementType, lambda.Body.Type },
                source.Expression, Expression.Quote(lambda)));
    }
}

(旁注:坦率地说,我不知道 values 参数的用途,但添加了它以匹配相应的 DynamicQueryable.Select 方法签名.)

(Side note: Frankly I have no idea what values argument is for, but added it to match the corresponding DynamicQueryable.Select method signature.)

所以你的例子会变成这样

So your example will become something like this

public IQueryable<Thing> Foo(MyContext db)
{
    var rootQuery = db.People.Where(x => x.City != null && x.State != null);
    var groupedQuery = rootQuery.GroupBy("new ( it.City, it.State )", "it", new []{"City", "State"});
    var finalLogicalQuery = groupedQuery.Select<Thing>("new ( Count() as TotalNumber, Key.City as City, Key.State as State )");  // IQueryable<Thing>
    var executionDeferredTypedThings = finalLogicalQuery.Take(10);
    return executionDeferredTypedThings;
}

工作原理

这个想法很简单.

DynamicQueryable 中的 Select 方法实现看起来像这样

The Select method implementation inside the DynamicQueryable looks something like this

public static IQueryable Select(this IQueryable source, string selector, params object[] values)
{
    if (source == null) throw new ArgumentNullException("source");
    if (selector == null) throw new ArgumentNullException("selector");
    LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, null, selector, values);
    return source.Provider.CreateQuery(
        Expression.Call(
            typeof(Queryable), "Select",
            new Type[] { source.ElementType, lambda.Body.Type },
            source.Expression, Expression.Quote(lambda)));
}

它的作用是动态创建一个选择器表达式并将其绑定到源Select 方法.我们采用完全相同的方法,但在修改由 DynamicExpression.ParseLambda 调用创建的选择器表达式之后.

What it does is to dynamically create a selector expression and bind it to the source Select method. We take exactly the same approach, but after modifying the selector expression created by the DynamicExpression.ParseLambda call.

唯一的要求是投影使用new (...)"语法并且投影属性的名称和类型匹配,我认为这适合您的用例.

The only requirement is that the projection is using "new (...)" syntax and the names and types of the projected properties match, which I think fits in your use case.

返回的表达式是这样的

(source) => new TargetClass
{
    TargetProperty1 = Expression1(source),
    TargetProperty2 = Expression2(source),
    ...
}

其中 TargetClass 是动态生成的类.

where TargetClass is a dynamically generated class.

我们想要的只是保留源部分,并用所需的类/属性替换目标类/属性.

All we want is to keep the source part and just replace that target class/properties with the desired class/properties.

至于实现,首先用

var bindings = memberInit.Bindings.Cast<MemberAssignment>()
    .Select(mb => Expression.Bind(
        (MemberInfo)resultType.GetProperty(mb.Member.Name) ?? resultType.GetField(mb.Member.Name),
        mb.Expression));

然后将 new DynamicClassXXX { ... } 替换为

var body = Expression.MemberInit(Expression.New(resultType), bindings);

这篇关于执行延迟的 IQueryable<T>来自动态 Linq?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆