La conversion en valeur de type «Int32» a échoué car la valeur matérialisée est null

193

J'ai le code suivant. J'obtiens une erreur:

"La conversion en valeur de type 'Int32' a échoué car la valeur matérialisée est null. Soit le paramètre générique du type de résultat, soit la requête doit utiliser un type Nullable."

lorsque la table CreditHistory n'a aucun enregistrement.

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();

Comment puis-je modifier la requête pour accepter des valeurs nulles?

zosim
la source

Réponses:

330

Une requête linq-to-sql n'est pas exécutée en tant que code, mais plutôt traduite en SQL. Il s'agit parfois d'une "abstraction qui fuit" qui produit un comportement inattendu.

Un tel cas est la gestion des null, où il peut y avoir des valeurs nulles inattendues à différents endroits. ...DefaultIfEmpty(0).Sum(0)peut aider dans ce cas (assez simple), où il pourrait n'y avoir aucun élément et les SUMretours de sqlnull alors que c # attend 0.

Une approche plus générale consiste à utiliser ??qui sera traduit COALESCEchaque fois qu'il y a un risque que le SQL généré renvoie un null inattendu:

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;

Cela commence int?par indiquer au compilateur C # que cette expression peut effectivement retourner null, même si elle Sum()renvoie un int. Ensuite, nous utilisons l' ??opérateur normal pour gérer le nullcas.

Sur la base de cette réponse, j'ai écrit un article de blog avec des détails sur LINQ to SQL et LINQ to Entities.

Anders Abel
la source
3
merci Anders, la solution avec DefaultIfEmpty (0) .Sum () fonctionne très bien pour moi. J'ai aussi essayé la deuxième solution avec (int?) ... ?? 0 ..., mais il lève la même exception qu'avant ..
zosim
J'ai finalement essayé de tester cela et de l'ajuster, alors maintenant la deuxième version fonctionne aussi.
Anders Abel
1
Sum () et d'autres fonctions d'agrégation renverront null lorsqu'elles sont appliquées à un ensemble de données vide. Contrairement à leur définition, ils renvoient en réalité une version nullable du type sous-jacent.
Suncat2000
2
@recursive: votre exemple est LINQ-to-Objects, pas LINQ-to-SQL (ou LINQ-to-Entities). Leurs fournisseurs de données sous-jacents les font se comporter différemment.
Suncat2000
C'était une bonne idée. J'ai mis à jour mon objet de retour pour avoir des propriétés nullables et cela a fonctionné comme un charme.
Kremena Lalova
8

Pour autoriser un Amountchamp Nullable , utilisez simplement l'opérateur de fusion NULL pour convertir les NULL en 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();
récursif
la source
1
quand j'utilise votre astuce, le compilateur dit: Opérateur '??' ne peut pas être appliqué aux opérandes de type 'int' et 'int'. ai-je oublié quelque chose?
zosim
@zosim: C'est la raison pour laquelle ajouter le casting en int?premier.
Anders Abel
j'ai ajouté int?, mais la même exception. Je vous en serai reconnaissant, lorsque vous aurez dev env. pour vérifier ce qui ne va pas dans cette syntaxe.
zosim
1
@zosim: Je ne comprends pas le problème. Si Amountest un int, alors nous sommes déjà sûrs qu'il ne peut pas être nul, et la fusion n'est pas nécessaire. Si vous obtenez l'erreur que vous avez dite, alors Amountn'est pas nullable, c'est juste un int, auquel cas vous devrez peut-être changer votre colonne dbml linq2sql dans le concepteur pour autoriser les valeurs nulles.
récursif le
1
@recursive: Le montant est entier, c'est OK. Le montant a déjà une valeur. Je pense que l'erreur ci-dessus s'est produite parce que la table CreditHistory est vide. J'ai un enregistrement dans la table User et 0 enregistrements dans la table CreditHistory et une erreur s'est produite. Quand j'utilise DefaultIfEmpty (0) .Sum () cela fonctionne bien, mais avec ?? 0 il jette une erreur. Ma autre question est quelle est la meilleure pratique dans ce cas? DefaultIfEmpty (0)? merci
zosim
4

Vous utilisez une aggregatefonction qui n'obtient pas les éléments pour effectuer l'action, vous devez vérifier que la requête linq donne un résultat comme ci-dessous:

var maxOrderLevel =sdv.Any()? sdv.Max(s => s.nOrderLevel):0
Ashwini
la source
11
Cela ferait exécuter sdv deux fois. Ce qui n'est pas ce que vous voulez pour IQueryables
Ody
4

Eu ce message d'erreur lorsque j'essayais de sélectionner dans une vue.

Le problème était que la vue avait récemment gagné de nouvelles lignes nulles (dans la colonne SubscriberId) et n'avait pas été mise à jour dans EDMX (base de données EF en premier).

La colonne devait être de type Nullable pour que cela fonctionne.

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

Avant l'actualisation de la vue:

public int SubscriberId { get; set; }

Après l'actualisation de la vue:

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

La suppression et l'ajout de la vue dans EDMX ont fonctionné.

J'espère que ça aide quelqu'un.

l'amour en direct
la source
C'était aussi mon problème et ma réponse
Simon Nicholls
4

J'ai utilisé ce code et il répond correctement, seule la valeur de sortie est nullable.

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
                            }
MohammadSoori
la source
1

Je vois que cette question est déjà répondue. Mais si vous voulez qu'il soit divisé en deux instructions, vous pouvez envisager de suivre.

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;
LCJ
la source
0

Vous avez cette erreur dans Entity Framework 6 avec ce code au moment de l'exécution:

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

Mise à jour de LeandroSoares:

Utilisez ceci pour une exécution unique:

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

Original:

Changé à ceci et puis cela a fonctionné:

var fileEventsSum = db.ImportInformations.Any() ? db.ImportInformations.Sum(x => x.FileEvents) : 0;
Ogglas
la source
1
Cela ne l'exécuterait-il pas deux fois?
nawfal
Ce n'est pas une bonne réponse. Il récupérera deux fois de la base de données.
Leandro Soares
@nawfal C'est vrai mais c'est bien mieux qu'une erreur d'exécution. Vous pouvez absolument utiliser linq-to-sql mais avec lambda c'est plus difficile. Vous pouvez bien sûr attraper l'exception mais je pense que la solution est pire que deux exécutions.
Ogglas
@LeandroSoares voir le commentaire ci-dessus
Ogglas
1
@LeandroSoares Belle! J'ai mis à jour ma réponse et utilisé le code que vous avez fourni et une description des raisons de l'utiliser à la place.
Ogglas
0

J'étais également confronté au même problème et résolu en rendant la colonne comme nullable en utilisant "?" opérateur.

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

Parfois, null est renvoyé.

user3820036
la source