Linq to Entities kullanarak 'İçerir ()' geçici çözümü?


86

Silverlight ADO.Net Data Services istemci api'sini (ve dolayısıyla Linq To Entities) kullanarak where yan tümcesinde kimliklerin listesini kullanan bir sorgu oluşturmaya çalışıyorum. Desteklenmeyen İçerikler için bir geçici çözüm bilen var mı?

Bunun gibi bir şey yapmak istiyorum:

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

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

Bunu denedim:

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

Ancak "'Herhangi' yöntemi desteklenmiyor" var.


36
Not: Entity Framework 4 (.NET 4'te) bir "İçerir" yöntemine sahiptir, sadece birisinin bunu bilmeyen bir kişi tarafından okunması durumunda. OP'nin EF1 (.NET 3.5) kullandığını biliyorum.
DarrellNorton

7
@Darrell Yorumunuzu atladığım için yarım saatimi boşa harcadım. Keşke yorumunuzun ekranda göz kırpmasını ve kayan çerçevesini çizebilseydim.
Chris Dwyer

Yanıtlar:


97

Güncelleme: EF ≥ 4 Containsdoğrudan (Checkout Any) destekler , bu nedenle herhangi bir geçici çözüme ihtiyacınız yoktur.

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);
}

KULLANIM:

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"
      );
  }
}

6
Uyarı; arg büyük bir koleksiyon olduğunda (benimki 8500 item int list idi), stack overflow. Böyle bir listeden geçmenin çılgınca olduğunu düşünebilirsiniz, ancak yine de bu yaklaşımda bir kusur olduğunu düşünüyorum.
dudeNumber4

2
Eğer Yanlışsam beni düzelt. ancak bu, aktarılan koleksiyon (filtre) boş bir küme olduğunda temelde tüm verilerle sonuçlanacağı anlamına gelir, çünkü yalnızca sorgu parametresini döndürür. Tüm değerleri filtrelemesini bekliyordum, bunu yapmanın bir yolu var mı?
Nap

1
Kontrol koleksiyonu boş olduğunda hiçbir sonuç döndürmemesi gerektiğini kastediyorsanız, yukarıdaki ön bilgide if (!collection.Any()) //action;- değiştirme eylemini en iyi performans için istenen türde boş bir sorgu döndürerek değiştirin - veya yalnızca bu satırı kaldırın.
Shimmy Weitzhandler

1
geri dön WhereIn (sorgu, seçici, koleksiyon); return WhereIn (sorgu, seçici, (IEnumerable <TValue>) koleksiyonu) ile değiştirilmelidir; istenmeyen özyinelemeyi önlemek için.
Antoine Aubry

1
Kodda bir hata olduğuna inanıyorum. Sağlanan değerler listesi boşsa, doğru davranış hiçbir sonuç döndürmemek olmalıdır - yani / koleksiyonda sorguda hiç nesne yok. Bununla birlikte, kod tam tersini yapar - hiçbiri değil, tüm değerler döndürülür. "İf (! Collection.Any ()) return query.Where (e => false)" istediğinize inanıyorum
ShadowChaser

18

Biraz e-sql kodlayarak geri dönebilirsiniz ("it" anahtar kelimesine dikkat edin):

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

İşte bir koleksiyondan bazı e-sql oluşturmak için kullandığım kod, YMMV:

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

1
"O" hakkında daha fazla bilginiz var mı? "İt" öneki MSDN örneklerinde görünüyor, ancak "o" nun ne zaman / neden gerekli olduğu hakkında hiçbir yerde bir açıklama bulamıyorum.
Robert Claypool

1
Entity Framework dinamik sorgusunda kullanıldığında, Geekswithblogs.net/thanigai/archive/2009/04/29/… ' a bir göz atın , Thanigainathan Siranjeevi bunu orada açıklıyor.
Shimmy Weitzhandler

13

Gönderen 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);
} 

ve sorgu şu hale gelir:

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

3
Bir 'İçermez' yapmak istiyorsanız, BuildContainsExpression yönteminde aşağıdaki düzenlemeleri yapmanız yeterlidir: - Expression.Equal, Expression.NotEqual olur - Expression.Or, Expression.And
Merritt

2

Silverligth'ten emin değilim, ancak nesnelere linq'te bu sorgular için her zaman () kullanıyorum.

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

5
Herhangi biri sıra türünden bir nesneyi almaz - ya hiçbir parametresi yoktur (bu durumda sadece "bu boştur ya da değildir") veya bir yüklem alır.
Jon Skeet

Bu yanıtı bulduğuma çok sevindim :) +1 Teşekkürler AndreasN
SDReyes

1

Kaydı tamamlamak için, işte nihayet kullandığım kod (hata kontrolü açıklık için atlanmıştır) ...

// 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);
        }


0

Çok teşekkürler. WhereIn extension yöntemi benim için yeterliydi. Bunun profilini çıkardım ve DataBase için e-sql ile aynı SQL komutunu oluşturdum.

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

Bunu oluşturdu:

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])


0

Üzgünüm yeni kullanıcı, gerçek cevaba yorum yapardım, ancak bunu henüz yapamam gibi görünüyor?

Her neyse, BuildContainsExpression () için örnek kodla ilgili yanıtla ilgili olarak, bu yöntemi veritabanı Varlıkları (yani bellek içi nesneler değil) üzerinde kullanıyorsanız ve IQueryable kullanıyorsanız, aslında veritabanına gitmesi gerektiğini unutmayın. çünkü temelde "where in" cümlesini kontrol etmek için çok fazla SQL "veya" koşul yapar (görmek için SQL Profiler ile çalıştırın).

Bu, bir IQueryable'ı birden çok BuildContainsExpression () ile iyileştiriyorsanız, sonunda beklediğiniz gibi çalıştırılan tek bir SQL ifadesine dönüştürmeyeceği anlamına gelebilir.

Bizim için geçici çözüm, onu tek bir SQL çağrısında tutmak için birden çok LINQ birleşimini kullanmaktı.


0

Seçilen cevaba ek olarak.

Nhibernate Expression.Orile Expression.OrElsekullanmak için değiştirin ve Unable to cast object of type 'NHibernate.Hql.Ast.HqlBitwiseOr' to type 'NHibernate.Hql.Ast.HqlBooleanExpression'istisnayı düzeltin .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.