Вернуть тип ввода универсального с ограничением типа в LINQ to Entities (EF4.1) - PullRequest
10 голосов
/ 18 ноября 2011

У меня есть простой метод расширения для фильтрации 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:

"Невозможно привести тип 'ReleaseGateway.Models.Product' к типу. 'ReleaseGateway.Models.ITaggable. LINQ to Entities поддерживает только приведение типов примитивных моделей данных сущностей. " (System.NotSupportedException) System.NotSupportedException было поймал: «Невозможно привести тип« Project.Models.Product »к типу 'Project.Models.ITaggable. LINQ to Entities поддерживает только приведение Типы примитивов модели данных объекта. "

Он работает без таких ограничений, но я должен явно объявить тип 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>();
}

Вопрос: Почему ограничение типа приводит тип возвращаемого значения? Могу ли я переписать это, чтобы воспользоваться выводом типа из вызывающего метода расширения?

Ответы [ 4 ]

7 голосов
/ 18 ноября 2011

Я подозреваю, что проблема возникает из-за звонка на 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>, который должен работать, поскольку ваш второй пример в основном делает то же самое.

2 голосов
/ 23 апреля 2015

Я искал тот же ответ и не был удовлетворен синтаксической чистотой предоставленных ответов, я продолжал искать и нашел этот пост.

tl; dr;- добавить класс к вашим ограничениям, и это работает .

LINQ to Entities поддерживает только приведение типов примитивов или перечислений EDM с интерфейсом IEntity

public static IQueryable<T> WhereTagged<T>(this IQueryable<T> set, string tag)
    where T: class, ITaggable
0 голосов
/ 18 ноября 2011

Тебе не нужен этот бросок в конце, как уже упоминалось.

Я предполагаю, что класс продукта реализует ITaggable? Я думаю, что удаление броска решит проблему.

0 голосов
/ 18 ноября 2011

Вы никогда не показываете, где это используется.Я думаю, что вы уже передаете 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

??

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...