¿Cuál es la mejor manera de obtener el valor máximo de una consulta LINQ que puede no devolver filas? Si solo lo hago
Dim x = (From y In context.MyTable _
Where y.MyField = value _
Select y.MyCounter).Max
Recibo un error cuando la consulta no devuelve filas. Yo podría hacer
Dim x = (From y In context.MyTable _
Where y.MyField = value _
Select y.MyCounter _
Order By MyCounter Descending).FirstOrDefault
pero eso se siente un poco obtuso para una solicitud tan simple. ¿Me estoy perdiendo una mejor manera de hacerlo?
ACTUALIZACIÓN: Aquí está la historia de fondo: estoy tratando de recuperar el siguiente contador de elegibilidad de una tabla secundaria (sistema heredado, no me hagas empezar ...). La primera fila de elegibilidad para cada paciente es siempre 1, la segunda es 2, etc. (obviamente, esta no es la clave principal de la tabla secundaria). Entonces, selecciono el valor máximo de contador existente para un paciente, y luego agrego 1 para crear una nueva fila. Cuando no hay valores secundarios existentes, necesito que la consulta devuelva 0 (por lo tanto, agregar 1 me dará un valor de contador de 1). Tenga en cuenta que no quiero confiar en el recuento bruto de las filas secundarias, en caso de que la aplicación heredada presente huecos en los valores del contador (posible). Mi mal por tratar de hacer la pregunta demasiado genérica.
fuente
Max
de aDateTime
.Max(x => (DateTime?)x.TimeStamp)
sigue siendo la única manera ..Simplemente tuve un problema similar, pero estaba usando métodos de extensión LINQ en una lista en lugar de la sintaxis de consulta. El lanzamiento a un truco de Nullable también funciona allí:
fuente
Suena como un caso para
DefaultIfEmpty
(el código no probado sigue):fuente
var colCount = RowsEnumerable.Select(row => row.Cols.Count).DefaultIfEmpty().Max()
¡Piensa en lo que estás preguntando!
El máximo de {1, 2, 3, -1, -2, -3} es obviamente 3. El máximo de {2} es obviamente 2. ¿Pero cuál es el máximo del conjunto vacío {}? Obviamente esa es una pregunta sin sentido. El máximo del conjunto vacío simplemente no está definido. Intentar obtener una respuesta es un error matemático. El máximo de cualquier conjunto debe ser un elemento en ese conjunto. El conjunto vacío no tiene elementos, por lo que afirmar que un número particular es el máximo de ese conjunto sin estar en ese conjunto es una contradicción matemática.
Así como es correcto que la computadora arroje una excepción cuando el programador le pide que se divida entre cero, así es correcto que la computadora arroje una excepción cuando el programador le pide que tome el máximo del conjunto vacío. La división por cero, tomar el máximo del conjunto vacío, mover el spacklerorke y montar el unicornio volador hacia Neverland no tiene sentido, es imposible, no está definido.
Ahora, ¿qué es lo que realmente quieres hacer?
fuente
Siempre puedes agregar
Double.MinValue
a la secuencia. Esto garantizaría que haya al menos un elemento yMax
lo devolvería solo si en realidad es el mínimo. Para determinar qué opción es más eficiente (Concat
,FirstOrDefault
oTake(1)
), debe realizar una evaluación comparativa adecuada.fuente
Si la lista tiene algún elemento (es decir, no está vacío), tomará el máximo del campo MyCounter, de lo contrario devolverá 0.
fuente
Desde .Net 3.5 puede usar DefaultIfEmpty () pasando el valor predeterminado como argumento. Algo así como una de las siguientes formas:
La primera está permitida cuando consulta una columna NO NULL y la segunda es la forma en que se utiliza para consultar una columna NULLABLE. Si usa DefaultIfEmpty () sin argumentos, el valor predeterminado será el definido para el tipo de salida, como puede ver en la Tabla de valores predeterminados .
El SELECT resultante no será tan elegante pero es aceptable.
Espero eso ayude.
fuente
Creo que el problema es qué quieres que suceda cuando la consulta no tiene resultados. Si este es un caso excepcional, envolvería la consulta en un bloque try / catch y manejaría la excepción que genera la consulta estándar. Si está bien que la consulta no devuelva ningún resultado, debe averiguar cuál quiere que sea el resultado en ese caso. Puede ser que la respuesta de @ David (o algo similar funcione). Es decir, si el MAX siempre será positivo, entonces puede ser suficiente insertar un valor "malo" conocido en la lista que solo se seleccionará si no hay resultados. En general, esperaría que una consulta que está recuperando un máximo tenga algunos datos para trabajar e iría a la ruta try / catch ya que de lo contrario siempre se verá obligado a verificar si el valor que obtuvo es correcto o no. YO'
fuente
Otra posibilidad sería la agrupación, similar a la forma en que podría abordarlo en SQL sin formato:
Lo único es (probar nuevamente en LINQPad) cambiar al sabor VB LINQ da errores de sintaxis en la cláusula de agrupación. Estoy seguro de que el equivalente conceptual es bastante fácil de encontrar, simplemente no sé cómo reflejarlo en VB.
El SQL generado sería algo similar a:
El SELECT anidado parece repulsivo, como si la ejecución de la consulta recuperara todas las filas y luego seleccionara la correspondiente del conjunto recuperado ... la pregunta es si SQL Server optimiza la consulta en algo comparable a aplicar la cláusula where al SELECT interno. Estoy investigando eso ahora ...
No estoy bien versado en la interpretación de planes de ejecución en SQL Server, pero parece que cuando la cláusula WHERE está en el SELECT externo, el número de filas reales que dan como resultado ese paso es todas las filas de la tabla, en comparación con solo las filas coincidentes cuando la cláusula WHERE está en el SELECT interno. Dicho esto, parece que solo el 1% del costo se desplaza al siguiente paso cuando se consideran todas las filas, y de cualquier manera solo una fila regresa del SQL Server, por lo que tal vez no sea una gran diferencia en el gran esquema de las cosas .
fuente
un poco tarde, pero tenía la misma preocupación ...
Reformulando su código de la publicación original, desea el máximo del conjunto S definido por
Teniendo en cuenta tu último comentario
Puedo reformular su problema como: Desea el máximo de {0 + S}. Y parece que la solución propuesta con concat es semánticamente la correcta :-)
fuente
¿Por qué no algo más directo como:
fuente
Una diferencia interesante que parece destacarse es que, si bien FirstOrDefault y Take (1) generan el mismo SQL (de acuerdo con LINQPad, de todos modos), FirstOrDefault devuelve un valor, el valor predeterminado, cuando no hay filas coincidentes y Take (1) devuelve sin resultados ... al menos en LINQPad.
fuente
Solo para que todos sepan que está utilizando Linq to Entities, los métodos anteriores no funcionarán ...
Si intentas hacer algo como
Lanzará una excepción:
Sugeriría simplemente hacer
Y
FirstOrDefault
devolverá 0 si su lista está vacía.fuente
fuente
He encontrado un
MaxOrDefault
método de extensión. No hay mucho, pero su presencia en Intellisense es un recordatorio útil de queMax
en una secuencia vacía causará una excepción. Además, el método permite especificar el valor predeterminado si es necesario.fuente
Para Entity Framework y Linq to SQL podemos lograr esto definiendo un método de extensión que modifique un método
Expression
pasado aIQueryable<T>.Max(...)
:Uso:
La consulta generada es idéntica, funciona igual que una llamada normal al
IQueryable<T>.Max(...)
método, pero si no hay registros, devuelve un valor predeterminado de tipo T en lugar de arrojar una excepciónfuente
Acabo de tener un problema similar, mis pruebas unitarias pasaron usando Max () pero fallaron cuando se ejecutaban en una base de datos en vivo.
Mi solución fue separar la consulta de la lógica que se realizaba, no unirlas en una sola consulta.
Necesitaba una solución para trabajar en pruebas unitarias utilizando objetos Linq (en objetos Linq Max () funciona con valores nulos) y Linq-sql cuando se ejecuta en un entorno en vivo.
(Me burlo de Seleccionar () en mis pruebas)
¿Menos eficiente? Probablemente.
¿Me importa, siempre y cuando mi aplicación no se caiga la próxima vez? No
fuente