Valor de retorno máximo si consulta vacía

175

Tengo esta consulta:

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

¿Qué habrá maxShoeSizesi la empresa 8 no tiene trabajadores?

ACTUALIZACIÓN:
¿Cómo puedo cambiar la consulta para obtener 0 y no una excepción?

Naor
fuente
Naor: ¿has oído hablar de LINQPad?
Mitch Wheat
3
No entiendo por qué preguntarías '¿Qué habrá dentro maxShoeSize?' si ya lo habías probado
jwg
@jwg: Supongo que quería ver si sabes la respuesta :) Eventualmente obtuve una mejor manera de hacer lo que pedí y esto es lo que quise decir.
Naor
@Naor, este no es un juego de adivinanzas. También rechazaría la pregunta original. Si conoce la respuesta, dénoslo, de lo contrario, se ve perezoso. Justo ahora estaba a punto de hacer la misma pregunta y preparo toda la información, incluido el mensaje de excepción.
Juan Carlos Oropeza

Respuestas:

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

El cero en DefaultIfEmptyno es necesario.

Ron K.
fuente
Funciona :) Pero en mi código el cero en DefaultIfEmpty era necesario.
Carlos Tenorio Pérez
1
Con respecto a la primera parte de la pregunta, que no se responde completamente aquí: de acuerdo con la documentación oficial , si el tipo genérico de la secuencia vacía es un tipo de referencia, se obtiene nulo. De lo contrario, de acuerdo con esta pregunta , llamar Max()a una secuencia vacía da como resultado un error.
Raimund Krämer
59

Sé que esta es una pregunta antigua y la respuesta aceptada funciona, pero esta pregunta respondió a mi pregunta sobre si un conjunto tan vacío daría como resultado una excepción o un default(int)resultado.

Sin embargo, la respuesta aceptada, aunque funciona, no es la solución ideal en mi humilde opinión, que no se da aquí. Por lo tanto, lo estoy proporcionando en mi propia respuesta para el beneficio de cualquiera que lo esté buscando.

El código original del OP fue:

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

Así es como lo escribiría para evitar excepciones y proporcionar un resultado predeterminado:

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

Esto hace que el tipo de retorno de la Maxfunción sea int?, lo que permite el nullresultado y luego ??reemplaza el nullresultado con 0.


EDITAR
Solo para aclarar algo de los comentarios, Entity Framework actualmente no admite la aspalabra clave, por lo que la forma de escribirla cuando se trabaja con EF sería:

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

Dado que [TypeOfWorkers]podría ser un nombre de clase largo y es tedioso de escribir, he agregado un método de extensión para ayudar.

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

Esto sólo mangos int, pero el mismo se podría hacer para long, doubleo cualquier otro tipo de valor que necesita. El uso de este método de extensión es muy simple, simplemente pasa su función de selector y opcionalmente incluye un valor que se utilizará para nulo, que por defecto es 0. Por lo tanto, lo anterior podría reescribirse así:

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

Esperemos que eso ayude a las personas aún más.

CptRobby
fuente
3
Gran solución! La DefaultIfEmptyrespuesta más popular solo funciona bien cuando Maxno se está haciendo una evaluación.
McGuireV10
@ McGuireV10 Sí, normalmente no me gusta usar Selectcomo intermediario cuando solo voy a usar una función agregada como Maxen el resultado. También pienso (no lo he probado aún) que el SQL generado usaría una consulta de subselección adicional al hacer eso, mientras que el mío solo trataría con un conjunto vacío al devolver nulo. ¡Gracias por el voto y los comentarios! ;)
CptRobby
@ McGuireV10 Del mismo modo, si el ShoeSizeera en realidad en una relacionada Uniformentidad, yo no uso Workers.Where(x => x.CompanyId == 8).Select(x => x.Uniform).Max(x => x.ShoeSize), en vez me acaba de mantener toda la evaluación de la Maxfunción: Workers.Where(x => x.CompanyId == 8).Max(x => x.Uniform.ShoeSize). Prefiero utilizar la menor cantidad de métodos posible en mis consultas para permitir que EF tenga la mayor libertad para decidir cómo construir consultas de manera eficiente. ;-)
CptRobby
1
No pude hacer que esto funcione en EntityFramework. ¿Alguien puede arrojar algo de luz? (Utilizando la técnica DefaultIfEmpty funcionó).
Moe Sisko
1
Una versión genérica del método de extensión: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();
Cheng Chen
fuente
10

Si esto es Linq to SQL, no me gusta usarlo Any()porque da como resultado múltiples consultas al servidor SQL.

Si ShoeSizeno es un campo anulable, entonces usar solo el .Max(..) ?? 0no funcionará, pero lo siguiente sí:

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

Absolutamente no cambia el SQL emitido, pero devuelve 0 si la secuencia está vacía porque cambia el Max()para devolver un en int?lugar de un int.

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

(suponiendo que ShoeSizesea ​​de tipo int)

Si Workerses un DbSeto ObjectSetde Entity Framework, su consulta inicial arrojaría un InvalidOperationException, pero no se quejaría de una secuencia vacía, pero se quejaría de que el valor materializado NULL no se puede convertir en un int.

Slauma
fuente
2

Max lanzará System.InvalidOperationException "La secuencia no contiene elementos"

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;
}
Johan Tidén
fuente
2

NB: la consulta con DefaultIfEmpty()puede ser significativamente más lenta . En mi caso, fue una consulta simple con .DefaultIfEmpty(DateTime.Now.Date).

Era demasiado vago para perfilarlo, pero obviamente EF trató de obtener todas las filas y luego tomar el Max()valor.

Conclusión: a veces el manejo InvalidOperationExceptionpuede ser la mejor opción.

Andrey St
fuente
0

Puede usar un ternario dentro .Max()para manejar el predicado y establecer su valor;

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

Debería manejar que la Workerscolección sea nula / vacía si es una posibilidad, pero dependería de su implementación.

Jecoms
fuente
0

Puedes probar esto:

int maxShoeSize = Workers.Where(x=>x.CompanyId == 8).Max(x => x.ShoeSize) ?? 0;
Carlos Toledo
fuente
Creo que esto fallará ya que Max espera un int y se está volviendo nulo; Por lo tanto, ya se ha producido un error antes de que el operador de fusión nula entre en vigor.
d219
0

Puede verificar si hay trabajadores antes de hacer el 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);
}
Reverendo Sfinks
fuente