Esteban V Esteban V - 3 months ago 14
C# Question

Validation failed for one or more entities. See 'EntityValidationErrors' property for more details

I am having this error when seeding my database with code first approach.


Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.


To be honest I don't know how to check the content of the validation errors, Visual Studio shows me that its an array with 8 objects. so 8 validation Errors.

This was working with my previous model, but I made a few changes that I explain below:


  • I had an enum called Status, I changed it to a class called Status

  • I changed the class ApplicantsPositionHistory to have 2 foreign key to the same table



Excuse me for the long code, but I have to paste it all. The exception is thrown in the last line of the following code.

namespace Data.Model
{
public class Position
{
[DatabaseGenerated(System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.Identity)]
public int PositionID { get; set; }

[Required(ErrorMessage = "Position name is required.")]
[StringLength(20, MinimumLength = 3, ErrorMessage = "Name should not be longer than 20 characters.")]
[Display(Name = "Position name")]
public string name { get; set; }

[Required(ErrorMessage = "Number of years is required")]
[Display(Name = "Number of years")]
public int yearsExperienceRequired { get; set; }

public virtual ICollection<ApplicantPosition> applicantPosition { get; set; }
}

public class Applicant
{
[DatabaseGenerated(System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.Identity)]
public int ApplicantID { get; set; }

[Required(ErrorMessage = "Name is required")]
[StringLength(20, MinimumLength = 3, ErrorMessage="Name should not be longer than 20 characters.")]
[Display(Name = "First and LastName")]
public string name { get; set; }

[Required(ErrorMessage = "Telephone number is required")]
[StringLength(10, MinimumLength = 3, ErrorMessage = "Telephone should not be longer than 20 characters.")]
[Display(Name = "Telephone Number")]
public string telephone { get; set; }

[Required(ErrorMessage = "Skype username is required")]
[StringLength(10, MinimumLength = 3, ErrorMessage = "Skype user should not be longer than 20 characters.")]
[Display(Name = "Skype Username")]
public string skypeuser { get; set; }

public byte[] photo { get; set; }

public virtual ICollection<ApplicantPosition> applicantPosition { get; set; }
}

public class ApplicantPosition
{
[Key]
[Column("ApplicantID", Order = 0)]
public int ApplicantID { get; set; }

[Key]
[Column("PositionID", Order = 1)]
public int PositionID { get; set; }

public virtual Position Position { get; set; }

public virtual Applicant Applicant { get; set; }

[Required(ErrorMessage = "Applied date is required")]
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
[Display(Name = "Date applied")]
public DateTime appliedDate { get; set; }

[Column("StatusID", Order = 0)]
public int StatusID { get; set; }

public Status CurrentStatus { get; set; }

//[NotMapped]
//public int numberOfApplicantsApplied
//{
// get
// {
// int query =
// (from ap in Position
// where ap.Status == (int)Status.Applied
// select ap
// ).Count();
// return query;
// }
//}
}

public class Address
{
[StringLength(20, MinimumLength = 3, ErrorMessage = "Country should not be longer than 20 characters.")]
public string Country { get; set; }

[StringLength(20, MinimumLength = 3, ErrorMessage = "City should not be longer than 20 characters.")]
public string City { get; set; }

[StringLength(50, MinimumLength = 3, ErrorMessage = "Address should not be longer than 50 characters.")]
[Display(Name = "Address Line 1")]
public string AddressLine1 { get; set; }

[Display(Name = "Address Line 2")]
public string AddressLine2 { get; set; }
}

public class ApplicationPositionHistory
{
[DatabaseGenerated(System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.Identity)]
public int ApplicationPositionHistoryID { get; set; }

public ApplicantPosition applicantPosition { get; set; }

[Column("oldStatusID")]
public int oldStatusID { get; set; }

[Column("newStatusID")]
public int newStatusID { get; set; }

public Status oldStatus { get; set; }

public Status newStatus { get; set; }

[StringLength(500, MinimumLength = 3, ErrorMessage = "Comments should not be longer than 500 characters.")]
[Display(Name = "Comments")]
public string comments { get; set; }

[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
[Display(Name = "Date")]
public DateTime dateModified { get; set; }
}

public class Status
{
[DatabaseGenerated(System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.Identity)]
public int StatusID { get; set; }

[StringLength(20, MinimumLength = 3, ErrorMessage = "Status should not be longer than 20 characters.")]
[Display(Name = "Status")]
public string status { get; set; }
}
}




using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity;
using System.IO;

namespace Data.Model
{
public class HRContextInitializer : DropCreateDatabaseAlways<HRContext>
{
protected override void Seed(HRContext context)
{
#region Status
Status applied = new Status() { status = "Applied" };
Status reviewedByHR = new Status() { status = "Reviewed By HR" };
Status approvedByHR = new Status() { status = "Approved by HR" };
Status rejectedByHR = new Status() { status = "Rejected by HR" };
Status assignedToTechnicalDepartment = new Status() { status = "Assigned to Technical Department" };
Status approvedByTechnicalDepartment = new Status() { status = "Approved by Technical Department" };
Status rejectedByTechnicalDepartment = new Status() { status = "Rejected by Technical Department" };

Status assignedToGeneralManager = new Status() { status = "Assigned to General Manager" };
Status approvedByGeneralManager = new Status() { status = "Approved by General Manager" };
Status rejectedByGeneralManager = new Status() { status = "Rejected by General Manager" };

context.Status.Add(applied);
context.Status.Add(reviewedByHR);
context.Status.Add(approvedByHR);
context.Status.Add(rejectedByHR);
context.Status.Add(assignedToTechnicalDepartment);
context.Status.Add(approvedByTechnicalDepartment);
context.Status.Add(rejectedByTechnicalDepartment);
context.Status.Add(assignedToGeneralManager);
context.Status.Add(approvedByGeneralManager);
context.Status.Add(rejectedByGeneralManager);
#endregion

#region Position
Position netdeveloper = new Position() { name = ".net developer", yearsExperienceRequired = 5 };
Position javadeveloper = new Position() { name = "java developer", yearsExperienceRequired = 5 };
context.Positions.Add(netdeveloper);
context.Positions.Add(javadeveloper);
#endregion

#region Applicants
Applicant luis = new Applicant()
{
name = "Luis",
skypeuser = "le.valencia",
telephone = "0491732825",
photo = File.ReadAllBytes(@"C:\Users\LUIS.SIMBIOS\Documents\Visual Studio 2010\Projects\SlnHR\HRRazorForms\Content\pictures\1.jpg")
};

Applicant john = new Applicant()
{
name = "John",
skypeuser = "jo.valencia",
telephone = "3435343543",
photo = File.ReadAllBytes(@"C:\Users\LUIS.SIMBIOS\Documents\Visual Studio 2010\Projects\SlnHR\HRRazorForms\Content\pictures\2.jpg")
};

context.Applicants.Add(luis);
context.Applicants.Add(john);
#endregion

#region ApplicantsPositions
ApplicantPosition appicantposition = new ApplicantPosition()
{
Applicant = luis,
Position = netdeveloper,
appliedDate = DateTime.Today,
StatusID = 1
};

ApplicantPosition appicantposition2 = new ApplicantPosition()
{
Applicant = john,
Position = javadeveloper,
appliedDate = DateTime.Today,
StatusID = 1
};

context.ApplicantsPositions.Add(appicantposition);
context.ApplicantsPositions.Add(appicantposition2);
#endregion

context.SaveChanges(); --->> Error here
}
}
}

Answer

To be honest I dont know how to check the content of the validation errors, Visual Studio shows me that its an array with 8 objects. so 8 validation Errors.

Actually you should see the errors if you drill into that array in Visual studio during debug. But you can also catch the exception and then write out the errors to some logging store or the console:

try
{
    // Your code...
    // Could also be before try if you know the exception occurs in SaveChanges

    context.SaveChanges();
}
catch (DbEntityValidationException e)
{
    foreach (var eve in e.EntityValidationErrors)
    {
        Console.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
            eve.Entry.Entity.GetType().Name, eve.Entry.State);
        foreach (var ve in eve.ValidationErrors)
        {
            Console.WriteLine("- Property: \"{0}\", Error: \"{1}\"",
                ve.PropertyName, ve.ErrorMessage);
        }
    }
    throw;
}

EntityValidationErrors is a collection which represents the entities which couldn't be validated successfully, and the inner collection ValidationErrors per entity is a list of errors on property level.

These validation messages are usually helpful enough to find the source of the problem.

Edit

A few slight improvements:

The value of the offending property can be included in the inner loop like so:

        foreach (var ve in eve.ValidationErrors)
        {
            Console.WriteLine("- Property: \"{0}\", Value: \"{1}\", Error: \"{2}\"",
                ve.PropertyName,
                eve.Entry.CurrentValues.GetValue<object>(ve.PropertyName),
                ve.ErrorMessage);
        }

While debugging Debug.Write might be preferable over Console.WriteLine as it works in all kind of applications, not only console applications (thanks to @Bart for his note in the comments below).

For web applications that are in production and that use Elmah for exception logging it turned out to be very useful for me to create a custom exception and overwrite SaveChanges in order to throw this new exception.

The custom exception type looks like this:

public class FormattedDbEntityValidationException : Exception
{
    public FormattedDbEntityValidationException(DbEntityValidationException innerException) :
        base(null, innerException)
    {
    }

    public override string Message
    {
        get
        {
            var innerException = InnerException as DbEntityValidationException;
            if (innerException != null)
            {
                StringBuilder sb = new StringBuilder();

                sb.AppendLine();
                sb.AppendLine();
                foreach (var eve in innerException.EntityValidationErrors)
                {
                    sb.AppendLine(string.Format("- Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
                        eve.Entry.Entity.GetType().FullName, eve.Entry.State));
                    foreach (var ve in eve.ValidationErrors)
                    {
                        sb.AppendLine(string.Format("-- Property: \"{0}\", Value: \"{1}\", Error: \"{2}\"",
                            ve.PropertyName,
                            eve.Entry.CurrentValues.GetValue<object>(ve.PropertyName),
                            ve.ErrorMessage));
                    }
                }
                sb.AppendLine();

                return sb.ToString();
            }

            return base.Message;
        }
    }
}

And SaveChanges can be overwritten the following way:

public class MyContext : DbContext
{
    // ...

    public override int SaveChanges()
    {
        try
        {
            return base.SaveChanges();
        }
        catch (DbEntityValidationException e)
        {
            var newException = new FormattedDbEntityValidationException(e);
            throw newException;
        }
    }
}

A few remarks:

  • The yellow error screen that Elmah shows in the web interface or in the sent emails (if you have configured that) now displays the validation details directly at the top of the message.

  • Overwriting the Message property in the custom exception instead of overwriting ToString() has the benefit that the standard ASP.NET "Yellow screen of death (YSOD)" displays this message as well. In contrast to Elmah the YSOD apparently doesn't use ToString(), but both display the Message property.

  • Wrapping the original DbEntityValidationException as inner exception ensures that the original stack trace will still be available and is displayed in Elmah and the YSOD.

  • By setting a breakpoint on the line throw newException; you can simply inspect the newException.Message property as a text instead of drilling into the validation collections which is a bit awkward and doesn't seem to work easily for everyone (see comments below).