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
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
You don't need that Cast at the end as dlev mentioned.
I assume the product class implements ITaggable? I think removing the Cast will fix the problem.
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.
You never show where this is used. I think you are already passing an IQueryable<ITaggable>
to the method in the first place.
Proof of concept https://ideone.com/W8c66
using System;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public interface ITaggable {}
public struct TagStruct : ITaggable {}
public class TagObject : ITaggable {}
public static IEnumerable<T> DoSomething<T>(IEnumerable<T> input)
where T: ITaggable
{
foreach (var i in input) yield return i;
}
public static void Main(string[] args)
{
var structs = new [] { new TagStruct() };
var objects = new [] { new TagObject() };
Console.WriteLine(DoSomething(structs).First().GetType());
Console.WriteLine(DoSomething(objects).First().GetType());
}
}
Output
Program+TagStruct
Program+TagObject
So, it is returning the input type, not the constrained interface.
Not surprising, what would have been the result if DoSometing required two interfaces?
public static IEnumerable<T> DoSomething<T>(IEnumerable<T> input)
where T: ITaggable, ISerializable
??