我需要能够构建一个.Include,将结果过滤到特定日期或之前创建的记录。我不会知道原始LINQ语句中对象的类型,我将找到它们(通过大量的箍,但这是有效的。)
我有一个像这样的对象图:
A -> Ac
/ \
Bc <- B \
/ \
Cc <- C D -> Dc
对象Ac
,Bc
,Cc
和Dc
都是动态找到的(也就是说,开发人员在编写原始linq语句时不会输入这些关系)。然后,这些需要添加到IQueryable表达式,并且只返回特定DateTime的值。
我尝试构建一个构造lambda并比较日期的函数。到目前为止,如果原始查询是A.Include(x => x.B).Include(x => x.B.Select(y => y.C)
,我可以构造字符串"A.B.Bc"
,因此我知道A,B和Bc的类型以及它们的关联方式。
private static IQueryable<T> FilterChangeTrackerToDate<T>(this IQueryable<T> query, string includeString, DateTime targetDateTime)
{
var param = Expression.Parameter( typeof( T ), "x" );
var prop = Expression.Property(param, includeString + ".CreatedUtc");
var dateConst = Expression.Constant(targetDateTime);
var compare = Expression.LessThanOrEqual(prop, dateConst);
var lambda = Expression.Lambda<Func<T, bool>>(compare, param);
return query.Where(lambda);
}
问题是上面的代码崩溃了,因为"A.B.Bc.CreatedUtc"
不是加载date属性的正确方法 - 我需要通过.Select
语句来遍历它。
为了加载记录和过滤它们,我需要动态构建一个.Select
来提取我需要的值(谢天谢地, ,是基于继承的静态)并将这些值放入匿名类型,然后可以使用.Where()
进行过滤。所有这些都需要用最少的泛型来完成,因为我没有编译时类型来验证,我不得不滥用反射来让它们工作。
基本上,我需要创建它:
.Select( x => new
{
Bc = x.B.Select(z => z.Bc.Select(y => y.CreatedUtc).Where( q => q > DateTime.UtcNow ) ),
A= x
} )
动态地使用字符串“A.B.Bc”进行任何级别的嵌套。
这是我看到如何过滤EF include方法的地方: LINQ To Entities Include + Where Method
这篇文章讨论了动态创建Select的问题,但它只选择了顶级值,似乎不能构建我的问题所需的其余部分:How to create LINQ Expression Tree to select an anonymous type
使用System.Linq.Dynamic库,我试图访问Bc的CreatedUtc
值:
query = (IQueryable<T>) query.Select(includeString + ".CreatedUtc");
includeString = "B.Bc"
,但这给了我一个例外:
Exception thrown: 'System.Linq.Dynamic.ParseException' in System.Linq.Dynamic.dll
Additional information: No property or field 'Bc' exists in type 'List`1'
答案 0 :(得分:2)
我相信System.Linq.Dynamic
就是你所需要的。它是一个最初由Microsoft的ScottGu编写的库,它允许您从字符串构建LINQ查询:
IQueryable nestedQuery = query.Select("B.Bc.CreatedUtc");
其中查询为IQueryable<A>
。
这个库有很多分叉。您可以从here开始。查询语言的文档是here。另外here还有一个带有一些附加功能的版本,here是dotnet.core版本。
<强> UPD1:强> This库缺少SelectMany扩展方法,但this库有。因此,对于后一个库,您可以执行以下操作:
query.SelectMany("B").SelectMany("Bc").Select("CreatedUtc");