Qual é a melhor maneira de obter o valor máximo de uma consulta LINQ que pode não retornar linhas? Se eu apenas fizer
Dim x = (From y In context.MyTable _
Where y.MyField = value _
Select y.MyCounter).Max
Eu recebo um erro quando a consulta não retorna linhas. eu poderia fazer
Dim x = (From y In context.MyTable _
Where y.MyField = value _
Select y.MyCounter _
Order By MyCounter Descending).FirstOrDefault
mas isso parece um pouco obtuso para um pedido tão simples. Estou perdendo uma maneira melhor de fazer isso?
UPDATE: Aqui está a história de fundo: Estou tentando recuperar o próximo contador de elegibilidade de uma tabela filho (sistema legado, não me inicie ...). A primeira linha de elegibilidade para cada paciente é sempre 1, a segunda é 2, etc. (obviamente, essa não é a chave primária da tabela filho). Portanto, estou selecionando o valor máximo do contador existente para um paciente e adicionando 1 a ele para criar uma nova linha. Quando não há valores filho existentes, preciso que a consulta retorne 0 (portanto, adicionar 1 me fornecerá um valor de contador 1). Observe que não quero contar com a contagem bruta de linhas filhas, caso o aplicativo herdado introduza lacunas nos valores dos contadores (possível). Meu mal por tentar tornar a pergunta muito genérica.
fonte
Max
de aDateTime
.Max(x => (DateTime?)x.TimeStamp)
ainda a única maneira ..Eu apenas tive um problema semelhante, mas estava usando os métodos de extensão LINQ em uma lista, em vez da sintaxe de consulta. A conversão para um truque Nullable também funciona lá:
fonte
Parece um caso para
DefaultIfEmpty
(código não testado a seguir):fonte
var colCount = RowsEnumerable.Select(row => row.Cols.Count).DefaultIfEmpty().Max()
Pense no que você está perguntando!
O máximo de {1, 2, 3, -1, -2, -3} é obviamente 3. O máximo de {2} é obviamente 2. Mas qual é o máximo do conjunto vazio {}? Obviamente, essa é uma pergunta sem sentido. O máximo do conjunto vazio simplesmente não está definido. Tentar obter uma resposta é um erro matemático. O máximo de qualquer conjunto deve ser um elemento desse conjunto. O conjunto vazio não possui elementos, portanto, afirmar que um número específico é o máximo desse conjunto sem estar nesse conjunto é uma contradição matemática.
Assim como é correto o comportamento do computador lançar uma exceção quando o programador pede para dividir por zero, também é correto o comportamento do computador lançar uma exceção quando o programador pede que ele tire o máximo do conjunto vazio. Divisão por zero, pegar o máximo do conjunto vazio, agitar o spacklerorke e montar o unicórnio voador em Neverland são todos sem sentido, impossíveis, indefinidos.
Agora, o que você realmente quer fazer?
fonte
Você sempre pode adicionar
Double.MinValue
à sequência. Isso garantiria a existência de pelo menos um elemento eMax
o retornaria apenas se realmente for o mínimo. Para determinar qual opção é mais eficiente (Concat
,FirstOrDefault
ouTake(1)
), você deve executar comparações adequadas.fonte
Se a lista tiver algum elemento (ou seja, não estiver vazio), será necessário o máximo do campo MyCounter, caso contrário, retornará 0.
fonte
Desde o .Net 3.5, você pode usar DefaultIfEmpty () passando o valor padrão como argumento. Algo como uma das seguintes maneiras:
A primeira é permitida quando você consulta uma coluna NOT NULL e a segunda é a maneira como a usou para consultar uma coluna NULLABLE. Se você usar DefaultIfEmpty () sem argumentos, o valor padrão será o definido para o tipo de sua saída, como você pode ver na Tabela de Valores Padrão .
O SELECT resultante não será tão elegante, mas é aceitável.
Espero que ajude.
fonte
Acho que o problema é o que você deseja que aconteça quando a consulta não tiver resultados. Se esse for um caso excepcional, eu agruparia a consulta em um bloco try / catch e manipularia a exceção que a consulta padrão gera. Se não for aceitável que a consulta retorne nenhum resultado, você precisará descobrir o que deseja que seja o resultado nesse caso. Pode ser que a resposta de @ David (ou algo semelhante funcione). Ou seja, se o MAX sempre for positivo, poderá ser suficiente inserir um valor "ruim" conhecido na lista que será selecionada apenas se não houver resultados. Geralmente, eu esperaria que uma consulta que estivesse recuperando o máximo tivesse alguns dados para trabalhar e seguisse a rota try / catch, caso contrário, você sempre será forçado a verificar se o valor obtido está correto ou não. EU'
fonte
Outra possibilidade seria agrupar, semelhante à maneira como você pode abordá-la no SQL bruto:
A única coisa é (testando novamente no LINQPad) a mudança para o sabor VB LINQ fornece erros de sintaxe na cláusula de agrupamento. Tenho certeza de que o equivalente conceitual é fácil de encontrar, simplesmente não sei como refleti-lo no VB.
O SQL gerado seria algo como:
O SELECT aninhado parece nojento, como se a execução da consulta recuperasse todas as linhas e selecionasse a correspondente do conjunto recuperado ... a questão é se o SQL Server otimiza ou não a consulta em algo comparável à aplicação da cláusula where ao SELECT interno. Estou investigando isso agora ...
Não sou versado em interpretar planos de execução no SQL Server, mas parece que quando a cláusula WHERE está no SELECT externo, o número de linhas reais resultantes dessa etapa é todas as linhas da tabela, em comparação apenas às linhas correspondentes quando a cláusula WHERE estiver no SELECT interno. Dito isso, parece que apenas 1% do custo foi alterado para a etapa seguinte quando todas as linhas são consideradas, e de qualquer maneira apenas uma linha volta do SQL Server, portanto, talvez não seja essa a grande diferença no grande esquema das coisas .
fonte
litt tarde, mas eu tinha a mesma preocupação ...
Reescrevendo seu código da postagem original, você deseja o máximo do conjunto S definido por
Tendo em conta o seu último comentário
Posso reformular seu problema como: Você deseja o máximo de {0 + S}. E parece que a solução proposta com concat é semanticamente a correta :-)
fonte
Por que não algo mais direto como:
fonte
Uma diferença interessante que parece digna de nota é que, enquanto FirstOrDefault e Take (1) geram o mesmo SQL (de qualquer forma, de acordo com o LINQPad), FirstOrDefault retorna um valor - o padrão - quando não há linhas correspondentes e Take (1) retorna sem resultados ... pelo menos no LINQPad.
fonte
Apenas para que todos saibam que está usando o Linq to Entities, os métodos acima não funcionarão ...
Se você tentar fazer algo como
Irá lançar uma exceção:
Eu sugeriria apenas fazer
E
FirstOrDefault
retornará 0 se sua lista estiver vazia.fonte
fonte
Eu bati um
MaxOrDefault
método de extensão. Não há muito, mas sua presença no Intellisense é um lembrete útil de queMax
em uma sequência vazia causará uma exceção. Além disso, o método permite que o padrão seja especificado, se necessário.fonte
Para o Entity Framework e o Linq to SQL, podemos conseguir isso definindo um método de extensão que modifica um método
Expression
passado para oIQueryable<T>.Max(...)
método:Uso:
A consulta gerada é idêntica, funciona como uma chamada normal ao
IQueryable<T>.Max(...)
método, mas se não houver registros, ele retornará um valor padrão do tipo T em vez de gerar uma exceçãofonte
Eu apenas tive um problema semelhante, meus testes de unidade passaram usando Max (), mas falharam quando executados em um banco de dados ativo.
Minha solução foi separar a consulta da lógica que está sendo executada, não juntá-los em uma consulta.
Eu precisava de uma solução para trabalhar em testes de unidade usando objetos Linq (em Linq-objects Max () trabalha com nulos) e Linq-sql ao executar em um ambiente ativo.
(Eu zombei do Select () nos meus testes)
Menos eficiente? Provavelmente.
Eu me importo, desde que meu aplicativo não caia na próxima vez? Não.
fonte