J'essaie de déterminer comment compter les lignes correspondantes sur une table en utilisant EntityFramework.
Le problème est que chaque ligne peut contenir 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';
Je pourrais charger toutes les lignes, 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 tout à fait inefficace. Existe-t-il un moyen plus simple?
EDIT: Merci à tous. J'ai déplacé la base de données d'un attaché privé afin que je puisse exécuter le profilage; cela aide mais provoque des confusions auxquelles je ne m'attendais pas.
Et mes vraies données sont un peu plus profondes, j'utiliserai des camions transportant des palettes de caisses d' articles - et je ne veux pas que le camion parte à moins qu'il n'y ait au moins un article dedans.
Mes tentatives sont illustrées ci-dessous. La partie que je n'obtiens pas est que CASE_2 n'accède jamais au serveur de base de données (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é via 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 à partir du SQL, les relations camion-palette et palette-caisse sont plusieurs-à-plusieurs - 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. ]
la source
Réponses:
Syntaxe de la requête:
Syntaxe de la méthode:
Les deux génèrent la même requête SQL.
la source
SelectMany()
? Est-ce nécessaire? Cela ne fonctionnerait-il pas correctement sans cela?MyContainer.Where(o => o.ID == '1')
)Je pense que tu veux quelque chose comme
(modifié pour refléter les commentaires)
la source
var count = context.MyTable.Count(t => t.MyContainer.ID == '1');
pas long et moche:var count = (from o in context.MyContainer where o.ID == '1' from t in o.MyTable select t).Count();
mais cela dépend du style de codage ...Si je comprends bien, la réponse sélectionnée charge toujours tous les tests associés. Selon ce blog msdn, il existe un meilleur moyen.
http://blogs.msdn.com/b/adonet/archive/2011/01/31/using-dbcontext-in-ef-feature-ctp5-part-6-loading-related-entities.aspx
Plus précisément
la source
Find(1)
demande supplémentaire . Créez simplement l'entité et attachez-la au contexte:var princess = new PrincessEntity{ Id = 1 }; context.Princesses.Attach(princess);
Voici mon code:
Assurez-vous que la variable est définie comme IQueryable, puis lorsque vous utilisez la méthode Count (), EF exécutera quelque chose comme
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.
la source
Eh bien, même le
SELECT COUNT(*) FROM Table
sera assez inefficace, en particulier sur les grandes tables, car SQL Server ne peut vraiment rien faire d'autre que faire une analyse complète de la table (analyse d'index 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 instruction comme celle-ci peut suffire:
Cela inspectera la vue de gestion dynamique et en extraira le nombre de lignes et la taille de la table, étant donné une table spécifique. Il le fait en additionnant les entrées 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 précis ou à jour à 100%. Mais dans de nombreux cas, cela est "assez bon" (et met beaucoup moins de charge sur le serveur).
Peut-être que cela fonctionnerait pour vous aussi? Bien sûr, pour l'utiliser dans EF, vous devez encapsuler cela dans un processus stocké ou utiliser un simple appel "Execute SQL query".
Marc
la source
Utilisez la méthode ExecuteStoreQuery du contexte d'entité. Cela évite de télécharger tout le jeu de résultats et de désérialiser en objets pour effectuer un simple comptage de lignes.
la source
int count = context.MyTable.Count(m => m.MyContainerID == '1')
le SQL généré ressemblera exactement à ce que vous faites, mais le code est beaucoup plus agréable. Aucune entité n'est chargée en mémoire en tant que telle. Essayez-le dans LINQPad si vous le souhaitez - il vous montrera le SQL utilisé sous les couvertures.Je pense que cela devrait fonctionner ...
la source