2009-05-20 2 views
83

Я пытаюсь определить, как Соответствующие строки на столе, используя EntityFramework.Как указать строки внутри EntityFramework без загрузки содержимого?

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

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

я мог загрузить все строки и затем найти граф с:

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

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


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

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

Мои попытки показаны ниже. Часть, которую я не получаю, - это то, что 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

Как вы решили проблему загрузки поддона? – Sherlock

ответ

98

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

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

+0

Почему 'SelectMany()'? Это необходимо? Не будет ли это работать без него? –

+0

@JoSmo, нет, это совершенно другой запрос. –

+0

Благодарим вас за это. Просто хотел быть уверенным. :) –

37

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

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

(отредактирован, чтобы отразить комментарии)

+1

Я считаю, что ему нужен счет MyTable, а не MyContainer ... – bytebender

+0

Нет, ему нужен счет объектов в MyTable, на который ссылается одна сущность с ID = 1 в MyContainer –

+3

Кстати, если t.ID является PK, то счет в коде выше будет * всегда * быть 1. :) –

2

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

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

Это направление, в котором я тоже пошел, но я понимаю, что если вы не добавили его вручную, m будет иметь свойство MyContainer, но MyContainerId не будет. Следовательно, то, что вы хотите изучить, - m.MyContainer.ID. – Kevin

+0

Если MyContainer является родительским, а MyTable - дочерними элементами отношений, вам нужно было установить эти отношения с каким-то внешним ключом, я не уверен, как еще вы узнаете, какие объекты MyTable, связанные с объектом MyContainer ... Но, возможно, Я сделал предположение о структуре ... – bytebender

8

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

Marc

+1

Это не будет полная таблица сканирование из-за ссылки FK в ГДЕ. Будут сканироваться только данные мастера. Проблема производительности, с которой он столкнулся, заключалась в загрузке данных blob, а не в счетчике записи. Предполагая, что обычно нет десятков тысяч + подробных записей на главную запись, я бы не «оптимизировал» что-то, что на самом деле не медленное. –

+0

ОК, да, в этом случае вы выберете только подмножество - это должно быть хорошо. Что касается данных blob - у меня создалось впечатление, что вы можете установить «отложенную загрузку» в любом столбце любой из ваших таблиц EF, чтобы избежать ее загрузки, чтобы это могло помочь. –

+0

Есть ли способ использовать этот SQL с EntityFramework? Во всяком случае, в этом случае мне нужно было знать, что есть соответствующие строки, но я намеренно задал вопрос в более общем плане. – NVRAM

3

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

Если вы пишете 'int count = context.MyTable.Count (m => m.MyContainerID == '1')', тогда сгенерированный SQL будет похож на то, что вы делаете, но код намного приятнее. Никакие объекты не загружаются в память как таковые. Попробуйте в LINQPad, если хотите - он покажет вам SQL, используемый под обложками. –

+0

Встроенный SQL. , не моя любимая вещь. – Duanne

14

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

Нет необходимости делать запрос 'Найти (1)'. Просто создайте объект и присоедините его к контексту: 'var princess = new PrincessEntity {Id = 1}; context.Princesses.Attach (принцессы); ' – tenbits

8

Это мой код:

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

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

select count(*) from ... 

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

Смежные вопросы