我有以下代码(请注意,这是剥离到相关部分,实际查询要复杂得多):
public IQueryable<Menu> GetMenus(DateTime lastUpdate) {
...
result = GetAll().Where(m => lastUpdate < m.LastModified)
.ForEach(m => m.Descriptions = m.Descriptions
.Where(d => lastUpdate < d.LastModified));
...
enter code here
这是一个更新服务例程中的一个函数,用于获取任何菜单,自上次调用更新服务以来,该菜单本身或其任何描述都已更改。
澄清:该函数需要返回自上次调用后已更改的每个Menu。此外,它需要返回每个更改的菜单的每个更改的描述。但它必须省略未改变的描述。
作为示例:
Menu menuA = new Menu() {
LastModified = new DateTime(2014, 12, 24),
Descriptions = new List<Description>() {
new Description() { LastModified = new DateTime(2014, 12, 24) },
new Description() { LastModified = new DateTime(2014, 12, 01) }
}
};
Menu menuB = new Menu() {
LastModified = new DateTime(2014, 12, 20),
Descriptions = new List<Description>() {
new Description() { LastModified = new DateTime(2014, 12, 01) }
}
};
现在,当我使用 new DateTime(2014,12,15)调用更新函数时,这是它需要返回的结构:
List<Menu>: {
menuA: {
LastModified: DateTime(2014, 12, 24),
Descriptions: List<Description> {
Description: {
LastModified: DateTime(2014, 12, 24),
}
}
},
menuB: {
LastModified: DateTime(2014, 12, 20),
Descriptions: List<Description> {}
}
}
ForEach()如下所示:
public static IEnumerable<T> ForEach<T>(this IEnumerable<T> source, Action<T> action) {
... // Parameter check
foreach (T item in source) {
action(item);
}
return source;
}
菜单和说明由实体框架自动创建,如下所示:
public partial class Menu {
...
public System.DateTime LastModified { get; set; }
public virtual ICollection<Description> Descriptions { get; set; }
...
}
public partial class Description {
...
public System.DateTime LastModified { get; set; }
public virtual Menu Menu { get; set; }
...
}
不幸的是Where
函数返回IEnumerabley<Description>
,它不能内部转换为实体框架定义的ICollection<Description>
。
当我尝试自己这样投射时,我在标题中得到了运行时错误:
m => m.Descriptions = (ICollection<Description>)m.Descriptions.Where(...)
现在,我确实理解为什么会抛出此错误。描述的Where
表达式尚未被评估,因此应该转换为ICollection<Description>
的内容不是IEnumerable<Description>
,而是WhereEnumerableIterator。现在我将Where
表达式转换为列表,立即对其进行评估,然后转换为ICollection<Description>
。
m => m.Descriptions = (ICollection<Description>)m.Descriptions.Where(...).ToList()
然而,这仅仅是一种破坏LINQ表达式的好处的解决方法,此外,简单的丑陋。我可以编写一个扩展方法WhereCollection<T>(...)
来调用Where<T>
并返回一个ICollection<T>
,但这不会有太大变化,我必须在内部进行强制转换,这会导致相同的错误或在内部调用ToList()
。
是否有一个优雅的解决方案来解决这个问题,而不必在评估LINQ语句之前强制求Where
表达式?
答案 0 :(得分:0)
“这是一个更新服务例程中的一个功能,用于获取任何菜单,自上次调用更新服务以来,本身或其任何描述已更改。”
那么......在这种情况下你不会有一个稍微复杂的Where
条款而不是所有这些吗?
result = GetAll()
.Where(m => lastUpdate < m.LastModified ||
m.Descriptions.Any(d => lastUpdate < d.LastModified);
您的问题陈述基本上描述了LINQ查询。 ;)
答案 1 :(得分:0)
在不强制对LINQ语句求值之前要对Where表达式求值的情况下,是否有一个解决此问题的好方法?
ForEach
扩展名使其不美观,可能是问题所在。有一个原因ForEach
不包含在Linq中。 Linq使用功能性“纯”方法,但是ForEach
使用副作用。
您的GetMenus
方法返回IQueryable<Menu>
。因此,如果您的GetAll()
也返回了IQueryable<>
,那么您的ForEach
就是性能问题。原因是,当您像IQueryable<>
那样在dc.Customers.Where(c => c.Age >= 18)
上调用Linq方法时,当Linq语句转换为SQL WHERE
时,仅从数据库中加载了一些客户。如果您要编写dc.Customers.ForEach(c => ...)
,并且如果ForEach
接受IEnumerable
而不是IQueryable
(如您的情况),那么您将在此之后查询内存,而不是在数据库上。