Как указать строки в 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();