无法将类型为'WhereEnumerableIterator`1'的对象强制转换为'System.Collections.Generic.ICollection`1

Ott*_*her 7 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
Run Code Online (Sandbox Code Playgroud)

这是一个更新服务例程中的一个函数,用于获取任何菜单,自上次调用更新服务以来,该菜单本身或其任何描述都已更改.

澄清:该函数需要返回自上次调用以来已更改的每个菜单.此外,它需要返回每个更改的菜单的每个更改的描述.但它必须省略未改变的描述.

举个例子:

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) }
    }
};
Run Code Online (Sandbox Code Playgroud)

现在,当我使用新的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> {}
    }
}
Run Code Online (Sandbox Code Playgroud)

随着的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;
    }
Run Code Online (Sandbox Code Playgroud)

菜单和描述由实体框架自动创建,如下所示:

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; }
    ...
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,Where函数返回一个IEnumerabley<Description>,它不能内部转换为ICollection<Description>实体框架定义的.

当我尝试像我这样自己编译时,我在标题中得到运行时错误:

m => m.Descriptions = (ICollection<Description>)m.Descriptions.Where(...)
Run Code Online (Sandbox Code Playgroud)

现在,我确实理解为什么抛出这个错误.该描述的Where表达还没有得到评估,因此原本应该被强制转换为ICollection<Description>不是IEnumerable<Description>还没有,但WhereEnumerableIterator.现在我将Where表达式转换为列表,立即进行评估,然后转换为ICollection<Description>.

m => m.Descriptions = (ICollection<Description>)m.Descriptions.Where(...).ToList()
Run Code Online (Sandbox Code Playgroud)

然而,这仅仅是一种破坏LINQ表达式的好处的解决方法,此外,简单的丑陋.我可以写一个扩展方法WhereCollection<T>(...)调用Where<T>并返回一个ICollection<T>但不会改变很多,我必须在内部执行转换,这会导致相同的错误或ToList()内部调用.

是否有一个优雅的解决方案来解决这个问题,而不必在Where评估LINQ语句之前强制表达式进行求值?

toa*_*akz 1

“这是更新服务例程中的一个功能,用于让应用程序获取任何菜单,自上次调用更新服务以来,菜单本身或其任何描述都已更改。”

那么...在这种情况下你不会有一个稍微复杂的Where子句来代替所有这些吗?

result = GetAll()
         .Where(m => lastUpdate < m.LastModified || 
                m.Descriptions.Any(d => lastUpdate < d.LastModified);
Run Code Online (Sandbox Code Playgroud)

您的问题陈述基本上描述了 LINQ 查询。;)

  • 这不是我阅读他的问题陈述的方式 - OP 非常清楚该函数应该做什么,但他提供的代码显然没有这样做。主要是因为您无法对“Where()”的“IEnumerable&lt;T&gt;”结果调用“ForEach()”。 (2认同)