Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unable to cast object of type 'WhereEnumerableIterator`1' to type 'System.Collections.Generic.ICollection`1

I have the following code (please note that this is stripped down to the relevant part, the actual query is a lot more complex):

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

This is an function within an update service routine for an App to get any menu, which either itself or any of its descriptions has changed since the update service was last called.

Clarification: The function needs to return every Menu, which has changed since the last call. Additionally, it needs to return every changed Description of every changed Menu. but it must leave out the unchanged Descriptions.

As an Example:

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) }
    }
};

Now, when I call the update function with new DateTime(2014, 12, 15), this is the structure it needs to return:

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> {}
    }
}

With ForEach() looking like this:

public static IEnumerable<T> ForEach<T>(this IEnumerable<T> source, Action<T> action) {
        ... // Parameter check
        foreach (T item in source) {
            action(item);
        }
        return source;
    }

Menu and Description were automatically created by entity framework like this:

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; }
    ...
}

Unfortunately the Where function returns an IEnumerabley<Description>, which cannot be cast internally to the ICollection<Description> defined by entity framework.

When I try to cast it myself like this I get the runtime error within the title:

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

Now, I do understand as to why this error is thrown. The Description's Where expression has not yet been evaluated, so what is supposed to being cast to ICollection<Description> is not an IEnumerable<Description> yet, but a WhereEnumerableIterator. Right now I'm casting the Where expression to a list, which gets evaluated immediately and then cast to ICollection<Description>.

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

However, this is merely a workaround killing the benefits of the LINQ expression and besides, plain ugly. I could write an extension method WhereCollection<T>(...) calling Where<T> and returning an ICollection<T> but that wouldn't change much, I'd have to do the cast internally which either results in the same error or calls ToList() internally.

Is there an elegant solution to this problem, without forcing the Where expression to evaluate before the LINQ statement gets evaluated?

like image 545
Otto Abnormalverbraucher Avatar asked Dec 12 '14 14:12

Otto Abnormalverbraucher


1 Answers

"This is an function within an update service routine for an App to get any menu, which either itself or any of its descriptions has changed since the update service was last called."

So... wouldn't you have a slightly complex Where clause in this case instead of all that?

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

Your problem statement basically described the LINQ query. ;)

like image 67
toadflakz Avatar answered Dec 13 '22 06:12

toadflakz