DbEntityValidationException - ฉันจะบอกได้อย่างง่ายดายว่าอะไรทำให้เกิดข้อผิดพลาดได้อย่างไร


217

ฉันมีโครงการที่ใช้ Entity Framework ในขณะที่โทรหาSaveChangesฉันDbContextฉันได้รับข้อยกเว้นต่อไปนี้:

System.Data.Entity.Validation.DbEntityValidationException: การตรวจสอบล้มเหลวสำหรับเอนทิตีหนึ่งรายการขึ้นไป ดูคุณสมบัติ 'EntityValidationErrors' สำหรับรายละเอียดเพิ่มเติม

นี่คือทั้งหมดที่ดีและสวยงาม แต่ฉันไม่ต้องการแนบดีบักเกอร์ทุกครั้งที่เกิดข้อยกเว้นนี้ ยิ่งกว่านั้นในสภาพแวดล้อมการผลิตฉันไม่สามารถแนบดีบักเกอร์ได้อย่างง่ายดายดังนั้นฉันต้องใช้ความพยายามอย่างมากในการสร้างข้อผิดพลาดเหล่านี้ใหม่

ฉันจะดูรายละเอียดที่ซ่อนอยู่ภายในได้DbEntityValidationExceptionอย่างไร

คำตอบ:


430

ทางออกที่ง่ายที่สุดคือการแทนที่SaveChangesในคลาสเอนทิตีของคุณ คุณสามารถจับDbEntityValidationException, แกะข้อผิดพลาดจริงและสร้างใหม่DbEntityValidationExceptionด้วยข้อความที่ปรับปรุงแล้ว

  1. สร้างคลาสบางส่วนถัดจากไฟล์ SomethingSomething.Context.cs ของคุณ
  2. ใช้รหัสที่ด้านล่างของโพสต์นี้
  3. แค่นั้นแหละ. การใช้งานของคุณจะใช้ SaveChanges แบบ overriden โดยอัตโนมัติโดยไม่ต้องมี refactor

ข้อความยกเว้นของคุณจะมีลักษณะเช่นนี้:

System.Data.Entity.Validation.DbEntityValidationException: การตรวจสอบล้มเหลวสำหรับเอนทิตีหนึ่งรายการขึ้นไป ดูคุณสมบัติ 'EntityValidationErrors' สำหรับรายละเอียดเพิ่มเติม ข้อผิดพลาดในการตรวจสอบความถูกต้องคือ: ฟิลด์ PhoneNumber ต้องเป็นสตริงหรือประเภทอาร์เรย์ที่มีความยาวสูงสุด '12' จำเป็นต้องกรอกข้อมูลนามสกุล

คุณสามารถวาง SaveChanges ที่แทนที่ในคลาสใดก็ได้ที่สืบทอดจากDbContext:

public partial class SomethingSomethingEntities
{
    public override int SaveChanges()
    {
        try
        {
            return base.SaveChanges();
        }
        catch (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);
    
            // Throw a new DbEntityValidationException with the improved exception message.
            throw new DbEntityValidationException(exceptionMessage, ex.EntityValidationErrors);
        }
    }
}

DbEntityValidationExceptionนอกจากนี้ยังมีหน่วยงานที่ก่อให้เกิดข้อผิดพลาดในการตรวจสอบ ดังนั้นหากคุณต้องการข้อมูลเพิ่มเติมคุณสามารถเปลี่ยนรหัสด้านบนเพื่อส่งออกข้อมูลเกี่ยวกับเอนทิตีเหล่านี้

ดูเพิ่มเติมที่: http://devillers.nl/improving-dbentityvalidationexception/


6
คลาสเอนทิตีที่สร้างขึ้นสืบทอดมาจาก DbContext แล้วดังนั้นคุณไม่ต้องเพิ่มอีกในคลาสบางส่วน คุณจะไม่ทำลายหรือเปลี่ยนแปลงอะไรเลยโดยการเพิ่มเข้าไปในชั้นเรียนบางส่วน ในความเป็นจริงถ้าคุณเพิ่มการสืบทอดจาก DbContext Resharper จะแนะนำให้คุณลบมัน: "ประเภทฐาน 'DbContext' ได้ระบุไว้แล้วในส่วนอื่น ๆ "
Martin Devillers

15
เหตุใดจึงไม่เป็นพฤติกรรมเริ่มต้นของ SaveChanges
John Shedletsky

4
"ทำไมนี่ไม่ใช่พฤติกรรมเริ่มต้นของ SaveChanges" - นั่นเป็นคำถามที่ดีจริงๆ นี่เป็นทางออกที่น่าอัศจรรย์มันช่วยฉันได้หลายชั่วโมง! ฉันต้องโยนเข้ามาusing System.Linq;
จอห์น

1
ข้อผิดพลาดการสร้างของฉันดูใน base.SaveChanges () ซึ่งอยู่ในบล็อกลอง มันจะไม่กระโดดเข้าไปในบล็อคจับ ฉันได้รับรหัสของคุณมากกว่าการนั่ง SaveChanges แต่มันไม่เคยได้รับข้อผิดพลาด Catch Block
JustJohn

7
คุณควรตั้งค่าข้อยกเว้นภายในเพื่อรักษาการติดตามสแต็ก
dotjoe

48

DbEntityValidationResultมาร์ตินระบุมีข้อมูลเพิ่มเติมได้ใน ฉันพบว่ามีประโยชน์ที่จะได้รับทั้งชื่อคลาส POCO และชื่อคุณสมบัติในแต่ละข้อความและต้องการหลีกเลี่ยงการเขียนErrorMessageแอตทริบิวต์ที่กำหนดเองบน[Required]แท็กทั้งหมดของฉันสำหรับสิ่งนี้

การปรับแต่งต่อไปนี้เพื่อรหัสของมาร์ตินดูแลรายละเอียดเหล่านี้สำหรับฉัน:

// Retrieve the error messages as a list of strings.
List<string> errorMessages = new List<string>();
foreach (DbEntityValidationResult validationResult in ex.EntityValidationErrors)
{
    string entityName = validationResult.Entry.Entity.GetType().Name;
    foreach (DbValidationError error in validationResult.ValidationErrors)
    {
        errorMessages.Add(entityName + "." + error.PropertyName + ": " + error.ErrorMessage);
    }
}

1
ใช้SelectMany and AggregateในGitHubโดยDaring Coders
Kiquenet

43

หากต้องการดูEntityValidationErrorsคอลเลกชันให้เพิ่มนิพจน์ Watch ต่อไปนี้ในหน้าต่าง Watch

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

ฉันใช้ Visual Studio 2013


$ ยกเว้นยอดเยี่ยม! นั่นหมายความว่าในหน้าต่างทันทีฉันสามารถทำ $ exception.EntityValidationErrors.SelectMany (x => x.ValidationErrors) .Select (x => x.ErrorMessage);
chrispepper1989

13

ในขณะที่คุณอยู่ในโหมดแก้ไขข้อบกพร่องภายในcatch {...}บล็อกเปิดหน้าต่าง "QuickWatch" ( ctrl+ alt+ q) และวางใน:

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

นี้จะช่วยให้คุณสามารถเจาะลึกลงไปในValidationErrorsต้นไม้ มันเป็นวิธีที่ง่ายที่สุดที่ฉันพบว่าได้รับข้อมูลเชิงลึกเกี่ยวกับข้อผิดพลาดเหล่านี้ทันที

สำหรับผู้ใช้ Visual 2012+ ที่สนใจเฉพาะข้อผิดพลาดแรกและอาจไม่มีcatchบล็อกคุณสามารถทำได้:

((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors.First().ValidationErrors.First().ErrorMessage

9

หากต้องการค้นหาข้อความแสดงข้อผิดพลาดที่มีความหมายอย่างรวดเร็วโดยตรวจสอบข้อผิดพลาดระหว่างการดีบัก:

  • เพิ่มการดูอย่างรวดเร็วสำหรับ:

    ((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors
  • เจาะลึกลงใน EntityValidationErrors เช่นนี้:

    (รายการรวบรวมเช่น [0])> ValidationErrors> (รายการรวบรวมเช่น [0])> ErrorMessage


5

ที่จริงนี่เป็นเพียงปัญหาการตรวจสอบความถูกต้อง EF จะตรวจสอบคุณสมบัติเอนทิตีก่อนที่จะทำการเปลี่ยนแปลงใด ๆ ในฐานข้อมูล ดังนั้น EF จะตรวจสอบว่าค่าของอสังหาริมทรัพย์นั้นอยู่นอกช่วงหรือไม่เช่นเมื่อคุณออกแบบตาราง Table_Column_UserName คือ varchar (20) แต่ใน EF คุณป้อนค่าที่ยาวกว่า 20 หรือในกรณีอื่นถ้าคอลัมน์ไม่อนุญาตให้เป็น Null ดังนั้นในกระบวนการตรวจสอบคุณจะต้องตั้งค่าเป็นคอลัมน์ไม่เป็นโมฆะไม่ว่าคุณจะทำการเปลี่ยนแปลงก็ตาม โดยส่วนตัวแล้วฉันชอบคำตอบของ Leniel Macaferi มันสามารถแสดงรายละเอียดของปัญหาการตรวจสอบ


4

ฉันคิดว่า "ข้อผิดพลาดในการตรวจสอบความถูกต้องจริง" อาจมีข้อมูลที่ละเอียดอ่อนและนี่อาจเป็นสาเหตุที่ Microsoft เลือกที่จะนำพวกเขาไปไว้ที่อื่น (คุณสมบัติ) โซลูชันที่ทำเครื่องหมายไว้ที่นี่เป็นวิธีปฏิบัติ แต่ควรใช้ด้วยความระมัดระวัง

ฉันต้องการสร้างวิธีการขยาย เหตุผลเพิ่มเติมนี้:

  • เก็บสแต็กการติดตามดั้งเดิม
  • ปฏิบัติตามหลักการเปิด / ปิด (เช่น: ฉันสามารถใช้ข้อความที่แตกต่างกันสำหรับบันทึกชนิดต่าง ๆ )
  • ในสภาพแวดล้อมการผลิตอาจมีสถานที่อื่น ๆ (เช่น: อื่น ๆ dbcontext) ที่สามารถโยน DbEntityValidationException

1

สำหรับฟังก์ชัน Azure เราใช้ส่วนขยายแบบง่ายนี้ไปยังMicrosoft.Extensions.Logging.ILogger

public static class LoggerExtensions
{
    public static void Error(this ILogger logger, string message, Exception exception)
    {
        if (exception is DbEntityValidationException dbException)
        {
            message += "\nValidation Errors: ";
            foreach (var error in dbException.EntityValidationErrors.SelectMany(entity => entity.ValidationErrors))
            {
                message += $"\n * Field name: {error.PropertyName}, Error message: {error.ErrorMessage}";
            }
        }

        logger.LogError(default(EventId), exception, message);
    }
}

และตัวอย่างการใช้งาน:

try
{
    do something with request and EF
}
catch (Exception e)
{
    log.Error($"Failed to create customer due to an exception: {e.Message}", e);
    return await StringResponseUtil.CreateResponse(HttpStatusCode.InternalServerError, e.Message);
}

0

ใช้ลองบล็อกในรหัสของคุณเช่น

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

คุณสามารถตรวจสอบรายละเอียดได้ที่นี่เช่นกัน

  1. http://mattrandle.me/viewing-entityvalidationerrors-in-visual-studio/

  2. การตรวจสอบล้มเหลวสำหรับเอนทิตีอย่างน้อยหนึ่งรายการ ดูคุณสมบัติ 'EntityValidationErrors' สำหรับรายละเอียดเพิ่มเติม

  3. http://blogs.infosupport.com/improving-dbentityvalidationexception/


ลิงก์ที่สามคือสำเนาของบล็อกของคำตอบที่ยอมรับ แต่อยู่ในไซต์อื่น ลิงก์ที่สองคือคำถามล้นสแต็คที่อ้างอิงลิงก์แรกของคุณอยู่แล้ว
Eris

ดังนั้นการพยายามช่วยใครบางคนด้วยการอ้างอิงที่เหมาะสมเป็นปัญหาใด ๆ ที่นี่?
Atta H.

ใช่คำตอบของคุณไม่ควรมีเพียงแค่ลิงค์ สร้างบทสรุปที่ตอบคำถามจากนั้นโพสต์ลิงก์ในตอนท้ายเพื่ออ่านเพิ่มเติม
ChrisO
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.