web-dev-qa-db-fra.com

Comment COMPTER les lignes dans EntityFramework sans charger le contenu?

J'essaie de déterminer comment compter les lignes correspondantes d'une table à l'aide de EntityFramework.

Le problème est que chaque ligne peut contenir plusieurs mégaoctets de données (dans un champ binaire). Bien sûr, le code SQL ressemblerait à ceci:

SELECT COUNT(*) FROM [MyTable] WHERE [fkID] = '1';

Je pourrais 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 totalement inefficace. Y a-t-il un moyen plus simple?


EDIT: Merci à tous. J'ai déplacé la base de données d'un attaché privé pour pouvoir exécuter le profilage; cela aide mais provoque des confusions que je ne m'attendais pas.

Et mes vraies données sont un peu plus profondes, je vais utiliser Trucks portant Palettes de Cas de Articles - et je ne veux pas que le Camion quitte sauf s'il y a au moins un Article dedans.

Mes tentatives sont indiquées ci-dessous. La partie que je ne comprends pas, c'est que CASE_2 n'a jamais accès 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, chauffeurs, palettes, caisses ou articles; Comme vous pouvez le constater d'après le code SQL, les relations camion-palette et palette-caisse sont multiples - bien que, selon moi, cela ne compte pas. Mes vrais objets sont intangibles et plus difficiles à décrire, alors j'ai changé les noms. ]

97
NVRAM

Syntaxe de la 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.

110
Craig Stuntz

Je pense que tu veux quelque chose comme

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

(édité pour refléter les commentaires)

43
Kevin

Si je comprends bien, la réponse sélectionnée charge toujours tous les tests correspondants. 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

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
Quickhorn

Eh bien, même la SELECT COUNT(*) FROM Table sera assez inefficace, en particulier sur les grandes tables, car SQL Server ne peut rien faire d'autre qu'une analyse complète de la table (analyse d'index en cluster).

Parfois, il est suffisant de connaître un nombre approximatif de lignes de la base de données, et dans un tel cas, une instruction 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 inspectera la vue de gestion dynamique et en extraira le nombre de lignes et la taille de la table, en fonction d'une table spécifique. Pour ce faire, il récapitule les entrées du tas (index_id = 0) ou de l'index cluster (index_id = 1).

C'est rapide, facile à utiliser, mais sa précision et sa mise à jour ne sont pas garanties. 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 devrez envelopper cela dans un proc stocké ou utiliser un appel direct "Exécuter une requête SQL".

Marc

9
marc_s

Ceci est mon code:

IQueryable<AuctionRecord> records = db.AuctionRecord;
var count = records.Count();

Assurez-vous que la variable est définie en tant que IQueryable. Lorsque vous utilisez la méthode Count (), EF exécute quelque chose comme:

select count(*) from ...

Sinon, si les enregistrements sont définis en tant que IEnumerable, le SQL généré interrogera la table entière et comptera les lignes renvoyées.

9
Yang Zhang

Utilisez la méthode ExecuteStoreQuery du contexte de l'entité. Cela évite de télécharger l'ensemble des résultats et de procéder à la désérialisation en objets afin d'effectuer un simple décompte 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
goosemanjack

Je pense que ça devrait marcher ...

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
bytebender