İçerikler yüklenmeden EntityFramework içinde COUNT satır nasıl oluşturulur?


109

EntityFramework kullanarak bir tablodaki eşleşen satırların nasıl sayılacağını belirlemeye çalışıyorum .

Sorun, her satırda çok sayıda megabayt veri bulunmasıdır (İkili alanda). Elbette SQL şöyle bir şey olurdu:

SELECT COUNT(*) FROM [MyTable] WHERE [fkID] = '1';

Tüm satırları yükleyebilir ve ardından Sayımı şu şekilde bulabilirim:

var owner = context.MyContainer.Where(t => t.ID == '1');
owner.MyTable.Load();
var count = owner.MyTable.Count();

Ancak bu büyük ölçüde verimsizdir. Daha basit bir yol var mı?


DÜZENLEME: Herkese teşekkürler. DB'yi ekli bir özelden taşıdım, böylece profil oluşturmayı çalıştırabilirim; bu yardımcı olur ama beklemediğim kafa karışıklıklarına neden olur.

Ve benim gerçek veri, ben kullanacağız biraz daha derin Kamyon taşıyan Paletler ait Cases ait Öğeler ve ben istemiyorum - Kamyon en az bir olmadıkça bırakmak Öğe içinde.

Girişimlerim aşağıda gösterilmiştir. Anlamadığım kısım, CASE_2'nin asla DB sunucusuna (MSSQL) erişmemesi.

var truck = context.Truck.FirstOrDefault(t => (t.ID == truckID));
if (truck == null)
    return "Invalid Truck ID: " + truckID;
var dlist = from t in ve.Truck
    where t.ID == truckID
    select t.Driver;
if (dlist.Count() == 0)
    return "No Driver for this Truck";

var plist = from t in ve.Truck where t.ID == truckID
    from r in t.Pallet select r;
if (plist.Count() == 0)
    return "No Pallets are in this Truck";
#if CASE_1
/// This works fine (using 'plist'):
var list1 = from r in plist
    from c in r.Case
    from i in c.Item
    select i;
if (list1.Count() == 0)
    return "No Items are in the Truck";
#endif

#if CASE_2
/// This never executes any SQL on the server.
var list2 = from r in truck.Pallet
        from c in r.Case
        from i in c.Item
        select i;
bool ok = (list.Count() > 0);
if (!ok)
    return "No Items are in the Truck";
#endif

#if CASE_3
/// Forced loading also works, as stated in the OP...
bool ok = false;
foreach (var pallet in truck.Pallet) {
    pallet.Case.Load();
    foreach (var kase in pallet.Case) {
        kase.Item.Load();
        var item = kase.Item.FirstOrDefault();
        if (item != null) {
            ok = true;
            break;
        }
    }
    if (ok) break;
}
if (!ok)
    return "No Items are in the Truck";
#endif

Ve CASE_1 kaynaklı SQL sp_executesql aracılığıyla aktarılır , ancak:

SELECT [Project1].[C1] AS [C1]
FROM   ( SELECT cast(1 as bit) AS X ) AS [SingleRowTable1]
LEFT OUTER JOIN  (SELECT 
    [GroupBy1].[A1] AS [C1]
    FROM ( SELECT 
        COUNT(cast(1 as bit)) AS [A1]
        FROM   [dbo].[PalletTruckMap] AS [Extent1]
        INNER JOIN [dbo].[PalletCaseMap] AS [Extent2] ON [Extent1].[PalletID] = [Extent2].[PalletID]
        INNER JOIN [dbo].[Item] AS [Extent3] ON [Extent2].[CaseID] = [Extent3].[CaseID]
        WHERE [Extent1].[TruckID] = '....'
    )  AS [GroupBy1] ) AS [Project1] ON 1 = 1

[ Kamyonlarım, Sürücülerim, Paletlerim, Kılıflarım veya Eşyalarım yok; SQL'den de görebileceğiniz gibi, Kamyon-Palet ve Palet-Kasa ilişkileri çoka çoktur - bunun önemli olduğunu düşünmüyorum. Gerçek nesnelerim soyuttur ve tanımlanması daha zordur, bu yüzden isimleri değiştirdim. ]


1
palet yükleme problemini nasıl çözdünüz?
Sherlock

Yanıtlar:


123

Sorgu sözdizimi:

var count = (from o in context.MyContainer
             where o.ID == '1'
             from t in o.MyTable
             select t).Count();

Yöntem sözdizimi:

var count = context.MyContainer
            .Where(o => o.ID == '1')
            .SelectMany(o => o.MyTable)
            .Count()

Her ikisi de aynı SQL sorgusunu oluşturur.


Neden SelectMany()? Gerekli mi? Onsuz düzgün çalışmaz mı?
Jo Smo

@JoSmo, hayır, bu tamamen farklı bir sorgu.
Craig Stuntz

Bunu benim için açıkladığın için teşekkür ederim. Emin olmak istedim. :)
Jo Smo

1
Bana neden SelectMany ile farklı olduğunu söyleyebilir misiniz? Ben anlamadım Bunu SelectMany olmadan yapıyorum ama gerçekten yavaşlıyor çünkü 20 milyondan fazla kaydım var. Yang Zhang'ın yanıtını denedim ve harika çalışıyor, sadece SelectMany'nin ne yaptığını öğrenmek istedim.
mikesoft

1
@AustinFelipe SelectMany çağrısı olmadan sorgu, MyContainer'daki satır sayısını '1'e eşit kimliğe sahip olarak döndürür. SelectMany çağrısı (sonucu anlam sorgunun önceki sonuca ait MyTable tüm satırları döndürür MyContainer.Where(o => o.ID == '1'))
sbecker

49

Sanırım bir şey istiyorsun

var count = context.MyTable.Count(t => t.MyContainer.ID == '1');

(yorumları yansıtmak için düzenlendi)


1
Hayır, MyContainer'da ID = 1 olan tek varlık tarafından referans verilen MyTable'daki varlıkların sayısına ihtiyacı var
Craig Stuntz

3
Bu arada, eğer t.ID bir PK ise, yukarıdaki koddaki sayı her zaman 1 olacaktır. :)
Craig Stuntz

2
@Craig, haklısın, t.ForeignTable.ID kullanmalıydım. Güncellenmiş.
Kevin

1
Bu kısa ve basit. Benim seçimim: var count = context.MyTable.Count(t => t.MyContainer.ID == '1'); uzun ve çirkin değil: var count = (from o in context.MyContainer where o.ID == '1' from t in o.MyTable select t).Count(); Ama kodlama tarzına bağlı ...
CL

"using System.Linq"
ifadesini eklediğinizden

17

Anladığım kadarıyla, seçilen cevap hala tüm ilgili testleri yüklüyor. Bu msdn bloguna göre daha iyi bir yol var.

http://blogs.msdn.com/b/adonet/archive/2011/01/31/using-dbcontext-in-ef-feature-ctp5-part-6-loading-related-entities.aspx

Özellikle

using (var context = new UnicornsContext())

    var princess = context.Princesses.Find(1);

    // Count how many unicorns the princess owns 
    var unicornHaul = context.Entry(princess)
                      .Collection(p => p.Unicorns)
                      .Query()
                      .Count();
}

4
Ekstra Find(1)talepte bulunmanıza gerek yoktur . Sadece varlığı oluşturun ve bağlama ekleyin:var princess = new PrincessEntity{ Id = 1 }; context.Princesses.Attach(princess);
onbits

13

Bu benim kodum:

IQueryable<AuctionRecord> records = db.AuctionRecord;
var count = records.Count();

Değişkenin IQueryable olarak tanımlandığından emin olun, ardından Count () yöntemini kullandığınızda, EF,

select count(*) from ...

Aksi takdirde, kayıtlar IEnumerable olarak tanımlanmışsa, oluşturulan sql tablonun tamamını sorgulayacak ve döndürülen satırları sayacaktır.


10

Hatta SELECT COUNT(*) FROM TableSQL Server tam bir tablo taraması yapmaktan başka bir şey yapamayacağından (kümelenmiş dizin taraması) gerçekten de büyük tablolarda oldukça verimsiz olacaktır.

Bazen, veritabanından yaklaşık satır sayısını bilmek yeterince iyidir ve böyle bir durumda, bunun gibi bir ifade yeterli olabilir:

SELECT 
    SUM(used_page_count) * 8 AS SizeKB,
    SUM(row_count) AS [RowCount], 
    OBJECT_NAME(OBJECT_ID) AS TableName
FROM 
    sys.dm_db_partition_stats
WHERE 
    OBJECT_ID = OBJECT_ID('YourTableNameHere')
    AND (index_id = 0 OR index_id = 1)
GROUP BY 
    OBJECT_ID

Bu, dinamik yönetim görünümünü inceleyecek ve belirli bir tablo verildiğinde satır sayısını ve tablo boyutunu ondan çıkaracaktır. Bunu, yığın (dizin_kimliği = 0) veya kümelenmiş dizin (dizin_kimliği = 1) için girişleri toplayarak yapar.

Hızlıdır, kullanımı kolaydır, ancak% 100 doğru veya güncel olduğu garanti edilmez. Ancak çoğu durumda, bu "yeterince iyidir" (ve sunucuya çok daha az yük getirir).

Belki bu senin için de işe yarar? Tabii ki, bunu EF'de kullanmak için, bunu depolanan bir işlemde tamamlamanız veya düz bir "SQL sorgusu yürütme" çağrısı kullanmanız gerekir.

Marc


1
WHERE'deki FK referansı nedeniyle tam bir tablo taraması olmayacaktır. Sadece master'ın detayları taranacaktır. Yaşadığı performans sorunu, kayıt sayımı değil, blob verilerini yüklemekti. Ana kayıt başına tipik olarak onlarca bin + ayrıntı kaydı olmadığını varsayarsak, aslında yavaş olmayan bir şeyi "optimize etmem".
Craig Stuntz

Tamam, evet, bu durumda, yalnızca bir alt küme seçeceksiniz - bu iyi olmalı. Blob verilerine gelince - Ben, herhangi bir EF tablonuzdaki herhangi bir sütunda, yüklemeyi önlemek için "ertelenmiş yükleme" ayarlayabileceğiniz izlenimi altındaydım, bu yardımcı olabilir.
marc_s

Bu SQL'i EntityFramework ile kullanmanın bir yolu var mı? Her neyse, bu durumda sadece eşleşen satırlar olduğunu bilmem gerekiyordu, ancak soruyu bilinçli olarak daha genel olarak sordum.
NVRAM

4

Varlık bağlamının ExecuteStoreQuery yöntemini kullanın . Bu, tüm sonuç kümesinin indirilmesini ve basit bir satır sayımı yapmak için nesnelere dönüştürülmesini önler.

   int count;

    using (var db = new MyDatabase()){
      string sql = "SELECT COUNT(*) FROM MyTable where FkId = {0}";

      object[] myParams = {1};
      var cntQuery = db.ExecuteStoreQuery<int>(sql, myParams);

      count = cntQuery.First<int>();
    }

6
Eğer yazarsanız int count = context.MyTable.Count(m => m.MyContainerID == '1'), üretilen SQL tam olarak yaptığınız şeye benzeyecektir, ancak kod çok daha güzeldir. Belleğe hiçbir varlık bu şekilde yüklenmez. İsterseniz LINQPad'de deneyin - kapakların altında kullanılan SQL'i size gösterecektir.
Drew Noakes

Satır içi SQL. . en sevdiğim şey değil.
Duanne 02

3

Sanırım bu işe yaramalı ...

var query = from m in context.MyTable
            where m.MyContainerId == '1' // or what ever the foreign key name is...
            select m;

var count = query.Count();

İlk başta benim de gittiğim yön buydu, ancak anladığım kadarıyla, manuel olarak eklemediğiniz sürece, m bir MyContainer özelliğine sahip olacak ancak MyContainerId olmayacak. Dolayısıyla, incelemek istediğiniz şey m.MyContainer.ID'dir.
Kevin

Eğer MyContainer ebeveyn ise ve MyTable ilişkideki çocuklar ise, o zaman bir yabancı anahtarla bu ilişkiyi kurmanız gerekiyordu, bir MyContainer varlığıyla ilişkili olan MyTable varlıklarını başka nasıl bilebilirdiniz emin değilim ... Ama belki ben yapı hakkında bir varsayımda bulundu ...
bytebender
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.