无法转换类型' WhereEnumerableIterator`1'键入' System.Collections.Generic.ICollection`1

时间:2014-12-12 14:44:11

标签: c# linq ienumerable icollection

我有以下代码(请注意,这是剥离到相关部分,实际查询要复杂得多):

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表达式?

2 个答案:

答案 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(如您的情况),那么您将在此之后查询内存,而不是在数据库上。