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.