Как указать строки в EntityFramework без загрузки содержимого?

Я пытаюсь определить, как подсчитать соответствующие строки в таблице, используя 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();

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


EDIT: Спасибо, все. Я переместил БД из частного приложения, чтобы запустить профилирование; это помогает, но вызывает путаницы, которых я не ожидал.

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

Мои попытки показаны ниже. Часть, которую я не получаю, это то, что 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, отношения между Truck-Pallet и Pallet-Case много-ко-многим, хотя я не думаю, что это имеет значение. Мои реальные объекты являются неосязаемыми и сложнее описать, поэтому я изменил имена.]

Ответ 1

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

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-запрос.

Ответ 2

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

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

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

Ответ 3

Как я понимаю, выбранный ответ все еще загружает все связанные тесты. Согласно этому блогу 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();
}

Ответ 4

Ну, даже 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-запрос".

Марк

Ответ 5

Это мой код:

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

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

select count(*) from ...

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

Ответ 6

Используйте метод 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>();
    }

Ответ 7

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

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