Обходной путь 'Contains ()' с использованием Linq to Entities?

Я пытаюсь создать запрос, который использует список идентификаторов в предложении where, используя клиентский api Silverlight ADO.Net Data Services (и, следовательно, Linq To Entities). Кто-нибудь знает об обходном пути, когда Contains не поддерживается?

Я хочу сделать что-то вроде этого:

List<long?> txnIds = new List<long?>();
// Fill list 

var q = from t in svc.OpenTransaction
        where txnIds.Contains(t.OpenTransactionId)
        select t;

Пробовал это:

var q = from t in svc.OpenTransaction
where txnIds.Any<long>(tt => tt == t.OpenTransactionId)
select t;

Но получил "Метод Any" не поддерживается ".

Примечание. Entity Framework 4 (в .NET 4) имеет метод «Содержит» на тот случай, если кто-то случайно об этом прочитает. Я знаю, что OP использовал EF1 (.NET 3.5).

DarrellNorton 20.12.2010 22:46

@ Даррелл Я потратил полчаса впустую, потому что пропустил твой комментарий. Хотел бы я, чтобы ваш комментарий мигал и выделялся на экране.

Chris Dwyer 15.01.2011 03:09
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
86
2
77 836
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

Я не уверен насчет Silverlight, но в linq to objects я всегда использую any () для этих запросов.

var q = from t in svc.OpenTranaction
        where txnIds.Any(t.OpenTransactionId)
        select t;

Any не принимает объект типа «последовательность» - он либо не имеет параметров (в этом случае он просто «пуст или нет»), либо принимает предикат.

Jon Skeet 17.12.2008 14:44

Я ужасно рад, что нашел этот ответ :) +1 Спасибо, АндреасN

SDReyes 23.02.2010 22:59

От MSDN:

static Expression<Func<TElement, bool>> BuildContainsExpression<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();

    // p => valueSelector(p) == values[0] || valueSelector(p) == ...
    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);
} 

и запрос становится:

var query2 = context.Entities.Where(BuildContainsExpression<Entity, int>(e => e.ID, ids));

Если вы хотите сделать «Не содержит», просто внесите следующие изменения в метод BuildContainsExpression: - Expression.Equal становится Expression.NotEqual - Expression.Or становится Expression.And

Merritt 25.06.2009 19:11

Вот пример, в котором я демонстрирую, как писать запросы на основе наборов с использованием DataServiceContext: http://blogs.msdn.com/phaniraj/archive/2008/07/17/set-based-operations-in-ado-net-data-services.aspx

Вы можете вернуться к написанию кода e-sql (обратите внимание на ключевое слово "it"):

return CurrentDataSource.Product.Where("it.ID IN {4,5,6}"); 

Вот код, который я использовал для создания некоторого e-sql из коллекции YMMV:

string[] ids = orders.Select(x=>x.ProductID.ToString()).ToArray();
return CurrentDataSource.Products.Where("it.ID IN {" + string.Join(",", ids) + "}");

У вас есть дополнительная информация об этом? Префикс «it» появляется в примерах MSDN, но я нигде не могу найти объяснение, когда и зачем «это» необходимо.

Robert Claypool 20.02.2009 16:48

Используется в динамическом запросе Entity Framework, взгляните на geekswithblogs.net/thanigai/archive/2009/04/29/…, Танигайнатан Сирандживи объясняет это там.

Shimmy Weitzhandler 25.01.2010 11:26
Ответ принят как подходящий

Обновлять: EF ≥ 4 поддерживает Contains напрямую (Checkout Any), поэтому вам не нужно никакого обходного пути.

public static IQueryable<TEntity> WhereIn<TEntity, TValue>
  (
    this ObjectQuery<TEntity> query,
    Expression<Func<TEntity, TValue>> selector,
    IEnumerable<TValue> collection
  )
{
  if (selector == null) throw new ArgumentNullException("selector");
  if (collection == null) throw new ArgumentNullException("collection");
  if (!collection.Any()) 
    return query.Where(t => false);

  ParameterExpression p = selector.Parameters.Single();

  IEnumerable<Expression> equals = collection.Select(value =>
     (Expression)Expression.Equal(selector.Body,
          Expression.Constant(value, typeof(TValue))));

  Expression body = equals.Aggregate((accumulate, equal) =>
      Expression.Or(accumulate, equal));

  return query.Where(Expression.Lambda<Func<TEntity, bool>>(body, p));
}

//Optional - to allow static collection:
public static IQueryable<TEntity> WhereIn<TEntity, TValue>
  (
    this ObjectQuery<TEntity> query,
    Expression<Func<TEntity, TValue>> selector,
    params TValue[] collection
  )
{
  return WhereIn(query, selector, (IEnumerable<TValue>)collection);
}

ПРИМЕНЕНИЕ:

public static void Main()
{
  using (MyObjectContext context = new MyObjectContext())
  {
    //Using method 1 - collection provided as collection
    var contacts1 =
      context.Contacts.WhereIn(c => c.Name, GetContactNames());

    //Using method 2 - collection provided statically
    var contacts2 = context.Contacts.WhereIn(c => c.Name,
      "Contact1",
      "Contact2",
      "Contact3",
      "Contact4"
      );
  }
}

(комментарий "связанный вопрос" удален, так как был удален автором)

Marc Gravell 05.01.2010 09:12

Действительно красиво, элегантно ... Хорошо работает.

Julien N 11.05.2010 16:38

Еще одна хорошая идея - иметь ту же функцию, объявляющую параметр коллекции как paramarray TValue []. это даст вам неограниченный контроль, чтобы вы могли вручную указывать элементы как коллекцию, при необходимости я уточню подробнее.

Shimmy Weitzhandler 12.05.2010 14:40

Предупреждение; когда arg является большой коллекцией (у меня было 8500 элементов int list), переполнение стека. Вы можете подумать, что пропустить такой список - это безумие, но, тем не менее, я думаю, что это обнажает изъян в этом подходе.

dudeNumber4 23.08.2010 19:48

Поправьте меня, если я ошибаюсь. но это означает, что когда переданная коллекция (фильтр) является пустым набором, это в основном приведет к получению всех данных, потому что он только что вернул параметр запроса. Я ожидал, что он отфильтрует все значения, есть ли способ сделать это?

Nap 22.09.2010 11:05

Если вы имеете в виду, что когда проверяемая коллекция пуста, она не должна возвращать результатов, в приведенном выше фрагменте кода замените if (!collection.Any()) //action; - замените действие простым возвратом пустого запроса запрошенного типа для лучшей производительности - или просто удалите эту строку.

Shimmy Weitzhandler 22.09.2010 12:59

return WhereIn (запрос, селектор, коллекция); следует заменить на return WhereIn (query, selector, (IEnumerable <TValue>) collection); чтобы избежать нежелательной рекурсии.

Antoine Aubry 26.01.2011 18:03

Можете ли вы предоставить ссылку на документацию, подтверждающую, что Contains поддерживается в EF4? У меня проблемы с отслеживанием. Спасибо!

grimus 11.05.2011 00:09

@grimus, я тестировал, работает, в чем проблема.

Shimmy Weitzhandler 11.05.2011 00:39

@Shimmy Я просто хочу посмотреть, было ли это где-нибудь задокументировано. У меня нет проблем.

grimus 11.05.2011 01:29

@grimus, я однажды видел, как это задокументировано в соединении и было помечено "как исправлено", но не смог до него добраться.

Shimmy Weitzhandler 11.05.2011 02:03

Я считаю, что в коде есть ошибка. Если предоставленный список значений пуст, правильное поведение должно заключаться в том, чтобы не возвращать результатов - т. Е. Объекты запроса отсутствуют в коллекции. Однако код делает прямо противоположное - возвращаются все значения, а не ни одно из них. Я считаю, что вы хотите "if (! Collection.Any ()) вернуть query.Where (e => false)"

ShadowChaser 29.02.2012 23:06

@Shimmy - Как мне сделать что-то подобное, где я должен явно указать типы. predicate = predicate.And(x=>Extensions.WhereIn<>(x.id,ids));. x представляет собой Person Entity, а id и ids оба являются strings. Я не уверен, что ставить между скобками WhereIn.

Xaisoft 30.05.2013 20:34

Какие изменения потребуются для создания метода «WhereNotIn ()»?

avenmore 15.07.2014 16:39

@ dudeNumber4 Вы нашли способ обойти эту проблему?

Romain Vergnory 18.08.2015 16:00

@RomainVergnory Оглядываясь назад на этот код, похоже, что я в конечном итоге использовал сохраненную процедуру в Sql Server. Я передал список целых чисел в sproc в виде строки, разделенной запятыми (varchar max), которую я превратил в заднюю часть в набор внутри sproc. Взломать точно.

dudeNumber4 20.08.2015 16:41

Огромное спасибо. Мне было достаточно метода расширения WhereIn. Я профилировал его и сгенерировал ту же команду SQL для базы данных, что и e-sql.

public Estado[] GetSomeOtherMore(int[] values)
{
    var result = _context.Estados.WhereIn(args => args.Id, values) ;
    return result.ToArray();
}

Создано это:

SELECT 
[Extent1].[intIdFRLEstado] AS [intIdFRLEstado], 
[Extent1].[varDescripcion] AS [varDescripcion]
FROM [dbo].[PVN_FRLEstados] AS [Extent1]
WHERE (2 = [Extent1].[intIdFRLEstado]) OR (4 = [Extent1].[intIdFRLEstado]) OR (8 = [Extent1].[intIdFRLEstado])

Я думаю, что присоединение к LINQ может быть обходным решением.

Однако я не тестировал код. Надеюсь, поможет. Ваше здоровье. :-)

List<long?> txnIds = new List<long?>();
// Fill list 

var q = from t in svc.OpenTransaction
        join tID in txtIds on t equals tID
        select t;

Присоединяйтесь к LINQ:

http://weblogs.asp.net/salimfayad/archive/2008/07/09/linq-to-entities-join-queries.aspx

Вот код, который я наконец использовал (проверка ошибок опущена для ясности) ...

// How the function is called
var q = (from t in svc.OpenTransaction.Expand("Currency,LineItem")
         select t)
         .Where(BuildContainsExpression<OpenTransaction, long>(tt => tt.OpenTransactionId, txnIds));



 // The function to build the contains expression
   static System.Linq.Expressions.Expression<Func<TElement, bool>> BuildContainsExpression<TElement, TValue>(
                System.Linq.Expressions.Expression<Func<TElement, TValue>> valueSelector, 
                IEnumerable<TValue> values)
        {
            if (null == valueSelector) { throw new ArgumentNullException("valueSelector"); }
            if (null == values) { throw new ArgumentNullException("values"); }
            System.Linq.Expressions.ParameterExpression p = valueSelector.Parameters.Single();

            // p => valueSelector(p) == values[0] || valueSelector(p) == ...
            if (!values.Any())
            {
                return e => false;
            }

            var equals = values.Select(value => (System.Linq.Expressions.Expression)System.Linq.Expressions.Expression.Equal(valueSelector.Body, System.Linq.Expressions.Expression.Constant(value, typeof(TValue))));
            var body = equals.Aggregate<System.Linq.Expressions.Expression>((accumulate, equal) => System.Linq.Expressions.Expression.Or(accumulate, equal));
            return System.Linq.Expressions.Expression.Lambda<Func<TElement, bool>>(body, p);
        }

Извините, новый пользователь, я бы прокомментировал фактический ответ, но, кажется, я еще не могу этого сделать?

В любом случае, что касается ответа с образцом кода для BuildContainsExpression (), имейте в виду, что если вы используете этот метод для объектов базы данных (то есть не в объектах в памяти), и вы используете IQueryable, он действительно должен перейти в базу данных поскольку он в основном выполняет множество условий SQL »или« для проверки предложения «where in» (запустите его с помощью SQL Profiler, чтобы увидеть).

Это может означать, что если вы уточняете IQueryable с помощью нескольких BuildContainsExpression (), он не превратит его в один оператор SQL, который запускается в конце, как вы ожидаете.

Обходной путь для нас состоял в том, чтобы использовать несколько соединений LINQ, чтобы сохранить один вызов SQL.

В дополнение к выбранному ответу.

Замените Expression.Or на Expression.OrElse для использования с Nhibernate и исправьте исключение Unable to cast object of type 'NHibernate.Hql.Ast.HqlBitwiseOr' to type 'NHibernate.Hql.Ast.HqlBooleanExpression'.

Другие вопросы по теме