2013-09-29 4 views
5

У меня есть заводская функция для возврата DbSet(Of IItemType). Фактическим типом возврата всегда будет реализация IItemType, например DbSet(Of CategoryType).Почему DbSet не ковариант?

Я думал, что ковариационная поддерживается генериков, и этот метод будет работать нормально, но я получаю исключение при попытке запустить мой код:

Невозможно привести объект типа «System.Data.Entity. DbSet 1[CategoryType]' to type 'System.Data.Entity.DbSet 1 [IItemType] '.

ответ

2

Похоже, что они могут быть ковариантными. Но есть host of differences между программированием в памяти и программированием против поставщика запросов.

Для того, чтобы Entity Framework материализовала объект из хранилища данных, для него требуется его точный тип и сопоставление от столбцов базы данных к свойствам типа. Интерфейс может представлять любой тип, и EF пока не может выбрать подходящую реализацию. Возможно, поддержка интерфейсов будет представлена ​​в будущих выпусках.

То же самое относится к приведению сущностей к интерфейсу в запросе EF (случай, который я только что добавил в список различий).

+0

Означает ли, что 'Cast' вызов добавляется к' Expression' перед тем его исполнение? Таким образом, EF пытается перевести вызов «Cast» в какой-то SQL и, очевидно, не может, потому что в базе данных нет «IItemType». –

+0

Да, именно это и происходит. –

+1

-1 DbSet никогда не может быть ковариантным. – Aron

5

DbSet<T> НЕ МОЖЕТ быть со-вариантом. Во-первых, это потому, что в C# классы не могут быть ко-вариантными ... только интерфейсами. Во-вторых, поскольку DbSet<T> имеет как ко-вариантные, так и контра-вариантные методы.

Примите следующие два примера.

DbSet<CategoryType> set = context.Set<CategoryType>(); 
IQueryable<IItemType> query = set.Where(x => x.Foo == Bar); 

Итак, мы знаем, за то, что все CategoryType s являются IItemType, поэтому мы знаем, что это всегда может работать.

Однако, наоборот, попробовать это ...

DbSet<CategoryType> set = context.Set<CategoryType>(); 
IItemType newItemType = new ProductType(); 
set.Add(newItemType); // Compiler error. 

Не все IItemType s являются CategoryType. Поэтому, если бы мы могли наложить DbSet<CategoryType> на DbSet<IItemType>, мы бы получили ошибки времени выполнения при добавлении ... Поскольку компилятор знает, что это может не всегда работать, оно не позволит вам делать кастинг.

Однако есть интерфейсы, которые DbSet<T> действительно позволяет Соотношение. Например, вы можете попробовать его отличить от IQueryable<IItemType>.

Однако это звучит, как вы пытаетесь запросить против DbSet с помощью запроса к интерфейсу ... попробуйте следующее

DbSet<CategoryType> set = context.Set<CategoryType>(); 
IQueryable<IItemType> cast = set.OfType<IITemType>(); //(or .Cast<>() works too) 
IQueryable<IItemType> query = cast.Where(x => x ....); 
+0

Ваше первое утверждение не совсем правильно. Вы можете использовать ключевое слово 'out' в универсальном интерфейсе и делегировать только определения, но классы, реализующие эти интерфейсы. _Are_ covariant: _Квариантность и контравариантность поддерживаются для ссылочных типов, но они не поддерживаются для типов значений ._ ([MSDN] (http : //msdn.microsoft.com/en-us/library/dd469487.aspx)). Я признаю, что мне пришлось это посмотреть. Мой ответ на ваш комментарий выше также не совсем корректен. –

+0

Но использование этих интерфейсов является ковариантным/контравариантным. Вы не можете наложить 'DbSet ' на 'DbSet ', потому что не все 'TBase' могут быть добавлены к базовому' DbSet '. Точно так же вы не можете использовать 'DbSet ' '' DbSet ', поскольку он может содержать элементы в основе' TOtherDerivied', когда вы запрашиваете его. – Aron

+0

IQueryable <> - хорошее решение. Это должен быть принятый ответ –

Смежные вопросы