执行延迟IQueryable< T>来自Dynamic Linq? [英] Execution-Deferred IQueryable<T> from Dynamic Linq?

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

问题描述

我正在使用 Dynamic Linq 执行一些查询(抱歉,这是我唯一的选择)。因此,我得到一个 IQueryable 而不是一个 IQueryable< T> 。在我的情况下,我想要一个 IQueryable< Thing> 其中 Thing 是具体类型。



我的查询是这样的:

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

IQueryable< Thing> executionDeferredTypedThings = ??; //< ---帮助这里!!!!

return executeDeferredTypedThings;
}

我的Thing.cs:

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

$ p

我有一些变化,我已经简化了这里。如果我的返回类型只是简单的 IQueryable ,但我无法弄清楚如何转换为 IQueryable< Thing> 同时保持执行延迟,同时保持实体框架的快乐。我确实有动态的选择总是返回一些(正确的数据)看起来像一个 Thing 。但是我根本无法想象如何返回 IQueryable< Thing> ,可以在那里使用一些帮助。谢谢!!



尝试失败1



根据 Rex M 的建议,我现在正试图用AutoMapper来解决这个问题(尽管我不是这样做,而是愿意尝试其他方法)。对于AutoMapper方法,我这样做:

  IQueryable< Thing> executionDeferredTypedThings = executionDeferredResults.ProjectTo< Thing>(); //< ---帮助这里!!!! 

但这会导致InvalidOperationException:


从DynamicClass2到Thing的地图丢失。创建使用Mapper.CreateMap。


事情是,虽然我已经定义了 Thing ,我没有定义 DynamicClass2 ,因此我无法映射。



尝试失败2



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

这给出了一个InvalidCastException,并且似乎是上述AutoMapper失败命中的基本问题: p>


无法将类型为System.Data.Entity.Infrastructure.DbQuery'1 [DynamicClass2]的对象输入到System.Linq.IQueryable '1 [MyDtos.Thing]'。



解决方案

如果我理解正确,方法应该为你做这个工作

  public static class DynamicQueryableEx 
{
public static IQueryable< TResult>选择< TResult>(此IQueryable源,字符串选择器,params对象[]值)
{
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)));
}
}

(旁注:坦白说,我不知道什么参数是为,但添加它匹配相应的 DynamicQueryable.Select 方法签名。)



所以你的例子将成为这样的一个例子

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

如何运作



这个想法很简单。



DynamicQueryable 中选择方法实现看起来有点东西像这样

  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)));
}

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



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



返回的表达式是这样的

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

其中 TargetClass 是一个动态生成的类。



我们只想保留源代码部分,只需用所需的类/属性替换目标类/属性。



对于实施,首先,财产分配将转换为

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

然后$ code新的DynamicClassXXX {...} 被替换为

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


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.

My query is as such:

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;
}

My Thing.cs:

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

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!!

Failed Attempt 1

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!!!!

But this results in an InvalidOperationException:

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

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

Failed Attempt 2

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

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

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)));
    }
}

(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;
}

How it works

The idea is quite simple.

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)));
}

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.

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.

The returned expression is something like this

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

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.

As for the implementation, first the property assignments are converted with

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

and then the new DynamicClassXXX { ... } is replaced with with

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

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

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