Maksimum veya Varsayılan?


176

Satır döndürmeyen bir LINQ sorgusundan Max değerini almanın en iyi yolu nedir? Sadece yaparsam

Dim x = (From y In context.MyTable _
         Where y.MyField = value _
         Select y.MyCounter).Max

Sorgu hiçbir satır döndürmediğinde bir hata alıyorum. Yapabilirim

Dim x = (From y In context.MyTable _
         Where y.MyField = value _
         Select y.MyCounter _
         Order By MyCounter Descending).FirstOrDefault

ama bu böylesine basit bir istek için biraz küstahlık hissi veriyor. Bunu yapmanın daha iyi bir yolunu mu kaçırıyorum?

GÜNCELLEME: İşte arka hikaye: Bir alt tablodan bir sonraki uygunluk sayacını almaya çalışıyorum (eski sistem, beni başlatma ...). Her hasta için ilk uygunluk sırası her zaman 1'dir, ikincisi 2'dir, vb. (Açıkçası bu alt tablonun birincil anahtarı değildir). Bu nedenle, bir hasta için mevcut maksimum sayaç değerini seçiyorum ve sonra yeni bir satır oluşturmak için buna 1 ekliyorum. Mevcut bir alt değer olmadığında, 0 döndürmek için sorguya ihtiyacım var (böylece 1 ekleyerek bana 1 sayaç değeri verecek). Eski uygulama sayaç değerlerinde boşluklar (olası) içeriyorsa, alt satırların ham sayısına güvenmek istemediğimi unutmayın. Soruyu çok genel yapmaya çalıştığım için üzüldüm.

Yanıtlar:


206

Yana DefaultIfEmptyLINQ SQL için uygulanmadı, ben iade hata üzerinde arama yaptı ve bir buldum büyüleyici makale toplam işlevleri null setleri ile fırsatlar olduğunu. Bulduğumu özetlemek için, seçiminiz dahilindeki boş bir karaktere döküm yaparak bu sınırlamanın üstesinden gelebilirsiniz. Benim VB biraz paslı, ama bence böyle bir şey gitmek istiyorum:

Dim x = (From y In context.MyTable _
         Where y.MyField = value _
         Select CType(y.MyCounter, Integer?)).Max

Veya C # ile:

var x = (from y in context.MyTable
         where y.MyField == value
         select (int?)y.MyCounter).Max();

1
VB'yi düzeltmek için, Select "Select CType (y.MyCounter, Integer?)" Olacaktır. Hiçbir şeyi 0'a dönüştürmek için orijinal bir kontrol yapmam gerekiyor, ancak sonuçları istisnasız elde etmeyi seviyorum.
gfrizzle

2
DefaultIfEmpty'nin iki aşırı yüklenmesinden biri, parametre almayan LINQ to SQL'de desteklenir.
DamienG

Muhtemelen bu bilgiler güncel değil, çünkü LINQ to SQL'deki DefaultIfEmpty'nin her iki formunu da başarıyla test ettim
Neil

3
@Neil: lütfen cevap ver. DefaultIfEmpty benim için çalışmıyor: Maxa DateTime. Max(x => (DateTime?)x.TimeStamp)hala tek yol ..
duedl0r

1
DefaultIfEmpty şimdi LINQ SQL için uygulanan olmasına rağmen, bu yanıt SQL deyimi döndürür bir işte SEÇ MyCounter 'in DefaultIfEmpty sonuçlarını kullanma gibi IMO daha iyi kalır her değerin ilişkin satırda toplanır ediliyor , MAX (MyCounter) ki bu cevap sonuçlarının oysa döner bir tek, özetlenmiş satır. (EntityFrameworkCore 2.1.3'te test edilmiştir.)
Carl Sharman

107

Ben sadece benzer bir sorun vardı, ama bir sorguda sözdizimi yerine LINQ uzantısı yöntemleri kullanıyordum. Nullable numarasına döküm burada da çalışır:

int max = list.Max(i => (int?)i.MyCounter) ?? 0;

48

Bir davaya benziyor DefaultIfEmpty(test edilmemiş kod takip ediyor):

Dim x = (From y In context.MyTable _
         Where y.MyField = value _
         Select y.MyCounter).DefaultIfEmpty.Max

DefaultIfEmpty ile aşina değilim, ama yukarıdaki sözdizimini kullanırken "SQL olarak yürütme için 'OptionalValue' düğümü biçimlendirilemedi" olsun. Ayrıca varsayılan bir değer (sıfır) sağlamaya çalıştım, ama o da hoşlanmadı.
gfrizzle

Ah. Görünüşe göre DefaultIfEmpty LINQ to SQL desteklenmiyor. İlk önce .ToList ile bir listeye yayınlayarak bunu başarabilirsiniz, ancak bu önemli bir performans hitidir.
Jacob Proffitt

3
Teşekkürler, tam da aradığım şey buydu. Uzantı yöntemlerini kullanarak:var colCount = RowsEnumerable.Select(row => row.Cols.Count).DefaultIfEmpty().Max()
Jani

35

Ne istediğini düşün!

En fazla {1, 2, 3, -1, -2, -3} 3'tür. En fazla {2} en fazla 2'dir. Ama boş kümenin maksimum değeri {} nedir? Açıkçası bu anlamsız bir soru. Boş kümenin maksimum değeri basitçe tanımlanmamıştır. Cevap almaya çalışmak matematiksel bir hatadır. Herhangi bir kümenin maksimum değeri, bu kümedeki bir öğe olmalıdır. Boş kümenin hiçbir öğesi yoktur, bu nedenle belirli bir sayının bu kümede bulunmadan o kümenin maksimum değeri olduğunu iddia etmek matematiksel bir çelişkidir.

Tıpkı programcı sıfıra bölünmesini istediğinde bilgisayarın bir istisna atması gibi doğru bir davranış olduğu gibi, programcı boş kümenin maks. Değerini almasını istediğinde bilgisayarın bir istisna atması doğru bir davranıştır. Sıfıra bölme, boş kümenin maksimumunu almak, spacklerorke'yi kıpırdatmak ve uçan tek boynuzlu atı Neverland'a sürmek anlamsız, imkansız, tanımsızdır.

Şimdi, gerçekte ne yapmak istiyorsunuz?


İyi bir nokta - sorumu kısa süre içinde bu ayrıntılarla güncelleyeceğim. Aralarından seçim yapabileceğiniz hiçbir kayıt olmadığında 0'ı istediğimi bildiğimi söylemek yeterlidir, bu da nihai çözüm üzerinde kesinlikle bir etkiye sahiptir.
gfrizzle

17
Sık sık tek boynuzlu atımı Neverland'e uçurmaya çalışıyorum ve çabalarımın anlamsız ve tanımsız olduğu fikrini kabul ediyorum.
Chris,

2
Bu tartışmanın doğru olduğunu düşünmüyorum. Açıkça linq-sql, ve sql sıfır sıfırdan fazla satır null olarak tanımlanır, değil mi?
duedl0r

4
Linq, genellikle sorgunun nesneler üzerinde bellekte yürütülüp yürütülmediği veya sorgunun veritabanında satırlar üzerinde yürütülüp yürütülmediği konusunda aynı sonuçları üretmelidir. Linq sorguları Linq sorgularıdır ve hangi adaptörün kullanımda olduğuna bakılmaksızın, sadık bir şekilde yürütülmelidir.
yfeldblum

1
Teoride Linq sonuçlarının bellekte veya sql'de aynı olması gerektiğine katılırken, aslında biraz daha derine inerseniz, bunun neden her zaman böyle olamayacağını keşfedersiniz. Linq ifadeleri karmaşık ifade çevirisi kullanılarak sql'ye çevrilir. Basit birebir çeviri değildir. Bir fark null örneğidir. C # "null == null" doğrudur. SQL'de "null == null" eşleşmeleri dış birleşimler için dahil edilir, ancak iç birleşimler için eklenmez. Ancak, iç birleşimler neredeyse her zaman istediğiniz şeydir, bu yüzden varsayılanlardır. Bu, davranışta olası farklılıklara neden olur.
Curtis Yallop

25

Diziye her zaman ekleyebilirsiniz Double.MinValue. Bu, en az bir elemanın olmasını ve Maxsadece asgari olması durumunda onu döndürmesini sağlayacaktır. Daha verimlidir (hangi seçeneği belirlemek için Concat, FirstOrDefaultya Take(1)), Yeterli kıyaslama gerçekleştirmelidir.

double x = context.MyTable
    .Where(y => y.MyField == value)
    .Select(y => y.MyCounter)
    .Concat(new double[]{Double.MinValue})
    .Max();

10
int max = list.Any() ? list.Max(i => i.MyCounter) : 0;

Listede herhangi bir öğe varsa (boş değil), Sayım Alanımın maksimumu alır, aksi takdirde 0 döndürür.


3
Bu 2 sorgu çalıştırmaz mı?
andreapier

10

Net 3.5'ten beri, varsayılan değeri bağımsız değişken olarak ileten DefaultIfEmpty () kullanabilirsiniz. Aşağıdaki yollardan biri gibi bir şey:

int max = (from e in context.Table where e.Year == year select e.RecordNumber).DefaultIfEmpty(0).Max();
DateTime maxDate = (from e in context.Table where e.Year == year select e.StartDate ?? DateTime.MinValue).DefaultIfEmpty(DateTime.MinValue).Max();

Birincisi NOT NULL sütununu sorguladığınızda izin verilir ve ikincisi NULLABLE sütununu sorgulamak için kullanılan yöntemdir. DefaultIfEmpty () öğesini bağımsız değişkenler olmadan kullanırsanız, varsayılan değerler, Varsayılan Değerler Tablosunda görebileceğiniz gibi, çıktı türünüz için tanımlanan değer olacaktır .

Ortaya çıkan SELECT çok zarif olmayacak, ancak kabul edilebilir.

Umarım yardımcı olur.


7

Sorunun hiçbir sonuç olmadığında ne olmasını istediğinizi düşünüyorum. Bu istisnai bir durumda, ben bir try / catch bloğundaki sorguyu sarmak ve standart sorgu oluşturur istisna işlemek. Sorgunun hiçbir sonuç döndürmemesi uygunsa, o durumda sonucun ne olmasını istediğinizi bulmanız gerekir. @ David'in cevabı olabilir (veya benzer bir şey işe yarayabilir). Yani, MAX her zaman pozitif olacaksa, listeye yalnızca hiçbir sonuç yoksa seçilecek bilinen bir "kötü" değer eklemek yeterli olabilir. Genel olarak, üzerinde çalışmak için bazı veriler olması için maksimum alan bir sorgu beklerdim ve aksi takdirde her zaman elde ettiğiniz değerin doğru olup olmadığını kontrol etmek zorunda olduğu gibi denemek / yakalamak yol gitmek istiyorum. BEN'

Try
   Dim x = (From y In context.MyTable _
            Where y.MyField = value _
            Select y.MyCounter).Max
   ... continue working with x ...
Catch ex As SqlException
       ... do error processing ...
End Try

Benim durumumda, hiçbir satır döndürmemek daha sık gerçekleşmez (eski sistem, hasta daha önce uygun olabilir veya olmayabilir, falan falan filan). Bu daha istisnai bir durum olsaydı, muhtemelen bu rotaya giderdim (ve yine de daha iyi göremiyorum).
gfrizzle

6

Başka bir olasılık, ham SQL'de nasıl yaklaşabileceğinize benzer şekilde gruplama olabilir:

from y in context.MyTable
group y.MyCounter by y.MyField into GrpByMyField
where GrpByMyField.Key == value
select GrpByMyField.Max()

Tek şey (tekrar LINQPad'de test etmek) VB LINQ lezzetine geçmek, gruplama cümlesinde sözdizimi hataları verir. Eminim kavramsal eşdeğer bulabilecek kadar kolaydır, sadece VB'ye nasıl yansıtacağımı bilmiyorum.

Oluşturulan SQL, aşağıdaki satırlarda bir şey olacaktır:

SELECT [t1].[MaxValue]
FROM (
    SELECT MAX([t0].[MyCounter) AS [MaxValue], [t0].[MyField]
    FROM [MyTable] AS [t0]
    GROUP BY [t0].[MyField]
    ) AS [t1]
WHERE [t1].[MyField] = @p0

İç içe SELECT, sorgu yürütme tüm satırları alır gibi geri alınan kümeden eşleşen birini seçer gibi icky görünüyor ... soru SQL Server sorgu iç SELECT için nereye yan tümcesini uygulamak karşılaştırılabilir bir şey optimize olup olmadığını sorusudur. Şimdi bakıyorum ...

SQL Server'da yürütme planlarını yorumlama konusunda bilgili değilim, ancak WHERE yan tümcesi dış SELECT'te olduğunda, bu adımda elde edilen gerçek satırların sayısı, yalnızca eşleşen satırlara göre tablodaki tüm satırlardır. WHERE yan tümcesi iç SELECT öğesinde olduğunda. Bununla birlikte, tüm satırlar dikkate alındığında sadece% 1 maliyetin aşağıdaki adıma kaydırıldığı ve her iki şekilde de sadece bir satırın SQL Server'dan geri geldiği görülüyor, bu yüzden belki de şeylerin büyük şemasında büyük bir fark yok .


6

Geç kaldım, ama aynı endişem vardı ...

Kodunuzu orijinal gönderiden yeniden sildiğinizde, S kümesinin maks.

(From y In context.MyTable _
 Where y.MyField = value _
 Select y.MyCounter)

Son yorumunuzu dikkate alarak

Aralarından seçim yapabileceğiniz hiçbir kayıt olmadığında 0'ı istediğimi biliyorum, ki bu da nihai çözüm üzerinde kesinlikle bir etkiye sahip

Sorununuzu şu şekilde yeniden ifade edebilirim: Maksimum {0 + S} istiyorsunuz. Ve concat ile önerilen çözüm semantik doğru olanı gibi görünüyor :-)

var max = new[]{0}
          .Concat((From y In context.MyTable _
                   Where y.MyField = value _
                   Select y.MyCounter))
          .Max();

3

Neden daha doğrudan bir şey değil:

Dim x = context.MyTable.Max(Function(DataItem) DataItem.MyField = Value)

1

Dikkat çekilmesi gereken ilginç bir fark, FirstOrDefault ve Take (1) aynı SQL'i oluştururken (yine de LINQPad'e göre), FirstOrDefault'un eşleşen bir satır olmadığında ve Take (1) döndürdüğünde bir değer döndürmesi sonuç yok ... en azından LINQPad'de.


1

Sadece orada herkesin Linq to Entities kullandığını bilmesini sağlamak için yukarıdaki yöntemler işe yaramaz ...

Eğer böyle bir şey yapmaya çalışırsanız

var max = new[]{0}
      .Concat((From y In context.MyTable _
               Where y.MyField = value _
               Select y.MyCounter))
      .Max();

Bir istisna atar:

System.NotSupportedException: 'NewArrayInit' LINQ ifade düğümü türü LINQ to Entities'de desteklenmiyor.

Sadece yapmayı öneririm

(From y In context.MyTable _
                   Where y.MyField = value _
                   Select y.MyCounter))
          .OrderByDescending(x=>x).FirstOrDefault());

Ve FirstOrDefaultlisteniz boşsa 0 döndürecektir.


Sıralama, büyük veri kümelerinde performansın ciddi şekilde düşmesine neden olabilir. Maksimum değer bulmanın verimsiz bir yoludur.
Peter Bruins

1
decimal Max = (decimal?)(context.MyTable.Select(e => e.MyCounter).Max()) ?? 0;

1

Bir MaxOrDefaultuzatma yöntemi çaldım . Çok fazla bir şey yok, ancak Intellisense'deki varlığı, Maxboş bir dizide istisnaya neden olacağına dair faydalı bir hatırlatmadır . Ayrıca, yöntem gerekirse varsayılanın belirtilmesine izin verir.

    public static TResult MaxOrDefault<TSource, TResult>(this 
    IQueryable<TSource> source, Expression<Func<TSource, TResult?>> selector,
    TResult defaultValue = default (TResult)) where TResult : struct
    {
        return source.Max(selector) ?? defaultValue;
    }

0

Entity Framework ve Linq to SQL için bunu, yönteme Expressiongeçirilen bir modifiye eden bir uzantı yöntemi tanımlayarak başarabiliriz IQueryable<T>.Max(...):

static class Extensions
{
    public static TResult MaxOrDefault<T, TResult>(this IQueryable<T> source, 
                                                   Expression<Func<T, TResult>> selector)
        where TResult : struct
    {
        UnaryExpression castedBody = Expression.Convert(selector.Body, typeof(TResult?));
        Expression<Func<T, TResult?>> lambda = Expression.Lambda<Func<T,TResult?>>(castedBody, selector.Parameters);
        return source.Max(lambda) ?? default(TResult);
    }
}

Kullanımı:

int maxId = dbContextInstance.Employees.MaxOrDefault(employee => employee.Id);
// maxId is equal to 0 if there is no records in Employees table

Oluşturulan sorgu aynıdır, tıpkı IQueryable<T>.Max(...)yönteme normal bir çağrı gibi çalışır , ancak kayıt yoksa bir istisna atmak yerine T türünün varsayılan değerini döndürür


-1

Benzer bir sorun yaşadım, birim testlerim Max () kullanarak geçti ancak canlı bir veritabanına karşı çalıştırıldığında başarısız oldu.

Benim çözüm, sorgu tek bir sorguda katılmak değil, gerçekleştirilen mantık ayırmak oldu.
Canlı bir ortamda yürütürken Linq-objects (Linq-objects Max () nulls ile çalışır) ve Linq-sql kullanarak birim testleri çalışmak için bir çözüm gerekiyordu.

(Testlerimde Select () ile alay ediyorum)

var requiredDataQuery = _dataRepo.Select(x => new { x.NullableDate1, .NullableDate2 }); 
var requiredData.ToList();
var maxDate1 = dates.Max(x => x.NullableDate1);
var maxDate2 = dates.Max(x => x.NullableDate2);

Daha az verimli mi? Muhtemelen.

Uygulamam bir dahaki sefere düşmediği sürece umurumda mı? Hayır!

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.