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

时间:2015-12-04 22:06:04

标签: c# entity-framework linq .net-4.5 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 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; }
}

是的,我知道如果没有Dynamic Linq,上面的确切事情可以完成,但我有一些变量,我已经简化了这里。如果我的返回类型只是IQueryable,我可以让它与我的变量一起使用但是我无法弄清楚如何在保持执行延迟的同时转换为IQueryable<Thing>并且同时保持实体框架的满意度。我确实让动态Select总是返回看起来像 Thing的内容(包含正确的数据)。但我根本无法弄清楚如何返回IQueryable<Thing>并可以在那里使用一些帮助。谢谢!

尝试失败1

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

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

但是这会导致InvalidOperationException:

  

缺少从DynamicClass2到Thing的地图。使用Mapper.CreateMap创建。

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

尝试失败2

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

这会产生InvalidCastException,并且似乎与上述AutoMapper失败的基本问题相同:

  

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

4 个答案:

答案 0 :(得分:4)

您可以使用AutoMapper's Queryable Extensions生成包装底层IQueryable的IQueryable,从而保留原始IQueryable的IQueryProvider和延迟执行,但是将映射/转换组件添加到管道以从一种类型转换为另一种类型

还有AutoMapper's UseAsDataSource使一些常见的查询扩展方案更容易。

答案 1 :(得分:1)

如果我理解正确,以下扩展方法应该为您完成工作

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方法签名。)

所以你的例子会变成这样的

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

工作原理

这个想法非常简单。

Select中的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)));
}

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

唯一的要求是投影正在使用&#34; new(...)&#34;语法以及投影属性的名称和类型匹配,我认为这适用于您的用例。

返回的表达式是这样的

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

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

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

答案 2 :(得分:0)

这样的事情会对你有益吗?

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

您可能需要查看位于此处的Github上的通用搜索项目: https://github.com/danielpalme/GenericSearch

答案 3 :(得分:-1)

动态Linq不需要这个。

+ '</p><div id="commentSection' + post.postId //COMMENTS (without #)