Как рассчитать строки в EntityFramework без загрузки содержимого? - PullRequest
96 голосов
/ 21 мая 2009

Я пытаюсь определить, как считать соответствующих строк в таблице, используя EntityFramework.

Проблема в том, что в каждой строке может быть много мегабайт данных (в двоичном поле). Конечно, SQL будет выглядеть примерно так:

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

Я мог бы загрузить все строки и , затем найти Count с помощью:

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

Но это крайне неэффективно. Есть ли более простой способ?


РЕДАКТИРОВАТЬ: Спасибо, все. Я переместил БД из частного приложения, чтобы я мог запустить профилирование; это помогает, но вызывает замешательство, которого я не ожидал.

И мои реальные данные немного глубже, я буду использовать Грузовые автомобили , перевозящие Паллеты из Ящики из Элементы - и Я не хочу, чтобы Грузовик ушел, если в нем нет хотя бы одного Предмета .

Мои попытки показаны ниже. Я не понимаю, что CASE_2 никогда не обращается к серверу БД (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

И SQL, полученный из CASE_1, передается через sp_executesql , но:

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

[ У меня действительно нет Грузовиков, Водителей, Поддонов, Случаев или Предметов; как вы можете видеть из SQL, отношения «грузовик-поддон» и «поддон-ящик» имеют много ко многим, хотя я не думаю, что это имеет значение. Мои настоящие объекты нематериальные и их сложнее описать, поэтому я изменил названия. ]

Ответы [ 7 ]

109 голосов
/ 21 мая 2009

Синтаксис запроса:

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

Синтаксис метода:

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

Оба генерируют один и тот же запрос SQL.

43 голосов
/ 21 мая 2009

Я думаю, вы хотите что-то вроде

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

(отредактировано для отражения комментариев)

14 голосов
/ 23 января 2014

Насколько я понимаю, выбранный ответ по-прежнему загружает все связанные тесты. Согласно этому блогу msdn, есть лучший способ.

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

В частности

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();
}
9 голосов
/ 24 июня 2016

Это мой код:

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

Убедитесь, что переменная определена как IQueryable, тогда, когда вы используете метод Count (), EF выполнит что-то вроде

select count(*) from ...

В противном случае, если записи определены как IEnumerable, сгенерированный sql запросит всю таблицу и вернет количество строк.

9 голосов
/ 21 мая 2009

Ну, даже SELECT COUNT(*) FROM Table будет довольно неэффективным, особенно для больших таблиц, поскольку SQL Server действительно ничего не может сделать, кроме как выполнить полное сканирование таблицы (сканирование кластерного индекса).

Иногда достаточно знать приблизительное количество строк в базе данных, и в таком случае может быть достаточно такого утверждения:

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

Это будет проверять динамическое административное представление и извлекать из него количество строк и размер таблицы с учетом конкретной таблицы. Это достигается путем суммирования записей для кучи (index_id = 0) или кластеризованного индекса (index_id = 1).

Это быстро, просто в использовании, но оно не гарантирует 100% точности или актуальности. Но во многих случаях это «достаточно хорошо» (и значительно снижает нагрузку на сервер).

Может быть, это сработало бы и для вас? Конечно, чтобы использовать его в EF, вам придется заключить его в хранимый процесс или использовать прямой вызов «Выполнить запрос SQL».

Марк

3 голосов
/ 03 мая 2012

Используйте метод ExecuteStoreQuery контекста сущности. Это позволяет избежать загрузки всего набора результатов и десериализации в объекты для простого подсчета строк.

   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>();
    }
2 голосов
/ 21 мая 2009

Я думаю, что это должно работать ...

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();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...