Тип возвращаемого типа generic с ограничением типа в LINQ to Entities (EF4.1)
У меня есть простой метод расширения для фильтрации LINQ IQueryable по тегам. Я использую это с LINQ to Entities с интерфейсом:
public interface ITaggable
{
ICollection<Tag> Tags { get; }
}
Не работает следующее, возвращая IQueryable<ITaggable>
вместо 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()));
}
Это приводит к исключению LINQ to Entities cast:
"Невозможно ввести тип 'ReleaseGateway.Models.Product' для ввода типа 'ReleaseGateway.Models.ITaggable. LINQ to Entities поддерживает только литье Entity Data Model примитивных типов." (System.NotSupportedException) Исключение System.NotSupportedException catch: "Невозможно ввести тип" Project.Models.Product "для ввода типа 'Project.Models.ITaggable. LINQ to Entities поддерживает только литье Entity Data Model примитивные типы."
Он работает без ограничения, подобного этому, но я должен явно объявить тип T в моем коде приложения:
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>();
}
Вопрос: Почему ограничение типа приводит к типу возврата? Могу ли я переписать это, чтобы воспользоваться выводом типа из вызывающего абонента метода расширения?
Ответы
Ответ 1
Я искал тот же ответ и не удовлетворен синтаксической чистотой предоставленных ответов, я продолжал искать и находил этот пост.
ТЛ; дг; - добавить класс к вашим ограничениям, и он работает.
LINQ to Entities поддерживает только листинг EDM примитивных или перечисляемых типов с интерфейсом IEntity
public static IQueryable<T> WhereTagged<T>(this IQueryable<T> set, string tag)
where T: class, ITaggable
Ответ 2
Я подозреваю, что проблема возникает из вызова s.Tags
. Поскольку s
является Product
, но вы вызываете ITaggable.Tags
, выражение, которое генерируется, выглядит более похоже:
set.Where(s=>((ITaggable)s).Tags.Any(...))
Это просто путает Entity Framework. Попробуйте следующее:
((IQueryable<ITaggable>)set)
.Where(s=>s.Tags.Any(t=>t.Name.ToLower() == tag.ToLower()))
.Cast<T>();
Так как IQueryable
является ковариантным интерфейсом, это будет рассматривать набор как IQueryable<ITaggable>
, который должен работать, поскольку ваш второй пример в основном делает точно то же самое.
Ответ 3
Вы никогда не показываете, где это используется. Я думаю, что вы уже передаете метод IQueryable<ITaggable>
в первую очередь.
Доказательство концепции 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());
}
}
Выход
Program+TagStruct
Program+TagObject
Таким образом, он возвращает тип ввода, а не ограниченный интерфейс.
Не удивительно, что было бы результатом, если бы DoSometing требовал два интерфейса?
public static IEnumerable<T> DoSomething<T>(IEnumerable<T> input)
where T: ITaggable, ISerializable
??
Ответ 4
Вам не нужно, чтобы Cast в конце был указан dlev.
Я предполагаю, что класс продукта реализует ITaggable? Я думаю, что удаление Cast поможет решить проблему.