Làm cách nào để COUNT hàng trong EntityFramework mà không cần tải nội dung?


109

Tôi đang cố gắng xác định cách đếm các hàng phù hợp trên bảng bằng EntityFramework.

Vấn đề là mỗi hàng có thể có nhiều megabyte dữ liệu (trong trường Nhị phân). Tất nhiên SQL sẽ giống như sau:

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

Tôi có thể tải tất cả các hàng và sau đó tìm Số đếm với:

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

Nhưng điều đó hoàn toàn không hiệu quả. đó có phải là cách dễ hơn?


EDIT: Cảm ơn, tất cả. Tôi đã chuyển DB từ một tệp đính kèm riêng tư để tôi có thể chạy hồ sơ; điều này giúp ích nhưng gây ra sự nhầm lẫn mà tôi không mong đợi.

Và dữ liệu thật của tôi là một chút sâu hơn, tôi sẽ sử dụng xe tải chở pallet của trường hợp của Items - và tôi không muốn các xe tải rời khỏi trừ khi có ít nhất một mục trong đó.

Những nỗ lực của tôi được hiển thị bên dưới. Phần tôi không hiểu là CASE_2 không bao giờ truy cập vào máy chủ 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

Và SQL kết quả từ CASE_1 được chuyển qua sp_executesql , nhưng:

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

[ Tôi thực sự không có Xe tải, Lái xe, Pallet, Thùng hoặc Vật dụng; như bạn có thể thấy từ SQL mối quan hệ Xe tải-Pallet và Pallet-Case là nhiều-nhiều - mặc dù tôi không nghĩ điều đó quan trọng. Các đối tượng thực của tôi vô hình và khó mô tả hơn, vì vậy tôi đã đổi tên. ]


1
làm thế nào bạn giải quyết vấn đề tải pallet?
Sherlock

Câu trả lời:


123

Cú pháp truy vấn:

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

Cú pháp phương pháp:

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

Cả hai đều tạo ra cùng một truy vấn SQL.


Tại sao SelectMany()? Nó có cần thiết không? Nó sẽ không hoạt động bình thường nếu không có nó?
Jo Smo

@JoSmo, không, đó là một truy vấn hoàn toàn khác.
Craig Stuntz

Cảm ơn bạn đã giải thích rõ ràng cho tôi. Chỉ muốn chắc chắn. :)
Jo Smo.

1
Bạn có thể cho tôi biết tại sao nó khác với SelectMany không? Tôi không hiểu. Tôi làm điều đó mà không có SelectMany nhưng nó thực sự chậm vì tôi có hơn 20 triệu bản ghi. Tôi đã thử câu trả lời từ Yang Zhang và hoạt động rất tốt, chỉ muốn biết SelectMany làm gì.
mikesoft

1
@AustinFelipe Nếu không có lệnh gọi đến SelectMany, truy vấn sẽ trả về số hàng trong MyContainer với ID bằng '1'. Cuộc gọi SelectMany trả về tất cả các hàng trong MyTable thuộc về kết quả trước đó của truy vấn (có nghĩa là kết quả của MyContainer.Where(o => o.ID == '1'))
sbecker

48

Tôi nghĩ bạn muốn một cái gì đó như

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

(chỉnh sửa để phản ánh ý kiến)


1
Không, anh ta cần số lượng các thực thể trong MyTable được tham chiếu bởi một thực thể có ID = 1 trong MyContainer
Craig Stuntz

3
Ngẫu nhiên, nếu t.ID là PK, thì số trong mã trên sẽ luôn là 1. :)
Craig Stuntz

2
@Craig, bạn nói đúng, tôi nên sử dụng t.ForeignTable.ID. Đã cập nhật.
Kevin

1
Điều này thật ngắn gọn và đơn giản. Lựa chọn của tôi là: var count = context.MyTable.Count(t => t.MyContainer.ID == '1'); không dài và xấu: var count = (from o in context.MyContainer where o.ID == '1' from t in o.MyTable select t).Count(); Nhưng nó phụ thuộc vào phong cách mã hóa ...
CL

đảm bảo rằng bạn bao gồm "using System.Linq", nếu không điều này sẽ không hoạt động
CountMurphy

16

Theo tôi hiểu, câu trả lời đã chọn vẫn tải tất cả các bài kiểm tra liên quan. Theo blog msdn này, có một cách tốt hơn.

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

Đặc biệt

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

3
Không cần phải Find(1)yêu cầu thêm . Chỉ cần tạo thực thể và đính kèm vào ngữ cảnh:var princess = new PrincessEntity{ Id = 1 }; context.Princesses.Attach(princess);
tenbits

13

Đây là mã của tôi:

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

Đảm bảo rằng biến được định nghĩa là IQueryable thì khi bạn sử dụng phương thức Count (), EF sẽ thực thi một cái gì đó như

select count(*) from ...

Ngược lại, nếu các bản ghi được xác định là IEnumerable, thì sql được tạo sẽ truy vấn toàn bộ bảng và đếm các hàng được trả về.


10

Chà, thậm chí SELECT COUNT(*) FROM Tablesẽ khá kém hiệu quả, đặc biệt là trên các bảng lớn, vì SQL Server thực sự không thể làm gì khác ngoài việc quét toàn bộ bảng (quét chỉ mục theo cụm).

Đôi khi, đủ tốt để biết số hàng gần đúng từ cơ sở dữ liệu và trong trường hợp như vậy, một câu lệnh như thế này có thể đủ:

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

Thao tác này sẽ kiểm tra chế độ xem quản lý động và trích xuất số hàng và kích thước bảng từ đó, cho một bảng cụ thể. Nó làm như vậy bằng cách tổng hợp các mục nhập cho heap (index_id = 0) hoặc chỉ mục nhóm (index_id = 1).

Nó nhanh chóng, dễ sử dụng, nhưng nó không được đảm bảo là chính xác 100% hoặc cập nhật. Nhưng trong nhiều trường hợp, điều này là "đủ tốt" (và giảm bớt gánh nặng cho máy chủ).

Có lẽ điều đó cũng sẽ làm việc cho bạn? Tất nhiên, để sử dụng nó trong EF, bạn phải gói nó trong một proc được lưu trữ hoặc sử dụng lệnh gọi "Thực thi truy vấn SQL".

Marc


1
Nó sẽ không phải là một bảng quét đầy đủ do tham chiếu FK trong WHERE. Chỉ các chi tiết của tổng thể sẽ được quét. Vấn đề hiệu suất mà anh ấy gặp phải là do tải dữ liệu blob, không phải số lượng bản ghi. Giả sử thường không có hàng chục nghìn nghìn bản ghi chi tiết trên mỗi bản ghi chính, tôi sẽ không "tối ưu hóa" thứ gì đó không thực sự chậm.
Craig Stuntz

OK, vâng, trong trường hợp đó, bạn sẽ chỉ chọn một tập hợp con - điều đó sẽ ổn thôi. Đối với dữ liệu blob - tôi có ấn tượng rằng bạn có thể đặt "tải chậm" trên bất kỳ cột nào trong bất kỳ bảng EF nào của bạn để tránh tải nó, vì vậy điều đó có thể hữu ích.
marc_s

Có cách nào để sử dụng SQL này với EntityFramework không? Dù sao, trong trường hợp này, tôi chỉ cần biết có các hàng phù hợp, nhưng tôi cố ý hỏi câu hỏi một cách tổng quát hơn.
NVRAM

4

Sử dụng phương thức ExecuteStoreQuery của ngữ cảnh thực thể. Điều này tránh tải xuống toàn bộ tập kết quả và giải kích thước thành các đối tượng để thực hiện đếm hàng đơn giản.

   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
Nếu bạn viết int count = context.MyTable.Count(m => m.MyContainerID == '1')thì SQL được tạo sẽ giống chính xác những gì bạn đang làm, nhưng mã đẹp hơn nhiều. Không có thực thể nào được tải vào bộ nhớ như vậy. Hãy dùng thử trong LINQPad nếu bạn thích - nó sẽ hiển thị cho bạn SQL được sử dụng dưới các trang bìa.
Drew Noakes

SQL nội dòng. . không phải điều tôi yêu thích.
Duanne

3

Tôi nghĩ rằng điều này sẽ làm việc...

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

Đây cũng là hướng tôi đi lúc đầu, nhưng tôi hiểu rằng trừ khi bạn đã thêm nó theo cách thủ công, m sẽ có thuộc tính MyContainer nhưng không có MyContainerId. Do đó, những gì bạn muốn kiểm tra là m.MyContainer.ID.
Kevin

Nếu MyContainer là cha mẹ và MyTable là con trong mối quan hệ thì bạn phải thiết lập mối quan hệ đó với một số khóa ngoại, tôi không chắc bằng cách nào khác bạn sẽ biết thực thể MyTable nào được liên kết với thực thể MyContainer ... Nhưng có lẽ tôi đưa ra một giả định về cấu trúc ...
bytebender
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.