¿Cómo CONTAR filas dentro de EntityFramework sin cargar contenido?

109

Estoy tratando de determinar cómo contar las filas coincidentes en una tabla usando EntityFramework.

El problema es que cada fila puede tener muchos megabytes de datos (en un campo binario). Por supuesto, el SQL sería algo como esto:

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

Podría cargar todas las filas y luego encontrar el Conde con:

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

Pero eso es tremendamente ineficiente. ¿Hay alguna forma más simple?


EDITAR: Gracias a todos. He movido la base de datos de un adjunto privado para poder ejecutar la creación de perfiles; esto ayuda pero causa confusiones que no esperaba.

Y mis datos reales son un poco más profundos, usaré camiones que transporten paletas de cajas de artículos , y no quiero que el camión se vaya a menos que haya al menos un artículo en él.

Mis intentos se muestran a continuación. La parte que no entiendo es que CASE_2 nunca accede al servidor de base de datos (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

Y el SQL resultante de CASE_1 se canaliza a través de sp_executesql , pero:

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

[ Realmente no tengo camiones, conductores, tarimas, cajas o artículos; Como puede ver en el SQL, las relaciones Camión-Palé y Palé-Caja son de muchos a muchos, aunque no creo que eso importe. Mis objetos reales son intangibles y más difíciles de describir, así que cambié los nombres. ]

NVRAM
fuente
1
¿Cómo resolvió el problema de carga de paletas?
Sherlock

Respuestas:

123

Sintaxis de la consulta:

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

Sintaxis del método:

var count = context.MyContainer
            .Where(o => o.ID == '1')
            .SelectMany(o => o.MyTable)
            .Count()

Ambos generan la misma consulta SQL.

Craig Stuntz
fuente
¿Por qué el SelectMany()? Es necesario ¿No funcionaría correctamente sin él?
Jo Smo
@JoSmo, no, esa es una consulta totalmente diferente.
Craig Stuntz
Gracias por aclarármelo. Sólamente quería estar seguro. :)
Jo Smo
1
¿Puede decirme por qué es diferente con SelectMany? No entiendo. Lo hago sin SelectMany, pero se vuelve muy lento porque tengo más de 20 millones de registros. Probé la respuesta de Yang Zhang y funciona muy bien, solo quería saber qué hace SelectMany.
mikesoft
1
@AustinFelipe Sin la llamada a SelectMany, la consulta devolvería el número de filas en MyContainer con el ID igual a '1'. La llamada SelectMany devuelve todas las filas en MyTable que pertenecen al resultado anterior de la consulta (es decir, el resultado de MyContainer.Where(o => o.ID == '1'))
sbecker
48

Creo que quieres algo como

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

(editado para reflejar comentarios)

Kevin
fuente
1
No, necesita el recuento de las entidades en MyTable a las que hace referencia la única entidad con ID = 1 en MyContainer
Craig Stuntz
3
Por cierto, si t.ID es un PK, el recuento en el código anterior siempre será 1. :)
Craig Stuntz
2
@ Craig, tienes razón, debería haber usado t.ForeignTable.ID. Actualizado.
Kevin
1
Bueno, esto es breve y simple. Mi elección es: var count = context.MyTable.Count(t => t.MyContainer.ID == '1'); no larga y fea: var count = (from o in context.MyContainer where o.ID == '1' from t in o.MyTable select t).Count(); pero depende del estilo de codificación ...
CL
asegúrese de incluir "using System.Linq", o esto no funcionará
CountMurphy
16

Según tengo entendido, la respuesta seleccionada aún carga todas las pruebas relacionadas. Según este blog de msdn, hay una forma mejor.

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

Específicamente

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();
}
Quickhorn
fuente
3
No es necesario realizar ninguna Find(1)solicitud adicional . Simplemente cree la entidad y adjúntela al contexto:var princess = new PrincessEntity{ Id = 1 }; context.Princesses.Attach(princess);
tenbits
13

Este es mi codigo:

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

Asegúrese de que la variable esté definida como IQueryable, luego, cuando use el método Count (), EF ejecutará algo como

select count(*) from ...

De lo contrario, si los registros se definen como IEnumerable, el sql generado consultará toda la tabla y contará las filas devueltas.

Yang Zhang
fuente
10

Bueno, incluso SELECT COUNT(*) FROM Tableserá bastante ineficiente, especialmente en tablas grandes, ya que SQL Server realmente no puede hacer nada más que realizar un escaneo completo de la tabla (escaneo de índice agrupado).

A veces, es suficientemente bueno saber un número aproximado de filas de la base de datos y, en tal caso, una declaración como esta podría ser suficiente:

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

Esto inspeccionará la vista de administración dinámica y extraerá el número de filas y el tamaño de la tabla, dada una tabla específica. Lo hace sumando las entradas para el montón (index_id = 0) o el índice agrupado (index_id = 1).

Es rápido, fácil de usar, pero no se garantiza que sea 100% preciso o esté actualizado. Pero en muchos casos, esto es "suficientemente bueno" (y supone una carga mucho menor para el servidor).

¿Quizás eso también funcionaría para ti? Por supuesto, para usarlo en EF, tendría que resumir esto en un proceso almacenado o usar una llamada directa "Ejecutar consulta SQL".

Bagazo

marc_s
fuente
1
No será un escaneo de tabla completo debido a la referencia FK en el DÓNDE. Solo se escanearán los detalles del maestro. El problema de rendimiento que tenía era la carga de datos de blobs, no el recuento de registros. Suponiendo que no hay típicamente decenas de miles + de registros detallados por registro maestro, no "optimizaría" algo que no sea realmente lento.
Craig Stuntz
De acuerdo, sí, en ese caso, solo seleccionará un subconjunto, eso debería estar bien. En cuanto a los datos de blob, tenía la impresión de que podría establecer una "carga diferida" en cualquier columna de cualquiera de sus tablas EF para evitar cargarla, por lo que podría ayudar.
marc_s
¿Hay alguna forma de usar este SQL con EntityFramework? De todos modos, en este caso solo necesitaba saber que había filas coincidentes, pero intencionalmente hice la pregunta de manera más general.
NVRAM
4

Utilice el método ExecuteStoreQuery del contexto de la entidad. Esto evita descargar todo el conjunto de resultados y deserializar en objetos para hacer un simple recuento de filas.

   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>();
    }
goosemanjack
fuente
6
Si escribe int count = context.MyTable.Count(m => m.MyContainerID == '1'), el SQL generado se parecerá exactamente a lo que está haciendo, pero el código es mucho mejor. Ninguna entidad se carga en la memoria como tal. Pruébelo en LINQPad si lo desea; le mostrará el SQL utilizado debajo de las cubiertas.
Drew Noakes
SQL en línea. . no es mi cosa favorita.
Duanne
3

Creo que esto debería funcionar...

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();
bytebender
fuente
Esta es la dirección que tomé al principio también, pero tengo entendido que, a menos que lo haya agregado manualmente, m tendrá una propiedad MyContainer pero no MyContainerId. Por lo tanto, lo que desea examinar es m.MyContainer.ID.
Kevin
Si MyContainer es el padre y MyTable son los hijos en la relación, entonces tenía que establecer esa relación con alguna clave externa, no estoy seguro de qué otra manera sabría qué entidades MyTable estaban asociadas con una entidad MyContainer ... Pero tal vez yo hizo una suposición sobre la estructura ...
bytebender