Como COUNT linhas dentro de EntityFramework sem carregar conteúdo?


109

Estou tentando determinar como contar as linhas correspondentes em uma tabela usando o EntityFramework.

O problema é que cada linha pode ter muitos megabytes de dados (em um campo Binário). Claro que o SQL seria algo assim:

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

Eu poderia carregar todas as linhas e , em seguida, encontrar o conde com:

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

Mas isso é extremamente ineficiente. Existe uma maneira mais simples?


EDIT: Obrigado a todos. Mudei o banco de dados de um anexo privado para poder executar a criação de perfil; isso ajuda, mas causa confusões que eu não esperava.

E meus dados reais são um pouco mais profundos, usarei Caminhões carregando paletes de caixas de itens - e não quero que o caminhão saia a menos que haja pelo menos um item nele.

Minhas tentativas são mostradas abaixo. A parte que não entendo é que CASE_2 nunca acessa o servidor de banco de dados (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

E o SQL resultante de CASE_1 é canalizado por meio de sp_executesql , mas:

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

[ Eu realmente não tenho caminhões, motoristas, paletes, caixas ou itens; como você pode ver no SQL, os relacionamentos Truck-Pallet e Pallet-Case são muitos para muitos - embora eu não ache que isso importe. Meus objetos reais são intangíveis e mais difíceis de descrever, então mudei os nomes. ]


1
como você resolveu o problema de carregamento de paletes?
— Sherlock

Respostas:


123

Sintaxe de consulta:

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

Sintaxe do método:

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

Ambos geram a mesma consulta SQL.


Por que SelectMany()? É necessário? Não funcionaria bem sem ele?
— Jo Smo

@JoSmo, não, essa é uma consulta totalmente diferente.
— Craig Stuntz

Obrigado por esclarecer isso para mim. Só queria ter certeza. :)
— Jo Smo

1
Você pode me dizer por que é diferente com o SelectMany? Eu não entendo. Eu faço isso sem SelectMany, mas fica muito lento porque tenho mais de 20 milhões de registros. Tentei a resposta de Yang Zhang e funciona muito bem, só queria saber o que o SelectMany faz.
— mikesoft de

1
@AustinFelipe Sem a chamada para SelectMany, a consulta retornaria o número de linhas em MyContainer com o ID igual a '1'. A chamada SelectMany retorna todas as linhas em MyTable que pertencem ao resultado anterior da consulta (significando o resultado de MyContainer.Where(o => o.ID == '1'))
— sbecker

48

Eu acho que você quer algo como

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

(editado para refletir os comentários)


1
Não, ele precisa da contagem das entidades em MyTable referenciadas por uma entidade com ID = 1 em MyContainer
— Craig Stuntz

3
A propósito, se t.ID for um PK, a contagem no código acima será sempre 1. :)
— Craig Stuntz

2
@Craig, você está certo, eu deveria ter usado t.ForeignTable.ID. Atualizada.
— Kevin de

1
Bem, isso é curto e simples. Minha escolha é: var count = context.MyTable.Count(t => t.MyContainer.ID == '1'); não longa e feia: var count = (from o in context.MyContainer where o.ID == '1' from t in o.MyTable select t).Count(); Mas depende do estilo de codificação ...
— CL

certifique-se de incluir "using System.Linq", ou isso não funcionará
— CountMurphy

16

Pelo que entendi, a resposta selecionada ainda carrega todos os testes relacionados. De acordo com este blog do msdn, existe uma maneira melhor.

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

Especificamente

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

3
Não há necessidade de fazer Find(1)pedido extra . Basta criar a entidade e anexar ao contexto:var princess = new PrincessEntity{ Id = 1 }; context.Princesses.Attach(princess);
— tenbits

13

Este é o meu código:

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

Certifique-se de que a variável está definida como IQueryable, então quando você usa o método Count (), EF irá executar algo como

select count(*) from ...

Caso contrário, se os registros forem definidos como IEnumerable, o sql gerado consultará a tabela inteira e contará as linhas retornadas.


10

Bem, mesmo o SELECT COUNT(*) FROM Tableserá bastante ineficiente, especialmente em tabelas grandes, uma vez que o SQL Server realmente não pode fazer nada além de fazer uma verificação completa da tabela (verificação de índice de cluster).

Às vezes, é bom o suficiente saber um número aproximado de linhas do banco de dados e, nesse caso, uma declaração como esta pode 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

Isso inspecionará a visualização de gerenciamento dinâmico e extrairá o número de linhas e o tamanho da tabela dela, dada uma tabela específica. Ele faz isso somando as entradas para o heap (index_id = 0) ou o índice clusterizado (index_id = 1).

É rápido e fácil de usar, mas não é garantido que seja 100% preciso ou atualizado. Mas, em muitos casos, isso é "bom o suficiente" (e sobrecarrega muito menos o servidor).

Talvez isso funcione para você também? Claro, para usá-lo no EF, você teria que embrulhar isso em um procedimento armazenado ou usar uma chamada direta "Execute SQL query".

Marc


1
Não será uma varredura completa da tabela devido à referência FK no WHERE. Apenas os detalhes do mestre serão verificados. O problema de desempenho que ele estava enfrentando era o carregamento de dados de blob, não a contagem de registros. Presumindo que não haja tipicamente dezenas de milhares de registros de detalhes por registro mestre, eu não "otimizaria" algo que não seja realmente lento.
— Craig Stuntz

OK, sim, nesse caso, você só selecionará um subconjunto - isso deve estar bem. Quanto aos dados de blob - tive a impressão de que você poderia definir um "carregamento adiado" em qualquer coluna de qualquer uma de suas tabelas EF para evitar carregá-lo, então isso pode ajudar.
— marc_s

Existe uma maneira de usar esse SQL com o EntityFramework? De qualquer forma, neste caso, eu só precisava saber que havia linhas correspondentes, mas intencionalmente fiz a pergunta de forma mais geral.
— NVRAM de

4

Use o método ExecuteStoreQuery do contexto da entidade. Isso evita o download de todo o conjunto de resultados e a desserialização em objetos para fazer uma contagem de linhas simples.

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

6
Se você escrever int count = context.MyTable.Count(m => m.MyContainerID == '1'), o SQL gerado se parecerá exatamente com o que você está fazendo, mas o código é muito melhor. Nenhuma entidade é carregada na memória como tal. Experimente no LINQPad se desejar - ele mostrará o SQL usado nos bastidores.
— Drew Noakes em

SQL em linha. . não é minha coisa favorita.
— Duanne,

3

Eu acho que isso deve 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();

Esta é a direção que eu segui no início também, mas é meu entendimento que, a menos que você o tenha adicionado manualmente, m terá uma propriedade MyContainer, mas nenhum MyContainerId. Portanto, o que você deseja examinar é m.MyContainer.ID.
— Kevin

Se MyContainer é o pai e MyTable são os filhos no relacionamento, você teria que estabelecer esse relacionamento com alguma chave estrangeira, não tenho certeza de como você saberia quais entidades MyTable estão associadas a uma entidade MyContainer ... Mas talvez eu fez uma suposição sobre a estrutura ...
— bytebender
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.