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



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

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,因为我没有您的数据库,但我也做了一个类似的版本,它确实从数据库中读取并且它在那里也可以工作。

相关内容

最新更新