Обходной путь '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" не поддерживается ".


person James Bloomer    schedule 17.12.2008    source источник
comment
Примечание. Entity Framework 4 (в .NET 4) имеет метод Contains, на всякий случай, если кто-то читает это, но не знает об этом. Я знаю, что OP использовал EF1 (.NET 3.5).   -  person DarrellNorton    schedule 20.12.2010
comment
@ Даррелл Я потратил полчаса впустую, потому что пропустил твой комментарий. Хотел бы я, чтобы ваш комментарий мигал и выделялся на экране.   -  person Chris    schedule 15.01.2011


Ответы (10)


Обновление: EF ≥ 4 поддерживает _ 1_ напрямую (Checkout _ 2_), поэтому обходной путь не требуется.

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"
      );
  }
}
person Shimmy Weitzhandler    schedule 01.07.2009
comment
(комментарий к соответствующему вопросу удален, так как был удален автором) - person Marc Gravell; 05.01.2010
comment
Действительно красиво, элегантно ... Хорошо работает. - person Julien N; 11.05.2010
comment
Еще одна хорошая идея - иметь ту же функцию, объявляющую параметр коллекции как paramarray TValue []. это даст вам неограниченный контроль, так что вы можете вручную указать элементы как коллекцию, при необходимости я уточню подробнее. - person Shimmy Weitzhandler; 12.05.2010
comment
Предупреждение; когда arg является большой коллекцией (у меня было 8500 элементов int list), переполнение стека. Вы можете подумать, что проповедовать такой список - это безумие, но, тем не менее, я думаю, что это обнажает изъян в таком подходе. - person dudeNumber4; 23.08.2010
comment
Поправьте меня, если я ошибаюсь. но это означает, что когда переданная коллекция (фильтр) является пустым набором, это в основном приведет к получению всех данных, потому что он только что вернул параметр запроса. Я ожидал, что он отфильтрует все значения, есть ли способ сделать это? - person Nap; 22.09.2010
comment
Если вы имеете в виду, что когда проверяемая коллекция пуста, она не должна возвращать результатов, в приведенном выше фрагменте замените действие if (!collection.Any()) //action; - заменить простым возвратом пустого запроса запрошенного типа для лучшей производительности - или просто удалите эту строку. - person Shimmy Weitzhandler; 22.09.2010
comment
return WhereIn (запрос, селектор, коллекция); следует заменить на return WhereIn (запрос, селектор, (IEnumerable ‹TValue›) коллекция); чтобы избежать нежелательной рекурсии. - person Antoine Aubry; 26.01.2011
comment
Можете ли вы предоставить ссылку на документацию, подтверждающую, что Contains поддерживается в EF4? У меня проблемы с отслеживанием. Спасибо! - person grimus; 11.05.2011
comment
@grimus, я тестировал, работает, в чем проблема. - person Shimmy Weitzhandler; 11.05.2011
comment
@Shimmy Я просто хочу посмотреть, было ли это где-нибудь задокументировано. У меня нет проблем. - person grimus; 11.05.2011
comment
@grimus, я как-то видел, как это задокументировано в соединении и было помечено как исправленное, но не смог до него добраться. - person Shimmy Weitzhandler; 11.05.2011
comment
Я считаю, что в коде есть ошибка. Если предоставленный список значений пуст, правильное поведение должно заключаться в том, чтобы не возвращать результатов - т. Е. Объекты запроса отсутствуют в коллекции. Однако код делает прямо противоположное - возвращаются все значения, а не ни одно из них. Я считаю, что вы хотите, чтобы if (! Collection.Any ()) возвращал запрос.Где (e = ›false) - person ShadowChaser; 29.02.2012
comment
@Shimmy - Как мне сделать что-то подобное, где я должен явно указать типы. predicate = predicate.And(x=>Extensions.WhereIn<>(x.id,ids));. x - это Person Entity, а id и ids - это strings. Я не уверен, что поставить в скобки WhereIn. - person Xaisoft; 30.05.2013
comment
Какие изменения потребуются для создания метода WhereNotIn ()? - person avenmore; 15.07.2014
comment
@ dudeNumber4 Вы нашли способ обойти эту проблему? - person Romain Vergnory; 18.08.2015
comment
@RomainVergnory Оглядываясь назад на этот код, похоже, что я в конечном итоге использовал сохраненную процедуру в Sql Server. Я передал список целых чисел в sproc в виде строки, разделенной запятыми (varchar max), которую я превратил в заднюю часть в набор внутри sproc. Взломать точно. - person dudeNumber4; 20.08.2015

Вы можете вернуться к написанию кода 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) + "}");
person Rob Fonseca-Ensor    schedule 01.01.2009
comment
У вас есть дополнительная информация об этом? Префикс it появляется в примерах MSDN, но нигде я не могу найти объяснения, когда и зачем он нужен. - person Robert Claypool; 20.02.2009
comment
Используемый в динамическом запросе Entity Framework, взгляните на geekswithblogs.net/thanigai/archive/2009/04/29/, Танигайнатан Сирандживи объясняет это там. - person Shimmy Weitzhandler; 25.01.2010

Из 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));
person James Bloomer    schedule 17.12.2008
comment
Если вы хотите сделать «Не содержит», просто внесите следующие изменения в метод BuildContainsExpression: - Expression.Equal становится Expression.NotEqual - Expression.Or становится Expression.And - person Merritt; 25.06.2009

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

var q = from t in svc.OpenTranaction
        where txnIds.Any(t.OpenTransactionId)
        select t;
person AndreasN    schedule 17.12.2008
comment
Any не принимает объект типа последовательность - он либо не имеет параметров (в этом случае он просто пуст или нет), либо принимает предикат. - person Jon Skeet; 17.12.2008
comment
Я ужасно рад, что нашел этот ответ :) +1 Спасибо, АндреасN - person SDReyes; 23.02.2010

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

// 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);
        }
person James Bloomer    schedule 23.12.2009

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

person Phani Raj    schedule 24.12.2008

Большое спасибо. Мне хватило метода расширения 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])
person javierlinked    schedule 23.07.2009

Я думаю, что присоединение к 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

person Gabriel Chung    schedule 26.08.2009

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

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

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

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

person Shannon    schedule 30.04.2010

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

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

person smg    schedule 11.06.2015