本文介绍了一个或多个实体的验证失败.有关更多详细信息,请参阅“EntityValidationErrors"属性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用代码优先方法为我的数据库做种时遇到此错误.

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

一个或多个实体的验证失败.有关更多详细信息,请参阅EntityValidationErrors"属性.

老实说,我不知道如何检查验证错误的内容.Visual Studio 显示它是一个包含 8 个对象的数组,因此有 8 个验证错误.

To be honest I don't know how to check the content of the validation errors. Visual Studio shows me that it's 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:

  • 我有一个名为 Status 的枚举,我将其更改为名为 Status 的类
  • 我将类申请者位置历史更改为具有 2 个指向同一个表的外键

请原谅我的代码太长,但我必须全部粘贴.在以下代码的最后一行抛出异常.

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:UsersLUIS.SIMBIOSDocumentsVisual Studio 2010ProjectsSlnHRHRRazorFormsContentpictures1.jpg")
            };

            Applicant john = new Applicant()
            {
                name = "John",
                skypeuser = "jo.valencia",
                telephone = "3435343543",
                photo = File.ReadAllBytes(@"C:UsersLUIS.SIMBIOSDocumentsVisual Studio 2010ProjectsSlnHRHRRazorFormsContentpictures2.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
        }
    }
}

推荐答案

实际上,如果在调试期间在 Visual Studio 中钻取该数组,您应该会看到错误.但是您也可以捕获异常,然后将错误写出到某些日志记录存储或控制台:

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 是代表无法成功验证的实体的集合,每个实体的内部集合 ValidationErrors 是属性级别的错误列表.

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.

编辑

一些细微的改进:

违规属性的可以像这样包含在内部循环中:

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

虽然调试 Debug.Write 可能比 Console.WriteLine 更可取,因为它适用于所有类型的应用程序,而不仅仅是控制台应用程序(感谢@Bart 的注释在下面的评论中).

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).

对于生产中的 Web 应用程序并使用 Elmah 进行异常记录,结果证明对我来说创建自定义异常并按顺序覆盖 SaveChanges 非常有用抛出这个新异常.

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;
        }
    }
}

SaveChanges 可以通过以下方式覆盖:

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;
        }
    }
}

几点说明:

  • Elmah 在 Web 界面或发送的电子邮件(如果您已配置)中显示的黄色错误屏幕现在直接在消息顶部显示验证详细信息.

  • 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.

覆盖自定义异常中的 Message 属性而不是覆盖 ToString() 的好处是标准 ASP.NET 黄屏死机 (YSOD)"也会显示此消息.与 Elmah 相比,YSOD 显然不使用 ToString(),但都显示 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.

将原始 DbEntityValidationException 包装为内部异常可确保原始堆栈跟踪仍然可用并显示在 Elmah 和 YSOD 中.

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.

通过在 throw newException; 行上设置断点,您可以简单地将 newException.Message 属性作为文本进行检查,而不是深入到验证集合中有点尴尬,似乎并不适合所有人(见下面的评论).

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).

这篇关于一个或多个实体的验证失败.有关更多详细信息,请参阅“EntityValidationErrors"属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-18 20:31