Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return input type of generic with type constraint in LINQ to Entities (EF4.1)

I have a simple extension method for filtering a LINQ IQueryable by tags. I'm using this with LINQ to Entities with an interface of:

public interface ITaggable
{
    ICollection<Tag> Tags { get; } 
}

The following does not work, returning IQueryable<ITaggable> instead of IQueryable<T>:

public static IQueryable<T> WhereTagged<T>(this IQueryable<T> set, string tag) where T:ITaggable
    {
        return set.Where(s=>s.Tags.Any(t=>t.Name.ToLower() == tag.ToLower()));
    }

This leads to a LINQ to Entities cast exception:

"Unable to cast the type 'ReleaseGateway.Models.Product' to type 'ReleaseGateway.Models.ITaggable'. LINQ to Entities only supports casting Entity Data Model primitive types." (System.NotSupportedException) A System.NotSupportedException was caught: "Unable to cast the type 'Project.Models.Product' to type 'Project.Models.ITaggable'. LINQ to Entities only supports casting Entity Data Model primitive types."

It works without the constraint like this, but I have to explicitly declare the type T in my application code:

public static IQueryable<T> WhereTagged<T>(this IQueryable<ITaggable> set, string tag)
{
    return set.Where(s=>s.Tags.Any(t=>t.Name.ToLower() == tag.ToLower())).Cast<T>();
}

Question: Why does the type constraint cast the return type? Can I rewrite this to take advantage of inferring the type from the extension method caller?

like image 673
Jason Suárez Avatar asked Nov 17 '11 22:11

Jason Suárez


2 Answers

I suspect that the problem arises from the call to s.Tags. Because s is a Product, but you're calling ITaggable.Tags, the expression that gets generated looks more like:

set.Where(s=>((ITaggable)s).Tags.Any(...))

That just confuses Entity Framework. Try this:

((IQueryable<ITaggable>)set)
    .Where(s=>s.Tags.Any(t=>t.Name.ToLower() == tag.ToLower()))
    .Cast<T>();

Since IQueryable is a covariant interface, this will treat the set as an IQueryable<ITaggable>, which should work since your second example basically does exactly the same thing.

like image 50
StriplingWarrior Avatar answered Nov 05 '22 02:11

StriplingWarrior


I was looking for the same answer and not being satisfied with the syntactic cleanliness of the answers provided, I kept looking and found this post.

tl;dr; - add class to your constraints and it works.

LINQ to Entities only supports casting EDM primitive or enumeration types with IEntity interface

public static IQueryable<T> WhereTagged<T>(this IQueryable<T> set, string tag)
    where T: class, ITaggable
like image 20
Max Weber Avatar answered Nov 05 '22 02:11

Max Weber