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?
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With