La conversión al tipo de valor 'Int32' falló porque el valor materializado es nulo


192

tengo el siguiente código. Recibo un error:

"El tipo de conversión al valor 'Int32' falló porque el valor materializado es nulo. El parámetro genérico del tipo de resultado o la consulta deben usar un tipo anulable".

cuando la tabla CreditHistory no tiene registros.

var creditsSum = (from u in context.User
                  join ch in context.CreditHistory on u.ID equals ch.UserID                                        
                  where u.ID == userID
                  select ch.Amount).Sum();

¿Cómo puedo modificar la consulta para aceptar valores nulos?

Respuestas:


328

Una consulta de linq a sql no se ejecuta como código, sino que se traduce a SQL. A veces se trata de una "abstracción permeable" que produce un comportamiento inesperado.

Uno de estos casos es el manejo nulo, donde puede haber nulos inesperados en diferentes lugares. ...DefaultIfEmpty(0).Sum(0)puede ayudar en este caso (bastante simple), donde puede no haber elementos y SUMretornos de sql nullmientras que c # espera 0.

Un enfoque más general es usar el ??cual se traducirá COALESCEcuando exista el riesgo de que el SQL generado devuelva un nulo inesperado:

var creditsSum = (from u in context.User
              join ch in context.CreditHistory on u.ID equals ch.UserID                                        
              where u.ID == userID
              select (int?)ch.Amount).Sum() ?? 0;

Esto se aplica primero int?para decirle al compilador de C # que esta expresión puede volver null, aunque Sum()devuelva un int. Luego usamos el ??operador normal para manejar elnull caso.

Con base en esta respuesta, escribí una publicación de blog con detalles para LINQ to SQL y LINQ to Entities.


3
gracias Anders, la solución con DefaultIfEmpty (0) .Sum () funciona bien para mí. También probé la segunda solución con (int?) ... ?? 0 ..., pero arroja la misma excepción que antes ..
zosim

Finalmente lo probé y lo ajusté, así que ahora la segunda versión también funciona.
Anders Abel

1
Sum () y otras funciones agregadas devolverán nulo cuando se apliquen a un conjunto de datos vacío. Contrariamente a su definición, en realidad devuelven una versión anulable del tipo subyacente.
Suncat2000

2
@recursive: su ejemplo es LINQ-to-Objects, no LINQ-to-SQL (o LINQ-to-Entities). Sus proveedores de datos subyacentes hacen que se comporten de manera diferente.
Suncat2000

Esta fue una buena idea. Actualicé mi objeto de devolución para que tuviera propiedades anulables y eso funcionó de maravilla.
Kremena Lalova

8

Para permitir un Amountcampo anulable , solo use el operador de fusión nula para convertir los valores nulos a 0.

var creditsSum = (from u in context.User
              join ch in context.CreditHistory on u.ID equals ch.UserID                                        
              where u.ID == userID
              select ch.Amount ?? 0).Sum();

1
cuando uso su consejo, el compilador dice: Operador '??' no se puede aplicar a operandos de tipo 'int' e 'int'. ¿Olvidé algo?
zosim

@zosim: Esa es la razón para agregar el elenco int?primero.
Anders Abel

He agregado int ?, pero la misma excepción. Te lo agradeceré cuando tengas dev env. para verificar qué está mal en esta sintaxis.
zosim

1
@zosim: No entiendo el problema. Si Amountes un int, entonces ya estamos seguros de que no puede ser nulo, y la fusión es innecesaria. Si está recibiendo el error que dijo, entonces Amountno es anulable, es solo un caso int, en cuyo caso tal vez necesite cambiar su columna linq2sql dbml en el diseñador para permitir valores nulos.
recursivo el

1
@recursive: la cantidad es int, está bien. La cantidad ya tiene valor. Creo que el error anterior se produjo porque la tabla CreditHistory está vacía. Tengo un registro en la tabla Usuario y 0 registros en la tabla CreditHistory y se produjo un error. Cuando uso DefaultIfEmpty (0) .Sum () funciona bien, pero con ?? 0 arroja error. Mi otra pregunta es ¿cuál es la mejor práctica en este caso? DefaultIfEmpty (0)? gracias
zosim

4

Está utilizando una aggregatefunción que no consigue que los elementos realicen una acción, debe verificar que la consulta linq esté dando algunos resultados de la siguiente manera:

var maxOrderLevel =sdv.Any()? sdv.Max(s => s.nOrderLevel):0

11
Esto haría que sdv se ejecutara dos veces. Que no es lo que quieres para
IQueryables

4

Tenía este mensaje de error cuando intentaba seleccionar desde una vista.

El problema era que la vista recientemente había ganado algunas filas nulas nuevas (en la columna SubscriberId), y no se había actualizado en EDMX (primero la base de datos EF).

La columna tenía que ser de tipo Nullable para que funcionara.

var dealer = Context.Dealers.Where (x => x.dealerCode == dealerCode) .FirstOrDefault ();

Antes de ver la actualización:

public int SubscriberId { get; set; }

Después de actualizar la vista:

public Nullable<int> SubscriberId { get; set; }

Eliminar y agregar la vista de nuevo en EDMX funcionó.

Espero que ayude a alguien.


Este también fue mi problema y mi respuesta
Simon Nicholls el

4

He usado este código y responde correctamente, solo el valor de salida es anulable.

var packesCount = await botContext.Sales.Where(s => s.CustomerId == cust.CustomerId && s.Validated)
                                .SumAsync(s => (int?)s.PackesCount);
                            if(packesCount != null)
                            {
                                // your code
                            }
                            else
                            {
                                // your code
                            }

1

Veo que esta pregunta ya está respondida. Pero si desea que se divida en dos declaraciones, puede considerarse lo siguiente.

var credits = from u in context.User
              join ch in context.CreditHistory 
                  on u.ID equals ch.UserID                                        
              where u.ID == userID
              select ch;

var creditSum= credits.Sum(x => (int?)x.Amount) ?? 0;

0

Tengo este error en Entity Framework 6 con este código en tiempo de ejecución:

var fileEventsSum = db.ImportInformations.Sum(x => x.FileEvents)

Actualización de LeandroSoares:

Use esto para ejecución única:

var fileEventsSum = db.ImportInformations.Sum(x => (int?)x.FileEvents) ?? 0

Original:

Cambió a esto y luego funcionó:

var fileEventsSum = db.ImportInformations.Any() ? db.ImportInformations.Sum(x => x.FileEvents) : 0;

1
¿No lo ejecutaría dos veces?
nawfal

Esta no es una buena respuesta. Se recuperará de la base de datos dos veces.
Leandro Soares

@nawfal Esto es cierto, pero es mucho mejor que un error de tiempo de ejecución. Absolutamente puede usar linq-to-sql pero con lambda es más difícil. Por supuesto, puede detectar la excepción, pero creo que esa solución es peor que dos ejecuciones.
Ogglas

@LeandroSoares ver comentario anterior
Ogglas

1
@LeandroSoares Nice one! Actualicé mi respuesta y usé el código que proporcionó y la descripción de por qué usarlo.
Ogglas

0

También estaba enfrentando el mismo problema y lo resolví haciendo que la columna se anulara usando "?" operador.

Sequnce = db.mstquestionbanks.Where(x => x.IsDeleted == false && x.OrignalFormID == OriginalFormIDint).Select(x=><b>(int?)x.Sequence</b>).Max().ToString();

A veces se devuelve nulo.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.