Jax*_*ian 10 c# linq entity-framework dynamic-linq .net-4.5
我正在使用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;
}
Run Code Online (Sandbox Code Playgroud)
我的事情:
public class Thing
{
public int TotalNumber { get; set; }
public string City { get; set; }
public string State { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
是的,我知道如果没有Dynamic Linq,上面的确切事情可以完成,但我有一些变量,我已经简化了这里.如果我的返回类型很简单IQueryable但我无法弄清楚如何转换为IQueryable<Thing>同时保持执行延迟并且同时保持实体框架满意,我可以使用我的变量.我确实有动态Select总是返回一些东西(使用正确的数据),看起来像一个Thing.但我根本无法想象如何返回IQueryable<Thing>并可以在那里使用一些帮助.谢谢!!
根据Rex M的建议,我现在正在尝试使用AutoMapper来解决这个问题(尽管我并不致力于这种方法并愿意尝试其他方法).对于AutoMapper方法,我这样做:
IQueryable<Thing> executionDeferredTypedThings = executionDeferredResults.ProjectTo<Thing>(); // <--- Help here!!!!
Run Code Online (Sandbox Code Playgroud)
但是这会导致InvalidOperationException:
缺少从DynamicClass2到Thing的地图.使用Mapper.CreateMap创建.
问题是,虽然我已定义Thing,但我没有定义DynamicClass2,因此我无法映射它.
IQueryable<Thing> executionDeferredTypedThings = db.People.Provider.CreateQuery<Thing>(executionDeferredResults.Expression);
Run Code Online (Sandbox Code Playgroud)
这会产生InvalidCastException,并且似乎与上述AutoMapper失败的基本问题相同:
无法将类型为'System.Data.Entity.Infrastructure.DbQuery'1 [DynamicClass2]'的对象强制转换为'System.Linq.IQueryable'1 [MyDtos.Thing]'.
您可以使用AutoMapper 的 Queryable Extensions生成 IQueryable,它包装底层 IQueryable,从而保留原始 IQueryable 的 IQueryProvider 和延迟执行,但将映射/转换组件添加到管道中以从一种类型转换为另一种类型。
还有AutoMapper 的 UseAsDataSource,它使一些常见的查询扩展场景变得更容易。
如果我理解正确,以下扩展方法应该可以为您完成工作
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)));
}
}
Run Code Online (Sandbox Code Playgroud)
(旁注:坦率地说,我不知道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;
}
Run Code Online (Sandbox Code Playgroud)
怎么运行的
这个想法很简单。
里面的方法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)));
}
Run Code Online (Sandbox Code Playgroud)
它的作用是动态创建一个选择器表达式并将其绑定到源Select方法。我们采用完全相同的方法,但修改了调用创建的选择器表达式DynamicExpression.ParseLambda。
唯一的要求是投影使用“new (...)”语法,并且投影属性的名称和类型 match,我认为这适合您的用例。
返回的表达式是这样的
(source) => new TargetClass
{
TargetProperty1 = Expression1(source),
TargetProperty2 = Expression2(source),
...
}
Run Code Online (Sandbox Code Playgroud)
其中TargetClass是动态生成的类。
我们想要的只是保留源部分,并将目标类/属性替换为所需的类/属性。
至于实现,首先将属性分配转换为
var bindings = memberInit.Bindings.Cast<MemberAssignment>()
.Select(mb => Expression.Bind(
(MemberInfo)resultType.GetProperty(mb.Member.Name) ?? resultType.GetField(mb.Member.Name),
mb.Expression));
Run Code Online (Sandbox Code Playgroud)
然后new DynamicClassXXX { ... }将 替换为 with
var body = Expression.MemberInit(Expression.New(resultType), bindings);
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1587 次 |
| 最近记录: |