Bagaimana cara MENGHITUNG baris dalam EntityFramework tanpa memuat konten?

109

Saya mencoba untuk menentukan bagaimana menghitung baris yang cocok pada tabel menggunakan EntityFramework.

Masalahnya adalah setiap baris mungkin memiliki banyak megabyte data (dalam bidang Biner). Tentu saja SQL akan menjadi seperti ini:

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

Saya dapat memuat semua baris dan kemudian menemukan Count dengan:

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

Tapi itu sangat tidak efisien. Apakah ada cara yang lebih sederhana?


EDIT: Terima kasih, semuanya. Saya telah memindahkan DB dari lampiran pribadi sehingga saya dapat menjalankan profil; ini membantu tetapi menyebabkan kebingungan yang tidak saya duga.

Dan data real saya sedikit lebih dalam, saya akan menggunakan Truk membawa Palet dari Kasus of Items - dan saya tidak ingin Truk meninggalkan kecuali ada setidaknya satu item di dalamnya.

Upaya saya ditunjukkan di bawah ini. Bagian yang saya tidak mengerti adalah bahwa CASE_2 tidak pernah mengakses server DB (MSSQL).

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

Dan SQL yang dihasilkan dari CASE_1 disalurkan melalui sp_executesql , tetapi:

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

[ Saya tidak benar-benar memiliki Truk, Driver, Palet, Casing, atau Item; seperti yang Anda lihat dari SQL, hubungan Truck-Pallet dan Pallet-Case adalah many-to-many - meskipun menurut saya itu tidak penting. Objek asli saya tidak berwujud dan lebih sulit untuk dijelaskan, jadi saya mengubah namanya. ]

NVRAM
sumber
1
bagaimana Anda mengatasi masalah pemuatan palet?
Sherlock

Jawaban:

123

Sintaks kueri:

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

Sintaks metode:

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

Keduanya menghasilkan kueri SQL yang sama.

Craig Stuntz
sumber
Mengapa SelectMany()? Apakah itu dibutuhkan? Bukankah itu akan bekerja dengan baik tanpanya?
Jo Smo
@JoSmo, tidak, itu pertanyaan yang sama sekali berbeda.
Craig Stuntz
Terima kasih telah membereskan hal itu untuk saya. Hanya ingin memastikan. :)
Jo Smo
1
Dapatkah Anda memberi tahu saya mengapa ini berbeda dengan SelectMany? Saya tidak mengerti. Saya melakukannya tanpa SelectMany tetapi menjadi sangat lambat karena saya memiliki lebih dari 20 juta rekaman. Saya mencoba jawaban dari Yang Zhang dan berhasil dengan baik, hanya ingin tahu apa yang dilakukan SelectMany.
mikesoft
1
@AustinFelipe Tanpa panggilan ke SelectMany, kueri akan mengembalikan jumlah baris di MyContainer dengan ID sama dengan '1'. Panggilan SelectMany mengembalikan semua baris di MyTable yang termasuk dalam hasil kueri sebelumnya (artinya hasil MyContainer.Where(o => o.ID == '1'))
sbecker
48

Saya pikir Anda menginginkan sesuatu seperti

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

(diedit untuk mencerminkan komentar)

Kevin
sumber
1
Tidak, dia memerlukan hitungan entitas di MyTable yang direferensikan oleh satu entitas dengan ID = 1 di MyContainer
Craig Stuntz
3
Kebetulan, jika t.ID adalah PK, maka hitungan kode di atas akan selalu 1. :)
Craig Stuntz
2
@Craig, Anda benar, saya seharusnya menggunakan t.ForeignTable.ID. Diperbarui.
Kevin
1
Ini singkat dan sederhana. Pilihan saya adalah: var count = context.MyTable.Count(t => t.MyContainer.ID == '1'); tidak lama dan jelek: var count = (from o in context.MyContainer where o.ID == '1' from t in o.MyTable select t).Count(); Tapi itu tergantung pada gaya pengkodean ...
CL
pastikan Anda menyertakan "using System.Linq", atau ini tidak akan berfungsi
CountMurphy
17

Seperti yang saya pahami, jawaban yang dipilih masih memuat semua tes terkait. Menurut blog msdn ini, ada cara yang lebih baik.

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

Secara khusus

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();
}
Quickhorn
sumber
4
Tidak perlu membuat Find(1)permintaan tambahan . Cukup buat entitas dan lampirkan ke konteks:var princess = new PrincessEntity{ Id = 1 }; context.Princesses.Attach(princess);
tenbits
13

Ini kode saya:

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

Pastikan variabel didefinisikan sebagai IQuerizable maka ketika Anda menggunakan metode Count (), EF akan mengeksekusi sesuatu seperti

select count(*) from ...

Jika tidak, jika record didefinisikan sebagai IEnumerable, sql yang dihasilkan akan menanyakan seluruh tabel dan menghitung baris yang dikembalikan.

Yang Zhang
sumber
10

Yah, bahkan SELECT COUNT(*) FROM Tableakan menjadi cukup tidak efisien, terutama pada tabel besar, karena SQL Server benar-benar tidak dapat melakukan apa pun selain melakukan pemindaian tabel lengkap (pemindaian indeks berkerumun).

Terkadang, cukup baik untuk mengetahui perkiraan jumlah baris dari database, dan dalam kasus seperti ini, pernyataan seperti ini mungkin cukup:

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

Ini akan memeriksa tampilan manajemen dinamis dan mengekstrak jumlah baris dan ukuran tabel darinya, berdasarkan tabel tertentu. Ia melakukannya dengan menjumlahkan entri untuk heap (index_id = 0) atau indeks berkerumun (index_id = 1).

Cepat, mudah digunakan, tetapi tidak dijamin 100% akurat atau mutakhir. Namun dalam banyak kasus, ini "cukup baik" (dan mengurangi beban server).

Mungkin itu akan berhasil untuk Anda juga? Tentu saja, untuk menggunakannya di EF, Anda harus membungkusnya dalam proc yang disimpan atau menggunakan panggilan langsung "Jalankan kueri SQL".

Marc

marc_s
sumber
1
Ini tidak akan menjadi pemindaian tabel lengkap karena referensi FK di WHERE. Hanya detail master yang akan dipindai. Masalah kinerja yang dia alami adalah dari memuat data gumpalan, bukan jumlah catatan. Dengan asumsi biasanya tidak ada puluhan ribu + data detail per catatan master, saya tidak akan "mengoptimalkan" sesuatu yang sebenarnya tidak lambat.
Craig Stuntz
Oke, ya, dalam hal ini, Anda hanya akan memilih subset - itu tidak masalah. Sedangkan untuk data blob - Saya mendapat kesan bahwa Anda bisa menetapkan "pemuatan yang ditangguhkan" pada kolom mana pun di tabel EF Anda untuk menghindari memuatnya, jadi itu mungkin membantu.
marc_s
Apakah ada cara untuk menggunakan SQL ini dengan EntityFramework? Bagaimanapun, dalam hal ini saya hanya perlu tahu ada baris yang cocok, tetapi saya sengaja mengajukan pertanyaan secara lebih umum.
NVRAM
4

Gunakan metode ExecuteStoreQuery dari konteks entitas. Ini menghindari mengunduh seluruh kumpulan hasil dan deserialisasi menjadi objek untuk melakukan penghitungan baris sederhana.

   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>();
    }
goosemanjack
sumber
6
Jika Anda menulis int count = context.MyTable.Count(m => m.MyContainerID == '1')maka SQL yang dihasilkan akan menyerupai apa yang Anda lakukan, tetapi kodenya jauh lebih bagus. Tidak ada entitas yang dimuat ke dalam memori seperti itu. Cobalah di LINQPad jika Anda suka - ini akan menunjukkan kepada Anda SQL yang digunakan di balik sampul.
Drew Noakes
SQL sebaris. . bukan hal favoritku.
Duanne
3

Saya pikir ini harus berhasil ...

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();
bytebender
sumber
Ini adalah arah yang saya tuju pada awalnya, juga, tetapi pemahaman saya bahwa kecuali Anda menambahkannya secara manual, m akan memiliki properti MyContainer tetapi tidak ada MyContainerId. Karenanya, yang ingin Anda periksa adalah m.MyContainer.ID.
Kevin
Jika MyContainer adalah orang tua dan MyTable adalah anak-anak dalam hubungan tersebut maka Anda harus membangun hubungan itu dengan beberapa kunci asing, saya tidak yakin bagaimana lagi Anda akan tahu entitas MyTable mana yang terkait dengan entitas MyContainer ... Tapi mungkin saya membuat asumsi tentang struktur ...
bytebender