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.]

88
demandé sur NVRAM 2009-05-21 01:35:16

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.

105
répondu Craig Stuntz 2014-11-03 16:09:58

Je pense que vous voulez quelque chose comme

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

(modifié pour refléter les commentaires)

38
répondu Kevin 2009-05-20 22:14:18

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.

Http://blogs.msdn.com/b/adonet/archive/2011/01/31/using-dbcontext-in-ef-feature-ctp5-part-6-loading-related-entities.aspx

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();
}
14
répondu Quickhorn 2014-01-23 17:04:34

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.

9
répondu Yang Zhang 2016-06-24 13:53:03

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

8
répondu marc_s 2009-05-21 08:44:16

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>();
    }
3
répondu goosemanjack 2012-05-03 18:27:26

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();
2
répondu bytebender 2009-05-20 21:43:34