【问题标题】:Unable to cast object of type 'WhereEnumerableIterator`1' to type 'System.Collections.Generic.ICollection`1无法将“WhereEnumerableIterator`1”类型的对象转换为“System.Collections.Generic.ICollection`1”类型
【发布时间】:2015-02-11 07:20:42
【问题描述】:

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

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 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&lt;Description&gt;,它不能在内部强制转换为实体框架定义的ICollection&lt;Description&gt;

当我尝试像这样自己投射时,标题中出现运行时错误:

m => m.Descriptions = (ICollection<Description>)m.Descriptions.Where(...)

现在,我明白为什么会抛出这个错误了。描述的Where 表达式尚未被评估,因此应该转换为ICollection&lt;Description&gt; 的不是IEnumerable&lt;Description&gt;,而是一个WhereEnumerableIterator。现在我正在将Where 表达式转换为一个列表,该列表会立即被评估,然后转换为ICollection&lt;Description&gt;

m => m.Descriptions = (ICollection<Description>)m.Descriptions.Where(...).ToList()

但是,这仅仅是一种解决方法,扼杀了 LINQ 表达式的好处,而且还很丑陋。我可以编写一个扩展方法WhereCollection&lt;T&gt;(...) 调用Where&lt;T&gt; 并返回一个ICollection&lt;T&gt; 但这不会有太大变化,我必须在内部进行强制转换,这要么导致相同的错误,要么在内部调用ToList()

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

【问题讨论】:

  • 你的ForEach 方法是什么,它显然会返回一些东西?
  • 有道理。你知道Menu.Descriptions属性是否被Entity Framework懒加载了吗?
  • 如果我理解正确,virtual 关键字声明延迟加载,所以是的,Menu.Descriptions 是延迟加载的。

标签: c# linq ienumerable icollection


【解决方案1】:

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

ForEach 扩展使其不优雅,很可能是问题所在。 ForEach 不包含在 Linq 中是有原因的。 Linq 使用函数式“纯”方法,但 ForEach 使用副作用。

您的GetMenus 方法返回IQueryable&lt;Menu&gt;。因此,如果您的GetAll() 也返回一个IQueryable&lt;&gt;,那么您的ForEach 就是一个性能问题。原因是,当你在dc.Customers.Where(c =&gt; c.Age &gt;= 18) 上调用IQueryable&lt;&gt; 上的Linq 方法时,当Linq 语句转换为SQL 时WHERE,所以只有一些客户从数据库中加载。如果您要编写 dc.Customers.ForEach(c =&gt; ...) 并且如果 ForEach 将接受 IEnumerable 而不是 IQueryable (如您的情况),那么您从那时起查询内存而不是数据库。

【讨论】:

    【解决方案2】:

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

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

    result = GetAll()
             .Where(m => lastUpdate < m.LastModified || 
                    m.Descriptions.Any(d => lastUpdate < d.LastModified);
    

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

    【讨论】:

    • 形成查询逻辑,我认为 OP 想要删除未更改的描述。
    • 这不是我阅读他的问题陈述的方式 - OP 非常清楚该函数应该做什么,但他提供的代码显然没有这样做。主要是因为你不能在IEnumerable&lt;T&gt;Where() 结果上调用ForEach()
    • 看来我还不够清楚,我的错。我已经更新了我的问题。本·罗宾逊确实是对的。是的,如果您编写了扩展方法,您可以IEnumerable&lt;T&gt; 上调用ForEach(),我就是这样做的。我只是忽略了它,因为它不是问题的一部分。
    猜你喜欢
    • 2018-10-16
    • 2022-01-14
    • 2016-02-07
    • 2015-09-14
    • 1970-01-01
    • 2021-01-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多