Linq to Entities - пункт SQL "IN"


230

У T-SQL у вас може бути запит на зразок:

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

Як би ви повторили це у запиті LINQ Entities? Чи можливо це навіть?

Відповіді:


349

Вам потрібно повернути його на голову з точки зору того, як ви про це думаєте. Замість того, щоб робити "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(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;
}

можливо, я був надто швидким, щоб позначити його як відповідь, але я не отримую.
— StevenMcD

1
вірно моєму імені "FailBoy", я зрозумів це: PI помістив у рядок [], а потім використав його, і він працював. Дякую!
— StevenMcD

Вибачте, я забув створити анонімний масив;) Я виправив приклад коду. Рада, що ви зрозуміли це самостійно.
— BenAlabaster

28
Ця відповідь була б правильною, якби питання стосувалося Linq-to-SQL або Linq взагалі. Однак, оскільки в ній конкретно сказано "Linq-to-Entities", ця відповідь є невірною. array.Contains ще не підтримується Linq-to-Entities.
— КрістоферА

6
@KristoferA - це може бути правдою для більш ранніх версій EF, але мені здається, що це добре для EF4.
— Дрю Ноакс

21

Це повинно вистачити вашої мети. Він порівнює дві колекції та перевіряє, чи має одна колекція значення, що відповідають тим, що є в іншій

fea_Features.Where(s => selectedFeatures.Contains(s.feaId))

9

Якщо ви використовуєте VS2008 / .net 3.5, див. Підказку Алекса Джеймса №8: http://blogs.msdn.com/alexj/archive/2009/03/26/tip-8-writing-where-in-style -запити-використання-linq-to-elements.aspx

В іншому випадку просто використовуйте метод array.Contains (someEntity.Member).


Будь ласка, уникайте використання відповідей, що стосуються лише посилань. Ви повинні узагальнити вміст посилання у своїй відповіді, якщо посилання розривається в майбутньому.


8

У цьому контексті я піду за Внутрішнє приєднання. Якби я використовував вміст, він повторився б 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(); 

Недоліки Містить

Припустимо, у мене є два об'єкти списку.

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

Використовуючи Містить, він шукатиме кожен елемент у списку 1 у списку 2, що означає, що повторення відбудеться 49 разів !!!


5
Це повністю ігнорує той факт, що оператор перекладається на SQL. Дивіться тут .
— Герт Арнольд

5

Це може бути можливим способом прямого використання методів розширення LINQ для перевірки пункту введення

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

2

Я також намагався працювати з подібною SQL-IN річчю - запит проти моделі даних Entity . Мій підхід - це конструктор струн, щоб створити велике вираження АБО. Це жахливо потворно, але я боюся, що це єдиний шлях зараз.

Тепер добре, це виглядає приблизно так:

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, коли все покращиться. :)


2

Альтернативний спосіб відповіді 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


0

Реальний приклад:

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;

-13

Серйозно? Ви люди ніколи не використовували

where (t.MyTableId == 1 || t.MyTableId == 2 || t.MyTableId == 3)

9
-1 Спробуйте це з 20 і більше значеннями в таблиці з понад 1000 рядків, і ви швидко побачите перевагу прийнятого рішення. Крім того, не просто додати довільну кількість умов до оператора where (наприклад, якщо користувач вирішує включити параметри 1 і 2, але не 3).
— Trisped

Ну, мені не потрібно було жодного з шалених вчених, і ця відповідь проголосує, бо мені потрібні були І і 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 () ;
— JustJohn

@Trisped - кількість рядків (1000) нічого не змінює - чи я нічого не пропускаю?
— тимтам

@Tymski Так, кількість рядків має значення. Чим більше рядків, тим більше розрахунків. Те ж саме з числом можливих значень: Checks = NumValues * NumRows. Оскільки це обчислення типу M * N, якщо будь-який невеликий, то час на виконання кожної необхідної перевірки також буде невеликим. Я додав обмеження, щоб cjm30305 знав, як створити тестове середовище, де показано, чому його рішення погано.
— Trisped

@Trisped Ви хочете сказати, що where new[] { 1, 2, 3 }.Contains(x)порівняння менше where (x == 1 || x == 2 || x == 3)?
— tymtam
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.