简体   繁体   English

执行 - 延期IQueryable <T> 来自Dynamic Linq?

[英]Execution-Deferred IQueryable<T> from Dynamic Linq?

I am using Dynamic Linq to perform some queries (sorry but it's my only option). 我正在使用Dynamic Linq来执行一些查询(抱歉,这是我唯一的选择)。 As a result, I am getting an IQueryable instead of an IQueryable<T> . 结果,我获得了IQueryable而不是IQueryable<T> In my case, I want an IQueryable<Thing> where Thing is a concrete type. 在我的情况下,我想要一个IQueryable<Thing> ,其中Thing是一个具体的类型。

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. 是的,我知道如果没有Dynamic Linq,上面的确切事情可以完成,但我有一些变量,我已经简化了这里。 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. 如果我的返回类型只是IQueryable ,我可以让它与我的变量一起使用但我无法弄清楚如何转换为IQueryable<Thing>同时保持执行延迟并同时保持实体框架的快乐。 I do have the dynamic Select always returning something (with the correct data) that looks like a Thing . 我有动态Select总是返回的东西(用正确的数据), 看起来像一个Thing But I simply can't figure how to return the IQueryable<Thing> and could use some help there. 但我根本无法想象如何返回IQueryable<Thing>并可以在那里使用一些帮助。 Thanks!! 谢谢!!

Failed Attempt 1 尝试失败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). 根据Rex M的建议,我现在正在尝试使用AutoMapper来解决这个问题(尽管我并不致力于这种方法并愿意尝试其他方法)。 For the AutoMapper approach, I am doing it as such: 对于AutoMapper方法,我这样做:

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

But this results in an InvalidOperationException: 但是这会导致InvalidOperationException:

Missing map from DynamicClass2 to Thing. 缺少从DynamicClass2到Thing的地图。 Create using Mapper.CreateMap. 使用Mapper.CreateMap创建。

The thing is, while I have defined Thing , I have not defined DynamicClass2 and as such, I cannot map it. 问题是,虽然我已经定义了Thing ,但我没有定义DynamicClass2 ,因此我无法映射它。

Failed Attempt 2 尝试失败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: 这会产生InvalidCastException,并且似乎与上述AutoMapper失败的基本问题相同:

Unable to cast object of type 'System.Data.Entity.Infrastructure.DbQuery'1[DynamicClass2]' to type 'System.Linq.IQueryable'1[MyDtos.Thing]'. 无法将类型为'System.Data.Entity.Infrastructure.DbQuery'1 [DynamicClass2]'的对象强制转换为'System.Linq.IQueryable'1 [MyDtos.Thing]'。

You can use AutoMapper's Queryable Extensions to produce an IQueryable which wraps the underlying IQueryable, thus preserving the original IQueryable's IQueryProvider and the deferred execution, but adds in a mapping/translating component to the pipeline to convert from one type to another. 您可以使用AutoMapper的可查询扩展来生成包装底层IQueryable的IQueryable,从而保留原始IQueryable的IQueryProvider和延迟执行,但是将映射/转换组件添加到管道以从一种类型转换为另一种类型。

There's also AutoMapper's UseAsDataSource which makes some common query extension scenarios easier. 还有AutoMapper的UseAsDataSource ,它使一些常见的查询扩展方案更容易。

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.) (旁注:坦率地说,我不知道参数的values是什么,但是添加它以匹配相应的DynamicQueryable.Select方法签名。)

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 DynamicQueryableSelect方法实现看起来像这样

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. 它的作用是动态创建一个选择器表达式并将其绑定到源Select方法。 We take exactly the same approach, but after modifying the selector expression created by the DynamicExpression.ParseLambda call. 我们采用完全相同的方法,但修改了DynamicExpression.ParseLambda调用创建的选择器表达式。

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. 唯一的要求是投影使用“new(...)”语法,并且投影属性的名称和类型匹配 ,我认为这适合您的用例。

The returned expression is something like this 返回的表达式是这样的

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

where TargetClass is a dynamically generated class. 其中TargetClass是动态生成的类。

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 然后将new DynamicClassXXX { ... }替换为

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

Would something like this be of benefit to you? 这样的事情会对你有益吗?

public static IQueryable<TEntity> GetQuery<TEntity>(this DbContext db, bool includeReferences = false) where TEntity : class
    {
        try
        {
            if (db == null)
            {
                return null;
            }

            var key = typeof(TEntity).Name;
            var metaWorkspace = db.ToObjectContext().MetadataWorkspace;
            var workspaceItems = metaWorkspace.GetItems<EntityType>(DataSpace.OSpace);
            var workspaceItem = workspaceItems.First(f => f.FullName.Contains(key));
            var navProperties = workspaceItem.NavigationProperties;

            return !includeReferences
                    ? db.Set<TEntity>()
                    : navProperties.Aggregate((IQueryable<TEntity>)db.Set<TEntity>(), (current, navProperty) => current.Include(navProperty.Name));
        }
        catch (Exception ex)
        {
            throw new ArgumentException("Invalid Entity Type supplied for Lookup", ex);
        }
    }

You may want to take a look into the Generic Search project on Github located here: https://github.com/danielpalme/GenericSearch 您可能需要查看位于Github上的Generic Search项目: https//github.com/danielpalme/GenericSearch

There is no need for Dynamic Linq on this one. 动态Linq不需要这个。

var groupedQuery = from p in db.People
    where p.City != null && p.State != null
    group p by new {p.City, p.State}
    into gp
    select new Thing {
        TotalNumber = gp.Count(),
        City = gp.Key.City,
        State = gp.Key.State
    };

IQueryable<Thing> retQuery = groupedQuery.AsQueryable(); 
retQuery= retQuery.Take(10);
return retQuery;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM