Как вы выполняете оператор "IN" в стиле SQL в LINQ to Entities (Entity Framework), если Contains не поддерживается?

Я использую LINQ to Entities (не LINQ to SQL), и мне трудно создавать запрос типа IN. Вот мой запрос на данный момент:

var items = db.InventoryItem
                .Include("Kind")
                .Include("PropertyValues")
                .Include("PropertyValues.KindProperty")
                .Where(itm => valueIds.Contains(itm.ID)).ToList<InventoryItem>();

Когда я это делаю, возникает следующее исключение:

LINQ to Entities не распознает метод "Boolean Contains (Int64)", и этот метод не может быть переведен в выражение хранилища.

У кого-нибудь есть обходное решение или другое решение для этого?

Ответ 1

Вам нужно либо использовать это:

.Where(string.Format("it.ID in {0}", string.Join(",", valueIds.ToArray())));

или динамически создайте часть WHERE, как в этот пост.

P.S. - Информация обновлена, и этот ответ обновлен следующим образом, чтобы сохранить актуальность:

Ссылка, на которую ссылается, содержит следующее обновление:

... в EF4 мы добавили поддержку Содержит метод и, по крайней мере, в этом конкретный случай для сбора ценностей параметры. Поэтому такой вид код теперь работает прямо из коробки и нет необходимости использовать какие-либо метод создания добавочных выражений:

var statusesToFind = new List<int> {1, 2, 3, 4};
var foos = from foo in myEntities.Foos
           where statusesToFind.Contains(foo.Status)
           select foo;

Ответ 2

Вы можете использовать метод расширения Linq Any для этого в некоторых случаях:

var userIds = new[] { 1, 2, 3 };

from u in Users
     where userIds.Any(i => i==u.Id)
     select u;

Сгенерированный SQL выглядит довольно странным в таком случае, но, как и много созданных Linq-to-Entities SQL, он может быть слишком многословным для человека, но на практике он быстро работает.

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[DisplayName] AS [DisplayName], 
FROM [dbo].[Users] AS [Extent1]
WHERE  EXISTS (SELECT 
    1 AS [C1]
    FROM  (SELECT 
        [UnionAll1].[C1] AS [C1]
        FROM  (SELECT 
            1 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]
        UNION ALL
            SELECT 
            2 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable2]) AS [UnionAll1]
    UNION ALL
        SELECT 
        3 AS [C1]
        FROM  ( SELECT 1 AS X ) AS [SingleRowTable3]) AS [UnionAll2]
    WHERE [UnionAll2].[C1] = [Extent1].[Id]
)

Ответ 3

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

Ответ 4

Как упоминалось Diego B Vega в этот пост (второй ответ), Contains теперь должен работать в EF4.

Ответ 5

Contains() и Any() не корректно переводит на операцию IN в SQL. При использовании лямбда-выражения столбец (который преобразуется в свойство в EF или Linq в SQL) должен находиться в левой части выражения лямбда. Я использовал XEvent Profiler в SQL, чтобы правильно отслеживать возникающие запросы, и вот как это выглядит.

Например, этот код:

List<int> ids = new List<int> {1, 2, 3};
IQueryable<Credits> creditsForUpdate = context.Credits.Where(c => unlistedCreditsIds.Any(a => a == c.CreditsId));
// it won't matter if you use .Any() or .Contains(),
// this won't be properly translated into how you want in in SQL statement.

Был переведен в эту инструкцию SQL из профилировщика:

SELECT [c].[TenantID], [c].[CreditsID], [c].[ProjectID], [c].[CharacterName], [c].[CreditsLabel], [c].[CreditsOrder], [c].[Name], [c].[PriorCredits]
FROM [Credits] AS [c]
WHERE 0 = 1

Ответ 6

Моим обходным решением является преобразование результатов сущностей в список и после, которые применяют Contains().

Пример:

var items = db.InventoryItem
                .Include("Kind")
                .Include("PropertyValues")
                .Include("PropertyValues.KindProperty")
                .ToList()
                .Where(itm => valueIds.Contains(itm.ID));