Comment compter les lignes dans EntityFramework sans charger le contenu?
J'essaie de déterminer comment compte les lignes correspondantes sur une table en utilisant EntityFramework.
Le problème est que chaque ligne peut avoir plusieurs mégaoctets de données (dans un champ Binaire). Bien sûr, le SQL serait quelque chose comme ceci:
SELECT COUNT(*) FROM [MyTable] WHERE [fkID] = '1';
J'ai pu charger toutes les lignes et , puis trouver le compte avec:
var owner = context.MyContainer.Where(t => t.ID == '1');
owner.MyTable.Load();
var count = owner.MyTable.Count();
Mais c'est grossièrement inefficace. Est-il un moyen plus simple?
EDIT: merci à tous. J'ai déplacé le DB à partir d'un privé attaché afin que je puisse exécuter le profilage; cela aide mais provoque des confusions auxquelles je ne m'attendais pas.
Et mes données réelles sont un peu plus profondes, j'utiliserai camionstransportant palettesde caissesde articles -- et je ne veux pas que le camionparte à moins qu'il n'y ait au moins un Article dedans.
Mes tentatives sont indiquées ci-dessous. La partie que je ne reçois pas est que CASE_2 n'accède jamais au serveur DB (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
Et le SQL résultant de CASE_1 est acheminée par des sp_executesql, mais
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
[Je n'ai pas vraiment de camions, de chauffeurs, de palettes, de caisses ou D'articles; comme vous pouvez le voir dans le SQL, les relations camion-palette et palette-caisse sont nombreuses-bien que je ne pense pas que cela compte. Mes objets réels sont intangibles et plus difficiles à décrire, alors j'ai changé les noms.]
7 réponses
Syntaxe de requête:
var count = (from o in context.MyContainer
where o.ID == '1'
from t in o.MyTable
select t).Count();
Syntaxe de la méthode:
var count = context.MyContainer
.Where(o => o.ID == '1')
.SelectMany(o => o.MyTable)
.Count()
Les deux génèrent la même requête SQL.
Je pense que vous voulez quelque chose comme
var count = context.MyTable.Count(t => t.MyContainer.ID == '1');
(modifié pour refléter les commentaires)
Si je comprends bien, la réponse sélectionnée charge toujours tous les tests connexes. Selon ce blog msdn, il y a un meilleur moyen.
Spécifiquement
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();
}
C'est mon code:
IQueryable<AuctionRecord> records = db.AuctionRecord;
var count = records.Count();
Assurez-vous que la variable est définie comme IQueryable puis lorsque vous utilisez la méthode Count (), EF exécutera quelque chose comme
select count(*) from ...
Sinon, si les enregistrements sont définis comme IEnumerable, le sql généré interrogera la table entière et comptera les lignes renvoyées.
Eh bien, même le {[1] } sera assez inefficace, en particulier sur les grandes tables, puisque SQL Server ne peut vraiment rien faire d'autre que faire une analyse complète de la table (analyse d'index en cluster).
Parfois, il est assez bon de connaître un nombre approximatif de lignes de la base de données, et dans un tel cas, une déclaration comme celle-ci pourrait suffire:
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
Cela va inspecter la vue de gestion dynamique et extraire le nombre de lignes et la taille de la table, en fonction d'une table spécifique. Il le fait en sommant les inscriptions pour le tas (index_id = 0) ou l'index cluster (index_id = 1).
C'est rapide, c'est facile à utiliser, mais il n'est pas garanti d'être 100% précis ou à jour. Mais dans de nombreux cas, c'est" assez bon " (et mettre beaucoup moins de fardeau sur le serveur).
Peut-être que ça marcherait pour toi aussi? Bien sûr, pour l'utiliser dans EF, vous devez l'envelopper dans un proc stocké ou utiliser un appel "Execute SQL query".
Marc
Utilisez le ExecuteStoreQuery méthode du contexte d'entité. Cela évite de télécharger l'ensemble des résultats et de les désérialiser en objets pour effectuer un simple comptage de lignes.
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>();
}
Je pense que cela devrait fonctionner...
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();