Valor máximo de retorno se consulta vazia


175

Eu tenho esta consulta:

int maxShoeSize = Workers
    .Where(x => x.CompanyId == 8)
    .Max(x => x.ShoeSize);

O que acontecerá maxShoeSizese a empresa 8 não tiver trabalhadores?

ATUALIZAÇÃO:
Como posso alterar a consulta para obter 0 e não uma exceção?


Naor: você já ouviu falar do LINQPad?
Mitch Wheat

3
Não entendo por que você pergunta "o que vai acontecer maxShoeSize?" se você já experimentou.
Jwg 16/05

@jwg: Acho que queria ver se você sabe a resposta :) Eventualmente, eu tenho uma maneira melhor de fazer o que pedi e foi isso que eu quis dizer.
Naor

@ Naor, este não é um jogo de adivinhação. Eu também rebaixaria a pergunta original. Se você souber a resposta, dê-a, caso contrário, você parece preguiçoso. Agora eu estava prestes a fazer a mesma pergunta e preparo todas as informações, incluindo a mensagem de exceção.
Juan Carlos Oropeza 11/11

Respostas:


298
int maxShoeSize = Workers.Where(x => x.CompanyId == 8)
                         .Select(x => x.ShoeSize)
                         .DefaultIfEmpty(0)
                         .Max();

O zero in DefaultIfEmptynão é necessário.


Funciona :) Mas no meu código o zero no DefaultIfEmpty era necessário.
Carlos Tenorio Pérez

1
Em relação à primeira parte da pergunta, que não é totalmente respondida aqui: De acordo com a documentação oficial , se o tipo genérico da sequência vazia for um tipo de referência, você será nulo. Caso contrário, de acordo com esta pergunta , chamar Max()uma sequência vazia resulta em um erro.
Raimund Krämer

59

Eu sei que essa é uma pergunta antiga e a resposta aceita funciona, mas essa pergunta respondeu à minha pergunta sobre se um conjunto vazio resultaria em uma exceção ou em um default(int)resultado.

A resposta aceita, no entanto, enquanto funciona, não é a solução ideal IMHO, que não é fornecida aqui. Portanto, estou fornecendo isso em minha própria resposta para o benefício de quem estiver procurando por ele.

O código original do OP era:

int maxShoeSize = Workers.Where(x => x.CompanyId == 8).Max(x => x.ShoeSize);

É assim que eu escreveria para evitar exceções e fornecer um resultado padrão:

int maxShoeSize = Workers.Where(x => x.CompanyId == 8).Max(x => x.ShoeSize as int?) ?? 0;

Isso faz com que seja o tipo de retorno da Maxfunção int?, que permite o nullresultado e, em seguida, ??substitui o nullresultado por 0.


EDIT
Apenas para esclarecer algo dos comentários, o Entity Framework atualmente não suporta a aspalavra - chave; portanto, a maneira de escrevê-la ao trabalhar com a EF seria:

int maxShoeSize = Workers.Where(x => x.CompanyId == 8).Max<[TypeOfWorkers], int?>(x => x.ShoeSize) ?? 0;

Como [TypeOfWorkers]poderia ser um nome de classe longo e é entediante de escrever, adicionei um método de extensão para ajudar.

public static int MaxOrDefault<T>(this IQueryable<T> source, Expression<Func<T, int?>> selector, int nullValue = 0)
{
    return source.Max(selector) ?? nullValue;
}

Isso só alças int, mas o mesmo poderia ser feito para long, doubleou qualquer outro tipo de valor que você precisa. O uso desse método de extensão é muito simples, basta passar na função seletora e, opcionalmente, incluir um valor a ser usado para null, cujo padrão é 0. Portanto, o acima pode ser reescrito da seguinte maneira:

int maxShoeSize = Workers.Where(x => x.CompanyId == 8).MaxOrDefault(x => x.ShoeSize);

Espero que ajude as pessoas ainda mais.


3
Ótima solução! A DefaultIfEmptyresposta mais popular só funciona bem quando Maxnão está sendo feita uma avaliação.
precisa saber é o seguinte

@ McGuireV10 Sim, normalmente não gosto de usar Selectcomo intermediário quando vou usar uma função agregada, como Maxno resultado. Eu também acho (ainda não testei isso) que o SQL gerado usaria uma consulta de subseleção extra fazendo isso, enquanto o meu lidaria apenas com um conjunto vazio retornando nulo. Obrigado pelo voto positivo e feedback! ;)
CptRobby

@ McGuireV10 Da mesma forma, se o ShoeSizefoi, na verdade, em uma relacionado Uniformentidade, eu não usar Workers.Where(x => x.CompanyId == 8).Select(x => x.Uniform).Max(x => x.ShoeSize), em vez gostaria apenas de manter toda a avaliação na Maxfunção: Workers.Where(x => x.CompanyId == 8).Max(x => x.Uniform.ShoeSize). Prefiro usar o menor número possível de métodos em minhas consultas para permitir que a EF tenha a maior liberdade para decidir como construir consultas com eficiência. ;-)
CptRobby

1
Não consegui fazer isso funcionar no EntityFramework. Alguém pode lançar alguma luz? (Usando a técnica DefaultIfEmpty funcionou).
Moe Sisko

1
Uma versão genérica do método de extensão:public static TResult MaxOrDefault<TElement, TResult>(this IQueryable<TElement> items, Expression<Func<TElement, TResult>> selector, TResult defaultValue = default) where TResult : struct => items.Select(selector).Max(item => (TResult?)item) ?? defaultValue;
relativamente_random


17
int maxShoeSize = Workers.Where(x => x.CompanyId == 8)
                     .Select(x => x.ShoeSize)
                     .DefaultIfEmpty()
                     .Max();

10

Se for Linq to SQL, não gosto de usar, Any()pois resulta em várias consultas ao SQL Server.

Se ShoeSizenão for um campo anulável, usar apenas o .Max(..) ?? 0não funcionará, mas o seguinte:

int maxShoeSize = Workers.Where(x = >x.CompanyId == 8).Max(x => (int?)x.ShoeSize) ?? 0;

Absolutamente não altera o SQL emitido, mas retorna 0 se a sequência estiver vazia porque altera o Max()retorno de um em int?vez de um int.


4
int maxShoeSize=Workers.Where(x=>x.CompanyId==8)
    .Max(x=>(int?)x.ShoeSize).GetValueOrDefault();

(supondo que ShoeSizeseja do tipo int)

Se Workersfor um DbSetou ObjectSetdo Entity Framework, sua consulta inicial geraria uma InvalidOperationException, mas não reclamaria de uma sequência vazia, mas reclamaria que o valor materializado NULL não pode ser convertido em uma int.


2

Max lançará System.InvalidOperationException "Sequence contém nenhum elemento"

class Program
{
    static void Main(string[] args)
    {
        List<MyClass> list = new List<MyClass>();

        list.Add(new MyClass() { Value = 2 });

        IEnumerable<MyClass> iterator = list.Where(x => x.Value == 3); // empty iterator.

        int max = iterator.Max(x => x.Value); // throws System.InvalidOperationException
    }
}

class MyClass
{
    public int Value;
}

2

Nota: a consulta com DefaultIfEmpty()pode ser significativamente mais lenta . No meu caso, isso foi uma consulta simples com .DefaultIfEmpty(DateTime.Now.Date).

Eu estava com preguiça de criar um perfil, mas obviamente a EF tentou obter todas as linhas e, em seguida, pegar o Max()valor.

Conclusão: às vezes o manuseio InvalidOperationExceptionpode ser a melhor escolha.


0

Você pode usar um ternário interno .Max()para manipular o predicado e definir seu valor;

// assumes Workers != null && Workers.Count() > 0
int maxShoeSize = Workers.Max(x => (x.CompanyId == 8) ? x.ShoeSize : 0);

Você precisaria manipular a Workerscoleção como nula / vazia, se for possível, mas isso dependerá da sua implementação.


0

Você pode tentar isso:

int maxShoeSize = Workers.Where(x=>x.CompanyId == 8).Max(x => x.ShoeSize) ?? 0;

Eu acho que isso falhará, pois Max está esperando um int e está recebendo um nulo; portanto, um erro já ocorreu antes que o operador de coalescência nula entre em vigor.
D219 08/1118

0

Você pode verificar se há trabalhadores antes de executar o Max ().

private int FindMaxShoeSize(IList<MyClass> workers) {
   var workersInCompany = workers.Where(x => x.CompanyId == 8);
   if(!workersInCompany.Any()) { return 0; }
   return workersInCompany.Max(x => x.ShoeSize);
}
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.