103 votes

Comment COMPTER les lignes dans EntityFramework sans charger le contenu ?

J'essaie de déterminer comment compter les lignes correspondantes d'une table en utilisant l'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 rangées et puis trouver le comte 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'une pièce jointe privée pour pouvoir 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, je vais utiliser Camions portant Palettes de Cas de Articles -- et je ne veux pas que le Camion de partir à moins qu'il y ait au moins un Article en elle.

Mes tentatives sont présentées ci-dessous. La partie que je ne comprends 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é par le biais de 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 entre les camions et les palettes et entre les palettes et les caisses sont de plusieurs à plusieurs, bien que je ne pense pas que cela soit important. Mes vrais objets sont intangibles et plus difficiles à décrire, c'est pourquoi j'ai changé les noms. ]

115voto

Craig Stuntz Points 95965
var count = (from o in context.MyContainer
             where o.ID == '1'
             from t in o.MyTable
             select t).Count();

44voto

Kevin Points 3692

Je pense que vous voulez quelque chose comme

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

(modifié pour tenir compte des commentaires)

14voto

Quickhorn Points 673

D'après ce que je comprends, la réponse sélectionnée charge toujours tous les tests associés. D'après ce blog msdn, il existe une meilleure solution.

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

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();
}

9voto

marc_s Points 321990

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 d'effectuer un balayage complet de la table (balayage de l'index en cluster).

Parfois, il suffit de connaître le nombre approximatif de lignes de la base de données, et dans ce cas, une instruction comme celle-ci peut 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

Cette fonction inspectera la vue de gestion dynamique et en extraira le nombre de lignes et la taille de la table, pour une table spécifique. Pour ce faire, elle additionne les entrées pour le tas (index_id = 0) ou l'index clusterisé (index_id = 1).

C'est rapide, facile à utiliser, mais il n'est pas garanti qu'il soit 100% exact ou à jour. Mais dans de nombreux cas, c'est "suffisant" (et cela fait peser une charge bien moindre sur le serveur).

Peut-être que ça pourrait marcher pour toi aussi ? Bien sûr, pour l'utiliser dans EF, il faudrait l'intégrer dans une procédure stockée ou utiliser un appel direct "Exécuter une requête SQL".

Marc

3voto

goosemanjack Points 339

Utilisez le ExecuteStoreQuery du contexte de l'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>();
    }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X