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. ]
fonte
Respostas:
Sintaxe de consulta:
Sintaxe do método:
Ambos geram a mesma consulta SQL.
fonte
SelectMany()
? É necessário? Não funcionaria bem sem ele?MyContainer.Where(o => o.ID == '1')
)Eu acho que você quer algo como
(editado para refletir os comentários)
fonte
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 ...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
fonte
Find(1)
pedido extra . Basta criar a entidade e anexar ao contexto:var princess = new PrincessEntity{ Id = 1 }; context.Princesses.Attach(princess);
Este é o meu código:
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
Caso contrário, se os registros forem definidos como IEnumerable, o sql gerado consultará a tabela inteira e contará as linhas retornadas.
fonte
Bem, mesmo o
SELECT COUNT(*) FROM Table
será 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:
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
fonte
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.
fonte
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.Eu acho que isso deve funcionar...
fonte