La validation a échoué pour une ou plusieurs entités lors de l'enregistrement des modifications dans la base de données SQL Server à l'aide d'Entity Framework

337

Je veux enregistrer ma modification dans la base de données et j'utilise Entity FrameWork Code-First dans ASP.NET MVC 3 / C # mais je reçois des erreurs. Dans ma classe Event, j'ai des types de données DateTime et TimeSpan mais dans ma base de données, j'ai respectivement la date et l'heure. Serait-ce la raison? Comment convertir le type de données approprié dans le code avant d'enregistrer les modifications dans la base de données.

public class Event
{
    public int EventId { get; set; }
    public int CategoryId { get; set; }
    public int PlaceId { get; set; }
    public string Title { get; set; }
    public decimal Price { get; set; }
    public DateTime EventDate { get; set; }
    public TimeSpan StartTime { get; set; }
    public TimeSpan EndTime { get; set; }
    public string Description { get; set; }
    public string EventPlaceUrl { get; set; }
    public Category Category { get; set; }
    public Place Place { get; set; }
}

Méthode dans le contrôleur >>>> Problème à storeDB.SaveChanges ();

// POST: /EventManager/Edit/386        
[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
    var theEvent = storeDB.Events.Find(id);

    if (TryUpdateModel(theEvent))
    {
        storeDB.SaveChanges();
        return RedirectToAction("Index");
    }
    else
    {
        ViewBag.Categories = storeDB.Categories.OrderBy(g => g.Name).ToList();
        ViewBag.Places = storeDB.Places.OrderBy(a => a.Name).ToList();
        return View(theEvent);
    }
}

avec

public class EventCalendarEntities : DbContext
{
    public DbSet<Event> Events { get; set; }
    public DbSet<Category> Categories { get; set; }
    public DbSet<Place> Places { get; set; } 
}

Base de données SQL Server 2008 R2 / T-SQL

EventDate (Datatype = date)  
StartTime (Datatype = time)  
EndTime (Datatype = time)  

Formulaire Http

EventDate (Datatype = DateTime) e.g. 4/8/2011 12:00:00 AM  
StartTime (Datatype = Timespan/time not sure) e.g. 08:30:00  
EndTime (Datatype = Timespan/time not sure) e.g. 09:00:00  

Erreur serveur dans l'application '/'

La validation a échoué pour une ou plusieurs entités. Voir la propriété 'EntityValidationErrors' pour plus de détails.

Description: une exception non gérée s'est produite lors de l'exécution de la demande Web en cours. Veuillez consulter la trace de la pile pour plus d'informations sur l'erreur et son origine dans le code.

Détails de l'exception: System.Data.Entity.Validation.DbEntityValidationException: la validation a échoué pour une ou plusieurs entités. Voir la propriété 'EntityValidationErrors' pour plus de détails.

Erreur source:

Line 75:             if (TryUpdateModel(theEvent))
Line 76:             {
Line 77:                 storeDB.SaveChanges();
Line 78:                 return RedirectToAction("Index");
Line 79:             }

Fichier source: C: \ sep \ MvcEventCalendar \ MvcEventCalendar \ Controllers \ EventManagerController.cs Ligne: 77

Trace de la pile:

[DbEntityValidationException: la validation a échoué pour une ou plusieurs entités. Voir la propriété 'EntityValidationErrors' pour plus de détails.]

user522767
la source
8
l'un de vos champs obligatoires a probablement une valeur nulle. Comme EventDate, StartTime, Price, Category etc
Daveo
Avez-vous inspecté les variables de formulaire publiées pour vous assurer que chacune correspond au type défini dans la base de données? Ou comme ce que Daveo a dit, l'une des valeurs de formulaire requises est manquante ...
timothyclifford
Toutes les variables de formulaire publiées ne correspondent pas au type défini par la base de données. J'ai utilisé la date et l'heure dans la base de données mais il n'y a pas d'équivalent de type de données direct dans .NET. Par conséquent, j'ai utilisé DateTime et TimeSpan. Maintenant, je dois convertir les deux en date et heure respectivement.
user522767
La source d'erreur pour moi était un champ de chaîne contenant plus de 30 caractères et la taille de mon champ dans la base de données était de 30.
Mojtaba

Réponses:

840

Vous pouvez extraire toutes les informations du DbEntityValidationExceptionavec le code suivant (vous devez ajouter les espaces de noms: System.Data.Entity.Validationet System.Diagnosticsà votre usingliste):

catch (DbEntityValidationException dbEx)
{
    foreach (var validationErrors in dbEx.EntityValidationErrors)
    {
        foreach (var validationError in validationErrors.ValidationErrors)
        {
            Trace.TraceInformation("Property: {0} Error: {1}", 
                                    validationError.PropertyName, 
                                    validationError.ErrorMessage);
        }
    }
}
Praveen Prasad
la source
7
Vous pouvez également obtenir cette erreur si des données de départ ne satisfont pas pleinement aux règles d'attribut de modèle (comme Required). J'ai ajouté une réponse avec un peu plus d'informations.
dan richardson
8
La réponse pourrait être améliorée en expliquant où vous pouvez réellement voir la sortie de trace.
mkataja
1
J'ai trouvé cet article msdn sur la trace très utile
Borik
2
Vous monsieur, avez gagné Internet.
Leandro
8
La sortie Trace est visible dans la fenêtre Sortie. Cliquez sur Déboguer en haut -> Windows -> Sortie
reggaeguitar
249

Aucun changement de code requis:

Pendant que vous êtes en mode débogage dans le catch {...}bloc, ouvrez la fenêtre "QuickWatch" ( Ctrl+ Alt+ Q) et collez-y:

((System.Data.Entity.Validation.DbEntityValidationException)ex).EntityValidationErrors

ou:

((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors

Si vous n'êtes pas dans un try / catch ou n'avez pas accès à l'objet d'exception.

Cela vous permettra de forer dans l' ValidationErrorsarbre. C'est le moyen le plus simple que j'ai trouvé pour obtenir un aperçu instantané de ces erreurs.

GONeale
la source
6
Vous mon ami êtes un génie, vous m'avez aidé à retrouver l'erreur ET que je recevais, merci!
Mark Kram
4
Pas de problème :) Mais pas un génie, j'adore QuickWatch :)
GONeale
4
Je viens de mettre à jour la réponse pour ceux qui n'ont pas accès à l'objet / variable d'exception.
GONeale
7
Chaque fois que je reçois cette exception, je recherche l'erreur, puis je viens ici (2ème résultat chez Google), je trouve ce post et j'utilise la deuxième solution dans le panneau Debug> Watch. J'ai fait des dizaines de fois. Merci GONeale.
Ata S.
1
Suis-je le seul à obtenir "Le nom '$ exception' n'existe pas dans le contexte actuel" lors de l'exécution de la deuxième requête?
Alex Klaus
37

Dans le cas où vous avez des classes avec les mêmes noms de propriété, voici une petite extension de la réponse de Praveen:

 catch (DbEntityValidationException dbEx)
 {
    foreach (var validationErrors in dbEx.EntityValidationErrors)
    {
       foreach (var validationError in validationErrors.ValidationErrors)
       {
          Trace.TraceInformation(
                "Class: {0}, Property: {1}, Error: {2}",
                validationErrors.Entry.Entity.GetType().FullName,
                validationError.PropertyName,
                validationError.ErrorMessage);
       }
    }
 }
Tony
la source
22

Pour améliorer Praveen et Tony, j'utilise un remplacement:

public partial class MyDatabaseEntities : DbContext
{
    public override int SaveChanges()
    {
        try
        {
            return base.SaveChanges();
        }
        catch (DbEntityValidationException dbEx)
        {
            foreach (var validationErrors in dbEx.EntityValidationErrors)
            {
                foreach (var validationError in validationErrors.ValidationErrors)
                {
                    Trace.TraceInformation("Class: {0}, Property: {1}, Error: {2}",
                        validationErrors.Entry.Entity.GetType().FullName,
                        validationError.PropertyName,
                        validationError.ErrorMessage);
                }
            }

            throw;  // You can also choose to handle the exception here...
        }
    }
}
Bolt Thunder
la source
6

Cette implémentation enveloppe une exception d'exception à une exception avec un texte détaillé. Il gère DbEntityValidationException, DbUpdateException, les datetime2erreurs de gamme (MS SQL), et comprennent la clé de l' entité non valide dans le message (utile lorsque de nombreuses entités savind à un SaveChangesappel).

Tout d'abord, remplacez SaveChangesdans la classe DbContext:

public class AppDbContext : DbContext
{
    public override int SaveChanges()
    {
        try
        {
            return base.SaveChanges();
        }
        catch (DbEntityValidationException dbEntityValidationException)
        {
            throw ExceptionHelper.CreateFromEntityValidation(dbEntityValidationException);
        }
        catch (DbUpdateException dbUpdateException)
        {
            throw ExceptionHelper.CreateFromDbUpdateException(dbUpdateException);
        }
    }   

    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken)
    {
        try
        {
            return await base.SaveChangesAsync(cancellationToken);
        }
        catch (DbEntityValidationException dbEntityValidationException)
        {
            throw ExceptionHelper.CreateFromEntityValidation(dbEntityValidationException);
        }
        catch (DbUpdateException dbUpdateException)
        {
            throw ExceptionHelper.CreateFromDbUpdateException(dbUpdateException);
        }
    }

Classe ExceptionHelper:

public class ExceptionHelper
{
    public static Exception CreateFromEntityValidation(DbEntityValidationException ex)
    {
        return new Exception(GetDbEntityValidationMessage(ex), ex);
    }

    public static string GetDbEntityValidationMessage(DbEntityValidationException ex)
    {
        // Retrieve the error messages as a list of strings.
        var errorMessages = ex.EntityValidationErrors
            .SelectMany(x => x.ValidationErrors)
            .Select(x => x.ErrorMessage);

        // Join the list to a single string.
        var fullErrorMessage = string.Join("; ", errorMessages);

        // Combine the original exception message with the new one.
        var exceptionMessage = string.Concat(ex.Message, " The validation errors are: ", fullErrorMessage);
        return exceptionMessage;
    }

    public static IEnumerable<Exception> GetInners(Exception ex)
    {
        for (Exception e = ex; e != null; e = e.InnerException)
            yield return e;
    }

    public static Exception CreateFromDbUpdateException(DbUpdateException dbUpdateException)
    {
        var inner = GetInners(dbUpdateException).Last();
        string message = "";
        int i = 1;
        foreach (var entry in dbUpdateException.Entries)
        {
            var entry1 = entry;
            var obj = entry1.CurrentValues.ToObject();
            var type = obj.GetType();
            var propertyNames = entry1.CurrentValues.PropertyNames.Where(x => inner.Message.Contains(x)).ToList();
            // check MS SQL datetime2 error
            if (inner.Message.Contains("datetime2"))
            {
                var propertyNames2 = from x in type.GetProperties()
                                        where x.PropertyType == typeof(DateTime) ||
                                            x.PropertyType == typeof(DateTime?)
                                        select x.Name;
                propertyNames.AddRange(propertyNames2);
            }

            message += "Entry " + i++ + " " + type.Name + ": " + string.Join("; ", propertyNames.Select(x =>
                string.Format("'{0}' = '{1}'", x, entry1.CurrentValues[x])));
        }
        return new Exception(message, dbUpdateException);
    }
}
Sel
la source
Vous devez attendre l'appel SaveChangesAsync, si vous souhaitez gérer l'exception.
Arnab Chakraborty
Merci, Arnab Chakraborty ! Réponse corrigée
Sel
5

Ce code a aidé à trouver mon problème lorsque j'ai eu un problème avec mes erros Entity VAlidation. Cela m'a dit le problème exact avec ma définition d'entité. Essayez le code suivant où vous devez couvrir storeDB.SaveChanges (); en suivant essayez catch block.

  try
{
         if (TryUpdateModel(theEvent))
         {
             storeDB.SaveChanges();
             return RedirectToAction("Index");
         }
}
catch (System.Data.Entity.Validation.DbEntityValidationException dbEx)
{
    Exception raise = dbEx;
    foreach (var validationErrors in dbEx.EntityValidationErrors)
    {
        foreach (var validationError in validationErrors.ValidationErrors)
        {
            string message = string.Format("{0}:{1}", 
                validationErrors.Entry.Entity.ToString(),
                validationError.ErrorMessage);
            // raise a new exception nesting
            // the current instance as InnerException
            raise = new InvalidOperationException(message, raise);
        }
    }
    throw raise;
}
Рахул Маквана
la source
4

J'obtenais cette erreur aujourd'hui et je n'ai pas pu le résoudre pendant un certain temps, mais je me suis rendu compte que c'était après avoir ajouté quelques RequireAttributes à mes modèles et que certaines données d'amorçage de développement ne remplissaient pas tous les champs requis.
Il suffit donc de noter que si vous obtenez cette erreur lors de la mise à jour de la base de données via une sorte de stratégie d'initialisation, DropCreateDatabaseIfModelChangesvous devez vous assurer que vos données de départ remplissent et satisfont tous les attributs de validation des données de modèle .

Je sais que cela est légèrement différent du problème de la question, mais c'est une question populaire, alors j'ai pensé ajouter un peu plus à la réponse pour les autres personnes ayant le même problème que moi.
J'espère que cela aide les autres :)

dan richardson
la source
4

Je pense que l'ajout de try / catch pour chaque SaveChanges()opération n'est pas une bonne pratique, il est préférable de centraliser ceci:

Ajoutez cette classe à la DbContextclasse principale :

public override int SaveChanges()
{
    try
    {
        return base.SaveChanges();
    }
    catch (DbEntityValidationException ex)
    {
        string errorMessages = string.Join("; ", ex.EntityValidationErrors.SelectMany(x => x.ValidationErrors).Select(x => x.ErrorMessage));
        throw new DbEntityValidationException(errorMessages);
    }
}

Cela écrasera la SaveChanges()méthode de votre contexte et vous obtiendrez une liste séparée par des virgules contenant toutes les erreurs de validation d'entité.

cela peut également être amélioré, pour consigner les erreurs dans l'environnement de production, au lieu de simplement lancer une erreur.

j'espère que cela vous sera utile.

Chtiwi Malek
la source
Où puis-je trouver la "classe DbContext"? Je suis un peu nouveau dans tous ces trucs MVC. J'utilise MVC 5 et Entity Framework 6. Je ne sais tout simplement pas ce que le nom voudrait dans mon explorateur de solutions.
JustJohn
@JustJohn c'est dans votre modèle de base de données (fichier de classe), si vous ne savez pas où il se trouve, vous pouvez faire une recherche complète du mot-clé par DbContextrapport à votre projet.
Chtiwi Malek,
1
J'aime le mieux cette approche car c'est une solution universelle dans l'application et révèle facilement ce qui se trouve sous la surface sans beaucoup changer pour les autres modules du système
Korayem
3

Voici une extension de l'extension de Tony ... :-)

Pour Entity Framework 4.x, si vous souhaitez obtenir le nom et la valeur du champ clé afin de savoir quelle instance d'entité (enregistrement DB) a le problème, vous pouvez ajouter ce qui suit. Cela permet d'accéder aux membres de classe ObjectContext les plus puissants à partir de votre objet DbContext.

// Get the key field name & value.
// This assumes your DbContext object is "_context", and that it is a single part key.
var e = ((IObjectContextAdapter)_context).ObjectContext.ObjectStateManager.GetObjectStateEntry(validationErrors.Entry.Entity);
string key = e.EntityKey.EntityKeyValues[0].Key;
string val = e.EntityKey.EntityKeyValues[0].Value;
Martin_W
la source
3

Je n'aime pas les exceptions, j'ai enregistré OnSaveChanges et je l'ai

var validationErrors = model.GetValidationErrors();

var h = validationErrors.SelectMany(x => x.ValidationErrors
                                          .Select(f => "Entity: " 
                                                      +(x.Entry.Entity) 
                                                      + " : " + f.PropertyName 
                                                      + "->" + f.ErrorMessage));
Mickey Perlstein
la source
Que voulez-vous dire par "J'ai enregistré les changements OnSave"?
Akira Yamamoto
Je me suis connecté aux OnSaveChanges sur le contexte comme les autres, je n'ai tout simplement pas atteint le stade d'exception. \
Mickey Perlstein
2

Cette erreur se produit également lorsque vous essayez d'enregistrer une entité contenant des erreurs de validation. Un bon moyen de provoquer cela est d'oublier de vérifier ModelState.IsValid avant d'enregistrer dans votre base de données.

laylarenee
la source
2

Assurez-vous que si vous avez nvarchar (50) dans la ligne DB, vous n'essayez pas d'y insérer plus de 50 caractères. Erreur stupide mais m'a pris 3 heures pour le comprendre.

user4030144
la source
1

Cela peut être dû au nombre maximal de caractères autorisé pour une colonne spécifique, comme dans sql un champ peut avoir le type de données suivant, nvarchar(5)mais le nombre de caractères saisis par l'utilisateur est supérieur au nombre spécifié, d'où l'erreur se produit.

lokopi
la source
1

J'ai rencontré le même problème il y a quelques jours lors de la mise à jour de la base de données. Dans mon cas, il y avait peu de nouvelles colonnes non nullables ajoutées pour la maintenance qui n'étaient pas fournies dans le code qui provoque l'exception. J'ai compris ces champs et fourni des valeurs pour eux et sa résolution.

Jhabar
la source
0

Merci pour vos réponses, ça m'aide beaucoup. comme je code dans Vb.Net, ce code Bolt pour Vb.Net

Try
   Return MyBase.SaveChanges()
Catch dbEx As Validation.DbEntityValidationException
   For Each [error] In From validationErrors In dbEx.EntityValidationErrors
                       From validationError In validationErrors.ValidationErrors
                       Select New With { .PropertyName = validationError.PropertyName,
                                         .ErrorMessage = validationError.ErrorMessage,
                                         .ClassFullName = validationErrors.Entry.Entity
                                                                    .GetType().FullName}

        Diagnostics.Trace.TraceInformation("Class: {0}, Property: {1}, Error: {2}",
                                           [error].ClassFullName,
                                           [error].PropertyName,
                                           [error].ErrorMessage)
   Next
   Throw
End Try
Exatex
la source
0

cela peut être provoqué par une propriété qui n'est pas remplie par le modèle .. au lieu de cela, elle est remplie par le contrôleur .. ce qui peut provoquer cette erreur .. la solution à cela consiste à affecter la propriété avant d'appliquer la validation ModelState. et cette seconde Assomption est. vous avez peut-être déjà des données dans votre base de données et essayez de les mettre à jour mais vous les récupérez maintenant.

Muhammad Mustafa
la source
0

Dans mon cas, j'ai un chemin de nom de colonne de table dont le type de données que j'ai défini était varchar (200). Après l'avoir mis à jour vers nvarchar (max), j'ai supprimé la table d'edmx, puis j'ai à nouveau ajouté la table et elle s'est réveillée correctement pour moi.

Saket Singh
la source