来自 Dynamic Linq 的执行延迟 IQueryable<T>?
Execution-Deferred IQueryable<T> from 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;
}
我的Thing.cs:
public class Thing
{
public int TotalNumber { get; set; }
public string City { get; set; }
public string State { get; set; }
}
是的,我知道没有 Dynamic Linq 也可以完成上面的事情,但是我有一些可变性,我已经从这里简化了。如果我的 return 类型只是 IQueryable
,我可以让它与我的可变性一起工作,但我无法弄清楚如何转换为 IQueryable<Thing>
,同时保持它的执行延迟,同时还保持Entity Framework 开心。我确实有动态 Select
总是 returning 一些 看起来像 和 Thing
的东西(使用正确的数据)。但我根本不知道如何 return 和 IQueryable<Thing>
并且可以在那里使用一些帮助。谢谢!!
失败尝试 1
根据Rex M的建议,我现在正在尝试使用AutoMapper来解决这个问题(虽然我不打算采用这种方法,也愿意尝试其他方法).对于 AutoMapper 方法,我是这样做的:
IQueryable<Thing> executionDeferredTypedThings = executionDeferredResults.ProjectTo<Thing>(); // <--- Help here!!!!
但这会导致 InvalidOperationException:
Missing map from DynamicClass2 to Thing. Create using Mapper.CreateMap.
问题是,虽然我定义了 Thing
,但我还没有定义 DynamicClass2
,因此我无法映射它。
第二次尝试失败
IQueryable<Thing> executionDeferredTypedThings = db.People.Provider.CreateQuery<Thing>(executionDeferredResults.Expression);
这给出了一个 InvalidCastException 并且似乎是与上述 AutoMapper 失败命中相同的潜在问题:
Unable to cast object of type 'System.Data.Entity.Infrastructure.DbQuery'1[DynamicClass2]' to type 'System.Linq.IQueryable'1[MyDtos.Thing]'.
您可以使用 AutoMapper's Queryable Extensions 生成一个 IQueryable 来包装底层的 IQueryable,从而保留原始 IQueryable 的 IQueryProvider 和延迟执行,但将 mapping/translating 组件添加到管道以从中转换一种类型到另一种类型。
还有 AutoMapper's UseAsDataSource 可以简化一些常见的查询扩展方案。
这个不需要 Dynamic 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;
这样的事情对您有帮助吗?
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
如果我没理解错的话,下面的扩展方法应该可以帮你完成工作
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;
}
工作原理
这个想法很简单。
DynamicQueryable
中的 Select
方法实现看起来像这样
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
调用创建的选择器表达式。
唯一的要求是 投影使用 "new (...)" 语法并且投影属性的名称和类型匹配 ,我认为这适合您的用例。
返回的表达式是这样的
(source) => new TargetClass
{
TargetProperty1 = Expression1(source),
TargetProperty2 = Expression2(source),
...
}
其中 TargetClass
是动态生成的 class。
我们想要的只是保留源部分,并将目标 class/properties 替换为所需的 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 { ... }
被替换为 with
var body = Expression.MemberInit(Expression.New(resultType), bindings);
我正在使用 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;
}
我的Thing.cs:
public class Thing
{
public int TotalNumber { get; set; }
public string City { get; set; }
public string State { get; set; }
}
是的,我知道没有 Dynamic Linq 也可以完成上面的事情,但是我有一些可变性,我已经从这里简化了。如果我的 return 类型只是 IQueryable
,我可以让它与我的可变性一起工作,但我无法弄清楚如何转换为 IQueryable<Thing>
,同时保持它的执行延迟,同时还保持Entity Framework 开心。我确实有动态 Select
总是 returning 一些 看起来像 和 Thing
的东西(使用正确的数据)。但我根本不知道如何 return 和 IQueryable<Thing>
并且可以在那里使用一些帮助。谢谢!!
失败尝试 1
根据Rex M的建议,我现在正在尝试使用AutoMapper来解决这个问题(虽然我不打算采用这种方法,也愿意尝试其他方法).对于 AutoMapper 方法,我是这样做的:
IQueryable<Thing> executionDeferredTypedThings = executionDeferredResults.ProjectTo<Thing>(); // <--- Help here!!!!
但这会导致 InvalidOperationException:
Missing map from DynamicClass2 to Thing. Create using Mapper.CreateMap.
问题是,虽然我定义了 Thing
,但我还没有定义 DynamicClass2
,因此我无法映射它。
第二次尝试失败
IQueryable<Thing> executionDeferredTypedThings = db.People.Provider.CreateQuery<Thing>(executionDeferredResults.Expression);
这给出了一个 InvalidCastException 并且似乎是与上述 AutoMapper 失败命中相同的潜在问题:
Unable to cast object of type 'System.Data.Entity.Infrastructure.DbQuery'1[DynamicClass2]' to type 'System.Linq.IQueryable'1[MyDtos.Thing]'.
您可以使用 AutoMapper's Queryable Extensions 生成一个 IQueryable 来包装底层的 IQueryable,从而保留原始 IQueryable 的 IQueryProvider 和延迟执行,但将 mapping/translating 组件添加到管道以从中转换一种类型到另一种类型。
还有 AutoMapper's UseAsDataSource 可以简化一些常见的查询扩展方案。
这个不需要 Dynamic 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;
这样的事情对您有帮助吗?
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
如果我没理解错的话,下面的扩展方法应该可以帮你完成工作
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;
}
工作原理
这个想法很简单。
DynamicQueryable
中的 Select
方法实现看起来像这样
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
调用创建的选择器表达式。
唯一的要求是 投影使用 "new (...)" 语法并且投影属性的名称和类型匹配 ,我认为这适合您的用例。
返回的表达式是这样的
(source) => new TargetClass
{
TargetProperty1 = Expression1(source),
TargetProperty2 = Expression2(source),
...
}
其中 TargetClass
是动态生成的 class。
我们想要的只是保留源部分,并将目标 class/properties 替换为所需的 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 { ... }
被替换为 with
var body = Expression.MemberInit(Expression.New(resultType), bindings);