Linq to Entities - предложение SQL IN

В T-SQL у вас может быть такой запрос:

SELECT * FROM Users WHERE User_Rights IN ("Admin", "User", "Limited")

Как бы вы воспроизвели это в запросе LINQ to Entities? Это вообще возможно?


person StevenMcD    schedule 13.05.2009    source источник


Ответы (8)


Вам нужно перевернуть его с ног на голову с точки зрения того, как вы думаете об этом. Вместо того, чтобы выполнять «in», чтобы найти права пользователя текущего элемента в предопределенном наборе применимых прав пользователя, вы запрашиваете предопределенный набор прав пользователя, содержит ли он применимое значение текущего элемента. Точно так же вы найдете элемент в обычном списке в .NET.

Есть два способа сделать это с помощью LINQ: один использует синтаксис запроса, а другой использует синтаксис метода. По сути, они одинаковы и могут использоваться взаимозаменяемо в зависимости от ваших предпочтений:

Синтаксис запроса:

var selected = from u in users
               where new[] { "Admin", "User", "Limited" }.Contains(u.User_Rights)
               select u

foreach(user u in selected)
{
    //Do your stuff on each selected user;
}

Синтаксис метода:

var selected = users.Where(u => new[] { "Admin", "User", "Limited" }.Contains(u.User_Rights));

foreach(user u in selected)
{
    //Do stuff on each selected user;
}

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

foreach(User u in users.Where(u => new [] { "Admin", "User", "Limited" }.Contains(u.User_Rights)))
{
    //Do stuff on each selected user;
}

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

Все сводится к вашему стилю кодирования и предпочтениям - все три моих примера делают одно и то же немного по-разному.

Альтернативный способ даже не использует LINQ, вы можете использовать тот же синтаксис метода, заменяя «where» на «FindAll», и получить тот же результат, который также будет работать в .NET 2.0:

foreach(User u in users.FindAll(u => new [] { "Admin", "User", "Limited" }.Contains(u.User_Rights)))
{
    //Do stuff on each selected user;
}
person BenAlabaster    schedule 13.05.2009
comment
возможно, я был слишком быстр, чтобы отметить ответ, но я не получаю .Contains после того, как {Admin, User, Limited} VS2008 не нравится этот код. - person StevenMcD; 13.05.2009
comment
Верный своему имени FailBoy Я понял это: P Я ввел в строку [], а затем использовал ее, и это сработало. Спасибо! - person StevenMcD; 13.05.2009
comment
извините, я забыл обновить анонимный массив;) Я исправил свой пример кода. Рад, что ты понял это самостоятельно. - person BenAlabaster; 13.05.2009
comment
Этот ответ был бы правильным, если бы вопрос касался Linq-to-SQL или Linq в целом. Однако, поскольку в нем конкретно говорится о Linq-to-Entities, этот ответ неверен. array.Contains (пока) не поддерживается Linq-to-Entities. - person KristoferA; 29.05.2009
comment
@KristoferA - это могло быть верно для более ранних версий EF, но для меня это нормально с EF4. - person Drew Noakes; 07.05.2011

Этого должно хватить для вашей цели. Он сравнивает две коллекции и проверяет, имеет ли одна коллекция значения, соответствующие значениям в другой коллекции.

fea_Features.Where(s => selectedFeatures.Contains(s.feaId))
person Balaji Birajdar    schedule 27.04.2011

В этом контексте я выберу Inner Join. Если бы я использовал contains, он бы повторил 6 раз, несмотря на то, что есть только одно совпадение.

var desiredNames = new[] { "Pankaj", "Garg" }; 

var people = new[]  
{  
    new { FirstName="Pankaj", Surname="Garg" },  
    new { FirstName="Marc", Surname="Gravell" },  
    new { FirstName="Jeff", Surname="Atwood" }  
}; 

var records = (from p in people join filtered in desiredNames on p.FirstName equals filtered  select p.FirstName).ToList(); 

Недостатки Contains

Предположим, у меня есть два объекта списка.

List 1      List 2
  1           12
  2            7
  3            8
  4           98
  5            9
  6           10
  7            6

Используя Contains, он будет искать каждый элемент списка 1 в списке 2, что означает, что итерация будет выполняться 49 раз !!!

person Pankaj    schedule 03.04.2012
comment
Это полностью игнорирует тот факт, что оператор переведен в SQL. См. здесь. - person Gert Arnold; 11.08.2015

Это может быть возможным способом, которым вы можете напрямую использовать методы расширения LINQ для проверки предложения in

var result = _db.Companies.Where(c => _db.CurrentSessionVariableDetails.Select(s => s.CompanyId).Contains(c.Id)).ToList();
person Torakami    schedule 05.08.2015

Я также пробовал работать с SQL-IN-подобной вещью - запросами к модели данных сущности. Мой подход - построитель строк для составления большого ИЛИ-выражения. Это ужасно уродливо, но, боюсь, сейчас это единственный выход.

Что ж, это выглядит так:

Queue<Guid> productIds = new Queue<Guid>(Products.Select(p => p.Key));
if(productIds.Count > 0)
{
    StringBuilder sb = new StringBuilder();
    sb.AppendFormat("{0}.ProductId = Guid\'{1}\'", entities.Products.Name, productIds.Dequeue());
    while(productIds.Count > 0)
    {
        sb.AppendFormat(" OR {0}.ProductId = Guid\'{1}\'",
          entities.Products.Name, productIds.Dequeue());
    }
}

Работа с идентификаторами GUID в этом контексте: как вы можете видеть выше, во фрагментах строки запроса всегда есть слово «GUID» перед самим идентификатором GUID. Если вы не добавите это, ObjectQuery<T>.Where вызовет следующее исключение:

Типы аргументов Edm.Guid и Edm.String несовместимы для этой операции., Выражение почти равно, строка 6, столбец 14.

Нашел это на форумах MSDN, может быть полезно иметь в виду.

Матиас

... с нетерпением жду следующей версии .NET и Entity Framework, когда все наладится. :)

person Matthias Meid    schedule 10.07.2009

Альтернативный метод ответа BenAlabaster

Прежде всего, вы можете переписать запрос так:

var matches = from Users in people
        where Users.User_Rights == "Admin" ||
              Users.User_Rights == "Users" || 
              Users.User_Rights == "Limited"
        select Users;

Конечно, это более «многословно» и неудобно писать, но все равно работает.

Так что, если бы у нас был какой-то служебный метод, который упростил бы создание такого рода выражений LINQ, мы были бы в бизнесе.

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

var matches = ctx.People.Where(
        BuildOrExpression<People, string>(
           p => p.User_Rights, names
        )
);

Это создает выражение, которое имеет тот же эффект, что и:

var matches = from p in ctx.People
        where names.Contains(p.User_Rights)
        select p;

Но что более важно, на самом деле работает против .NET 3.5 SP1.

Вот функция сантехники, которая делает это возможным:

public static Expression<Func<TElement, bool>> BuildOrExpression<TElement, TValue>(
        Expression<Func<TElement, TValue>> valueSelector, 
        IEnumerable<TValue> values
    )
{     
    if (null == valueSelector) 
        throw new ArgumentNullException("valueSelector");

    if (null == values)
        throw new ArgumentNullException("values");  

    ParameterExpression p = valueSelector.Parameters.Single();

    if (!values.Any())   
        return e => false;

    var equals = values.Select(value =>
        (Expression)Expression.Equal(
             valueSelector.Body,
             Expression.Constant(
                 value,
                 typeof(TValue)
             )
        )
    );
   var body = equals.Aggregate<Expression>(
            (accumulate, equal) => Expression.Or(accumulate, equal)
    ); 

   return Expression.Lambda<Func<TElement, bool>>(body, p);
}

Я не буду пытаться объяснять этот метод, кроме как сказать, что он, по сути, строит выражение предиката для всех значений, используя valueSelector (т.е. p => p.User_Rights) и объединяет эти предикаты вместе, чтобы создать выражение для полного предикат

Источник: http://blogs.msdn.com/b/alexj/archive/2009/03/26/tip-8-writing-where-in-style-queries-using-linq-to-entities..aspx

person fire in the hole    schedule 08.08.2015

Реальный пример:

var trackList = Model.TrackingHistory.GroupBy(x => x.ShipmentStatusId).Select(x => x.Last()).Reverse();
List<int> done_step1 = new List<int>() {2,3,4,5,6,7,8,9,10,11,14,18,21,22,23,24,25,26 };
bool isExists = trackList.Where(x => done_step1.Contains(x.ShipmentStatusId.Value)).FirstOrDefault() != null;
person Adel Mourad    schedule 03.05.2019

Шутки в сторону? Вы, ребята, никогда не использовали

where (t.MyTableId == 1 || t.MyTableId == 2 || t.MyTableId == 3)
person cjm30305    schedule 31.01.2012
comment
-1 Попробуйте это с 20 или более значениями в таблице с более чем 1000 строками, и вы быстро увидите преимущество принятого решения. Кроме того, нелегко добавить произвольное количество условий к оператору where (например, если пользователь выбирает включить вариант 1 и 2, но не 3). - person Trisped; 04.04.2013
comment
Что ж, мне не нужно было ничего из сумасшедшего ученого, и этот ответ стал моим голосом, потому что мне нужно было AND и 2 ORS var SamplePoints = (из c в _db.tblPWS_WSF_SPID_ISN_Lookup.OrderBy (x = ›x.WSFStateCode), где c. PWS == id && ((c.WSFStateCode.Substring (0, 2) == SR) || (c.WSFStateCode.Substring (0, 2) == CH)) выберите c) .ToList (); - person JustJohn; 04.11.2014
comment
@Trisped - количество строк (1000) ничего не меняет - или мне что-то не хватает? - person tymtam; 09.09.2016
comment
@Tymski Да, количество строк имеет значение. Чем больше строк, тем больше вычислений. То же самое с количеством возможных значений: Checks = NumValues * NumRows. Поскольку это вычисление типа M * N, если какое-либо из них мало, то время на выполнение каждой требуемой проверки также будет небольшим. Я добавил ограничение, чтобы cjm30305 знал, как настроить тестовую среду, в которой показано, почему его решение неудовлетворительное. - person Trisped; 18.09.2016
comment
@Trisped Вы хотите сказать, что where new[] { 1, 2, 3 }.Contains(x) делает меньше сравнений, чем where (x == 1 || x == 2 || x == 3)? - person tymtam; 20.09.2016
comment
@Tymski Нет, я говорю, что если вы проверяете только несколько значений или таблица слишком мала, вы не заметите разницы в производительности. - person Trisped; 20.09.2016
comment
Я считаю, что в Oracle производительность || vs Contains одинакова независимо от того, сколько || используется, а в MS SQL разница в производительности настолько велика (на 30% медленнее?). У меня такое ощущение, что ваши рассуждения о 1000, а то и о 100000 строках не соответствуют действительности. Не могли бы вы его поддержать? - person tymtam; 21.09.2016
comment
Шутки в сторону? Вы, ребята, никогда не использовали это слово как уничижительное, я предлагаю вам изменить формулировку. - person Tim Abell; 22.11.2017