Linq to Entities - SQL “IN” yan tümcesi


230

T-SQL'de şöyle bir sorgu olabilir:

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

Bunu bir LINQ to Entities sorgusunda nasıl çoğaltırsınız? Hatta mümkün mü?

Yanıtlar:


349

Düşünme şekliniz açısından kafasını çevirmeniz gerekiyor. Geçerli öğenin kullanıcı haklarını önceden tanımlanmış geçerli kullanıcı hakları kümesinde bulmak için "giriş" yapmak yerine, geçerli öğenin uygulanabilir değerini içeriyorsa önceden tanımlanmış bir kullanıcı hakları kümesi sorarsınız. Bu, .NET'te normal bir listede bir öğeyi bulmanızla tamamen aynıdır.

Bunu LINQ kullanarak yapmanın iki yolu vardır, biri sorgu sözdizimini, diğeri ise yöntem sözdizimini kullanır. Temel olarak, bunlar aynıdır ve tercihinize bağlı olarak birbirinin yerine kullanılabilir:

Sorgu Sözdizimi:

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

Yöntem Sözdizimi:

var selected = users.Where(u => new[] { "Admin", "User", "Limited" }.Contains(u.User_Rights));

foreach(user u in selected)
{
    //Do stuff on each selected user;
}

Bu durumda kişisel tercihim yöntem sözdizimi olabilir çünkü değişken atamak yerine, böyle bir anonim çağrı üzerinde foreach yapabiliriz:

foreach(User u in users.Where(u => new [] { "Admin", "User", "Limited" }.Contains(u.User_Rights)))
{
    //Do stuff on each selected user;
}

Sözdizimsel olarak bu daha karmaşık görünüyor ve neler olup bittiğini gerçekten anlamak için lambda ifadeleri veya delegeler kavramını anlamalısınız, ancak gördüğünüz gibi bu, kodu adil bir miktarda yoğunlaştırıyor.

Her şey kodlama stilinize ve tercihinize bağlıdır - örneklerimin üçü de aynı şeyi biraz farklı yapar.

Alternatif bir yol bile LINQ kullanmaz, aynı yöntem sözdizimini "where" yerine "FindAll" kullanarak değiştirebilir ve .NET 2.0'da da çalışacak aynı sonucu elde edebilirsiniz:

foreach(User u in users.FindAll(u => new [] { "Admin", "User", "Limited" }.Contains(u.User_Rights)))
{
    //Do stuff on each selected user;
}

belki cevap olarak işaretlemek için çok hızlıydım, ama alamıyorum. {"Yönetici", "Kullanıcı", "Sınırlı"} sonra içerir VS2008 bu kodu bir bit sevmez.
StevenMcD

1
Benim adıma "FailBoy" doğru anladım: PI bir dize [] koymak ve sonra onu kullandı ve çalıştı. Teşekkürler!
StevenMcD

üzgünüm, anonim dizi kadar yeni unuttum;) Kod örneğimi sabit. Gerçi kendi başınıza çözdüğünüze sevindim.
BenAlabaster

28
Soru genel olarak Linq-to-SQL veya Linq ile ilgili olsaydı, bu cevap doğru olurdu. Ancak, özellikle "Linq-to-Entities" yazdığı için bu cevap yanlıştır. array.Contains (henüz) Linq-to-Entities tarafından desteklenmemektedir.
KristoferA

6
@KristoferA - EF'in önceki sürümleri için geçerli olabilir, ancak EF4 ile benim için iyi görünüyor.
Drew Noakes

21

Bu sizin amacınıza yeter. İki koleksiyonu karşılaştırır ve bir koleksiyonun diğer koleksiyondakilerle eşleşen değerlere sahip olup olmadığını kontrol eder

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

9

VS2008 / .net 3.5 kullanıyorsanız, Alex James'in # 8 numaralı ipucuna bakın: http://blogs.msdn.com/alexj/archive/2009/03/26/tip-8-writing-where-in-style -queries kullanan-linq-to-entities.aspx

Aksi takdirde array.Contains (someEntity.Member) yöntemini kullanın.


Lütfen yalnızca bağlantı yanıtlarını kullanmaktan kaçının, bağlantının gelecekte kesilmesi durumunda bağlantınızın içeriğini özetlemelisiniz.


8

Bu bağlamda İçsel Katılım'a gideceğim. Eğer içerecek olsaydım, tek bir maç olmasına rağmen 6 kez tekrar ederdi.

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

İçeriğin Dezavantajları

İki liste nesnem olduğunu varsayalım.

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

İçerir kullanarak, Liste 2'deki her Liste 1 öğesini arayacak, bu da yinelemenin 49 kez olacağı anlamına gelir !!!


5
Bu, ifadenin SQL'e çevrildiği gerçeğini tamamen yok sayar. Buraya bakın .
Gert Arnold

5

Bu, doğrudan yan tümceyi denetlemek için LINQ genişletme yöntemlerini kullanmanın olası yolu olabilir

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

2

Ayrıca bir varlık veri modeli karşı sorgulama - bir SQL-IN benzeri bir şey ile çalışmaya çalıştı . Benim yaklaşım büyük bir OR-ifadesi oluşturmak için bir dize oluşturucu. Bu çok çirkin, ama korkarım şu an gitmek için tek yol bu.

Şimdi, bu şöyle görünüyor:

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

Bu bağlamda GUID'lerle çalışma : Yukarıda görebileceğiniz gibi, sorgu dizesi parçalarında GUID'den önce her zaman "GUID" sözcüğü vardır. Bunu eklemezseniz ObjectQuery<T>.Where, aşağıdaki istisnayı atar:

'Edm.Guid' ve 'Edm.String' bağımsız değişken türleri bu işlem için uyumsuzdur., Neredeyse ifade, satır 6, sütun 14'e eşittir.

Bunu MSDN Forumlarında buldum, aklınızda bulundurmanız yararlı olabilir.

Matthias

... her şey daha iyi olduğunda .NET ve Entity Framework'ün bir sonraki sürümünü dört gözle bekliyorum. :)


2

BenAlabaster cevabına alternatif bir yöntem

Her şeyden önce, sorguyu şu şekilde yeniden yazabilirsiniz:

var matches = from Users in people
        where Users.User_Rights == "Admin" ||
              Users.User_Rights == "Users" || 
              Users.User_Rights == "Limited"
        select Users;

Kesinlikle bu daha 'garip' ve yazmak için bir acı ama hepsi aynı şekilde çalışıyor.

Dolayısıyla, bu tür LINQ ifadeleri oluşturmayı kolaylaştıran bir yarar yöntemimiz olsaydı, işimizde olurduk.

yerinde bir yardımcı program ile böyle bir şey yazabilirsiniz:

var matches = ctx.People.Where(
        BuildOrExpression<People, string>(
           p => p.User_Rights, names
        )
);

Bu, aşağıdakiyle aynı etkiye sahip bir ifade oluşturur:

var matches = from p in ctx.People
        where names.Contains(p.User_Rights)
        select p;

Ama daha da önemlisi aslında .NET 3.5 SP1'e karşı çalışıyor.

İşte bunu mümkün kılan sıhhi tesisat fonksiyonu:

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

Ben esas olarak valueSelector (yani p => p.User_Rights) ve birlikte tam için bir ifade oluşturmak için tahmin ORs kullanarak tüm değerler için yüklem ifadesi oluşturur söylemek dışında bu yöntemi açıklamaya çalışacağız yüklem

Kaynak: http://blogs.msdn.com/b/alexj/archive/2009/03/26/tip-8-writing-where-in-style-queries-using-linq-to-entities.aspx


0

Gerçek örnek:

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;

-14

Ciddi anlamda? Siz hiç kullanmadınız

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

9
-1 Bunu 1000'den fazla satır içeren bir tabloda 20 veya daha fazla değerle deneyin ve kabul edilen çözümün avantajını hızlı bir şekilde göreceksiniz. Ayrıca where deyimine isteğe bağlı sayıda koşul eklemek kolay değildir (kullanıcı seçenek 1 ve 2'yi dahil etmeyi seçiyorsa, ancak 3 değil).
Trisped

Çılgın bilim adamı malzemelerinden birine ihtiyacım yoktu ve bu cevap oyuma gitti çünkü bir AND ve 2 ORS var SamplePoints = (_db.tblPWS_WSF_SPID_ISN_Lookup.OrderBy (x => x.WSFStateCode içinde c'den) gerektiğinde c. PWS == id && ((c.WSFStateCode.Substring (0, 2) == "SR") || (c.WSFStateCode.Substring (0, 2) == "CH")) c) seçin. ;
JustJohn

@Trisped - satır sayısı (1000) hiçbir şeyi değiştirmez - yoksa bir şey mi kaçırıyorum?
timtam

@Tymski Evet, satır sayısı önemlidir. Daha fazla satır, daha fazla hesaplama. Olası değerlerin sayısı ile aynı: Checks = NumValues * NumRows. Bu bir M * N tipi hesaplama olduğundan, her ikisi de küçükse, gerekli her bir kontrolü gerçekleştirme süresi de küçük olacaktır. Kısıtlamayı ekledim, böylece cjm30305, çözümünün neden zayıf olduğunu gösteren bir test ortamının nasıl kurulacağını bilecekti.
16:59

@Trisped Bunu mu söylüyorsun where new[] { 1, 2, 3 }.Contains(x) daha az karşılaştırmawhere (x == 1 || x == 2 || x == 3) ?
Timtam
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.