>我有以下代码(请注意,这被剥离到相关部分,实际查询要复杂得多(:
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) }
}
};
现在,当我使用新的 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()
。
是否有一个优雅的解决方案来解决这个问题,而不强制Where
表达式在计算 LINQ 语句之前进行计算?
"这是更新服务例程中的一个函数,用于获取任何菜单,自上次调用更新服务以来,该菜单本身或其任何描述已更改。
那么......在这种情况下,你不会有一个稍微复杂的Where
条款而不是所有这些吗?
result = GetAll()
.Where(m => lastUpdate < m.LastModified ||
m.Descriptions.Any(d => lastUpdate < d.LastModified);
您的问题陈述基本上描述了 LINQ 查询。 ;)
是否有解决此问题的优雅解决方案,而无需在计算 LINQ 语句之前强制 Where 表达式进行计算?
ForEach
扩展使其不优雅,可能是问题所在。Linq 中不包含ForEach
是有原因的。Linq 使用函数式"纯"方法,但ForEach
使用副作用。
您的GetMenus
方法返回IQueryable<Menu>
。因此,如果您的GetAll()
也返回IQueryable<>
,那么您的ForEach
就是性能问题。原因是,当你像dc.Customers.Where(c => c.Age >= 18)
一样在IQueryable<>
上调用Linq方法时,当Linq语句被转换为SQL WHERE
时,所以只有一些客户从数据库中加载。如果您编写dc.Customers.ForEach(c => ...)
并且ForEach
接受IEnumerable
而不是IQueryable
(如您的情况(,那么您将从该点开始在内存中查询,而不是在数据库上进行查询。
由于您需要Menu
对象与您从数据库中读取的记录不同(它将缺少一些Description
对象(,因此您需要创建新的Menu
对象:
这是完整的工作版本(直接来自LinqPad(
void Main()
{
GetMenus(new DateTime(2014,12,15)).Dump();
}
public IEnumerable<Menu> GetMenus(DateTime lastUpdate)
{
var result = from m in GetAll()
where lastUpdate < m.LastModified
select new Menu
{
LastModified = m.LastModified,
Descriptions = m.Descriptions.Where(d =>lastUpdate < d.LastModified ).ToList()
};
return result;
}
IEnumerable<Menu> GetAll()
{
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) }
}
};
return new List<Menu>{menuA, menuB};
}
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; }
}
我不得不将IQueryable
转换为IEnumerables
,因为我没有您的数据库,但我也做了一个类似的版本,它确实从数据库中读取并且它在那里也可以工作。