Я пытаюсь определить, как подсчитать совпадающие строки в таблице с помощью 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();
Но это крайне неэффективно. Есть способ попроще?
РЕДАКТИРОВАТЬ: Спасибо всем. Я переместил БД из частного прикрепленного, чтобы я мог запустить профилирование; это помогает, но вызывает недоумение, которого я не ожидал.
И мои реальные данные немного глубже, я буду использовать Грузовики проведения Поддонов из дел по пунктам - и я не хочу, чтобы грузовик , чтобы покинуть страну , если не существует , по крайней мере один Пункт в нем.
Мои попытки показаны ниже. Я не понимаю, что 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, отношения «грузовик-поддон» и «поддон-ящик» являются отношениями «многие-ко-многим», хотя я не думаю, что это имеет значение. Мои настоящие объекты нематериальны, и их сложнее описать, поэтому я изменил имена. ]
источник
Ответы:
Синтаксис запроса:
Синтаксис метода:
Оба генерируют один и тот же SQL-запрос.
источник
SelectMany()
? Это нужно? Разве без него он не работал бы нормально?MyContainer.Where(o => o.ID == '1')
)Я думаю ты хочешь что-то вроде
(отредактировано, чтобы отразить комментарии)
источник
var count = context.MyTable.Count(t => t.MyContainer.ID == '1');
недолго и некрасиво:var count = (from o in context.MyContainer where o.ID == '1' from t in o.MyTable select t).Count();
Но это зависит от стиля кодирования ...Насколько я понимаю, выбранный ответ по-прежнему загружает все связанные тесты. Согласно этому блогу msdn, есть способ лучше.
http://blogs.msdn.com/b/adonet/archive/2011/01/31/using-dbcontext-in-ef-feature-ctp5-part-6-loading-related-entities.aspx
В частности
источник
Find(1)
запрос. Просто создайте объект и прикрепите к контексту:var princess = new PrincessEntity{ Id = 1 }; context.Princesses.Attach(princess);
Это мой код:
Убедитесь, что переменная определена как IQueryable, тогда при использовании метода Count () EF выполнит что-то вроде
В противном случае, если записи определены как IEnumerable, созданный sql запросит всю таблицу и подсчитает возвращенные строки.
источник
Что ж, даже это
SELECT COUNT(*) FROM Table
будет довольно неэффективно, особенно для больших таблиц, поскольку SQL Server действительно ничего не может сделать, кроме как выполнить полное сканирование таблицы (сканирование кластерного индекса).Иногда достаточно знать приблизительное количество строк из базы данных, и в таком случае может хватить такой инструкции:
Это проверит динамическое представление управления и извлечет из него количество строк и размер таблицы для конкретной таблицы. Это делается путем суммирования записей для кучи (index_id = 0) или кластерного индекса (index_id = 1).
Это быстро, просто в использовании, но не гарантирует 100% точности и актуальности. Но во многих случаях это «достаточно хорошо» (и значительно снижает нагрузку на сервер).
Может быть, это сработает и для вас? Конечно, чтобы использовать его в EF, вам придется заключить это в сохраненную процедуру или использовать прямой вызов «Выполнить SQL-запрос».
Марк
источник
Используйте метод ExecuteStoreQuery контекста сущности. Это позволяет избежать загрузки всего набора результатов и десериализации в объекты для простого подсчета строк.
источник
int count = context.MyTable.Count(m => m.MyContainerID == '1')
сгенерированный SQL будет в точности напоминать то, что вы делаете, но код будет намного лучше. Никакие сущности не загружаются в память как таковые. Попробуйте, если хотите, в LINQPad - он покажет вам SQL, который используется под крышками.Я думаю, это должно сработать ...
источник