DbEntityValidationException - Làm thế nào tôi có thể dễ dàng biết nguyên nhân gây ra lỗi?


217

Tôi có một dự án sử dụng Entity Framework. Trong khi gọi SaveChangescho tôi DbContext, tôi nhận được ngoại lệ sau:

System.Data.Entity.Validation.DbEntityValidationException: Xác thực thất bại cho một hoặc nhiều thực thể. Xem thuộc tính 'EntityValidationErrors' để biết thêm chi tiết.

Đây là tất cả tốt và đẹp, nhưng tôi không muốn đính kèm một trình sửa lỗi mỗi khi ngoại lệ này xảy ra. Hơn nữa, trong môi trường sản xuất, tôi không thể dễ dàng đính kèm trình gỡ lỗi vì vậy tôi phải cố gắng hết sức để tái tạo các lỗi này.

Làm thế nào tôi có thể thấy các chi tiết ẩn trong DbEntityValidationException?

Câu trả lời:


429

Giải pháp đơn giản nhất là ghi đè lên SaveChangeslớp thực thể của bạn. Bạn có thể nắm bắt DbEntityValidationException, gỡ bỏ các lỗi thực tế và tạo một thông báo mới DbEntityValidationExceptionvới thông báo được cải thiện.

  1. Tạo một lớp một phần bên cạnh tệp SomethingS Something.Context.cs của bạn.
  2. Sử dụng mã ở dưới cùng của bài viết này.
  3. Đó là nó. Việc triển khai của bạn sẽ tự động sử dụng SaveChanges overriden mà không cần bất kỳ công việc tái cấu trúc nào.

Thông báo ngoại lệ của bạn sẽ trông như thế này:

System.Data.Entity.Validation.DbEntityValidationException: Xác thực thất bại cho một hoặc nhiều thực thể. Xem thuộc tính 'EntityValidationErrors' để biết thêm chi tiết. Các lỗi xác thực là: Trường PhoneNumber phải là kiểu chuỗi hoặc mảng có độ dài tối đa '12'; Trường LastName là bắt buộc.

Bạn có thể thả SaveChanges bị ghi đè trong bất kỳ lớp nào kế thừa từ 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);
        }
    }
}

DbEntityValidationExceptioncũng chứa các thực thể gây ra lỗi xác nhận. Vì vậy, nếu bạn yêu cầu nhiều thông tin hơn nữa, bạn có thể thay đổi mã trên thành thông tin đầu ra về các thực thể này.

Xem thêm: http://devillers.nl/improving-dbentityvalidationexception/


6
Lớp Thực thể được tạo đã kế thừa từ DbContext, do đó bạn không phải thêm lại vào lớp một phần. Bạn sẽ không phá vỡ hoặc thay đổi bất cứ điều gì bằng cách thêm nó vào lớp một phần. Trong thực tế, nếu bạn thêm sự kế thừa từ DbContext, Resharper sẽ đề nghị bạn xóa nó: "Loại cơ sở 'DbContext' đã được đặc trưng trong các phần khác."
Martin Devillers

15
Tại sao đây không phải là hành vi mặc định của SaveChanges?
John Shedletsky

4
"Tại sao đây không phải là hành vi mặc định của SaveChanges?" - Đây là một câu hỏi rất hay. Đây là một giải pháp tuyệt vời, nó đã giúp tôi tiết kiệm hàng giờ! Tôi đã phải ném vàousing System.Linq;
John ngày

1
Lỗi Tạo Chế độ xem của tôi trên cơ sở.SaveChanges () nằm trong khối thử. Nó không bao giờ nhảy vào khối bắt. Tôi đã nhận được mã của bạn để vượt qua SaveChanges nhưng nó không bao giờ gặp phải lỗi Catch Block.
JustJohn

7
Bạn nên đặt ngoại lệ bên trong để giữ dấu vết ngăn xếp.
dotjoe

48

Như Martin chỉ ra, có nhiều thông tin hơn trong DbEntityValidationResult. Tôi thấy hữu ích khi lấy cả tên thuộc tính và tên thuộc tính lớp POCO của mình trong mỗi tin nhắn và muốn tránh phải viết các ErrorMessagethuộc tính tùy chỉnh trên tất cả [Required]các thẻ của mình chỉ cho việc này.

Các tinh chỉnh sau đây về mã của Martin đã chăm sóc các chi tiết này cho tôi:

// 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
Sử dụng SelectMany and Aggregatetrong github bởi Daring Coders
Kiquenet

43

Để xem EntityValidationErrorsbộ sưu tập, hãy thêm biểu thức Đồng hồ sau vào cửa sổ Xem.

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

Tôi đang sử dụng visual studio 2013


$ ngoại lệ là tuyệt vời! điều đó có nghĩa là trong cửa sổ ngay lập tức tôi có thể thực hiện $ ngoại lệ.EntityValidationErrors.SelectMany (x => x.ValidationErrors) .Select (x => x.ErrorMessage);
chrispepper1989

13

Trong khi bạn đang ở chế độ gỡ lỗi trong catch {...}khối, hãy mở cửa sổ "QuickWatch" ( ctrl+ alt+ q) và dán vào đó:

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

Điều này sẽ cho phép bạn đi sâu vào ValidationErrorscây. Đó là cách dễ nhất mà tôi đã tìm thấy để có cái nhìn sâu sắc tức thì về những lỗi này.

Đối với người dùng Visual 2012+ chỉ quan tâm đến lỗi đầu tiên và có thể không có catchkhối, bạn thậm chí có thể thực hiện:

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

9

Để nhanh chóng tìm thấy một thông báo lỗi có ý nghĩa bằng cách kiểm tra lỗi trong quá trình gỡ lỗi:

  • Thêm một chiếc đồng hồ nhanh cho:

    ((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors
  • Đi sâu vào EntityValidationErrors như thế này:

    (mục bộ sưu tập, ví dụ [0])> ValidationErrors> (mục bộ sưu tập, ví dụ [0])> ErrorMessage


5

Trên thực tế, đây chỉ là vấn đề xác thực, trước tiên, EF sẽ xác thực các thuộc tính thực thể trước khi thực hiện bất kỳ thay đổi nào đối với cơ sở dữ liệu. Vì vậy, EF sẽ kiểm tra xem giá trị của tài sản có nằm ngoài phạm vi hay không, như khi bạn thiết kế bảng. Table_Column_UserName là varchar (20). Nhưng, trong EF, bạn đã nhập một giá trị dài hơn 20. Hoặc, trong các trường hợp khác, nếu cột không cho phép là Null. Vì vậy, trong quá trình xác thực, bạn phải đặt một giá trị thành cột không null, bất kể bạn có thực hiện thay đổi trên đó hay không. Cá nhân tôi, giống như câu trả lời của Leniel Macaferi. Nó có thể cho bạn thấy chi tiết về các vấn đề xác nhận


4

Tôi nghĩ rằng "Các lỗi xác thực thực tế" có thể chứa thông tin nhạy cảm và đây có thể là lý do tại sao Microsoft chọn đặt chúng ở một nơi khác (thuộc tính). Giải pháp được đánh dấu ở đây là thực tế, nhưng cần thận trọng.

Tôi muốn tạo ra một phương pháp mở rộng. Thêm lý do cho việc này:

  • Giữ dấu vết ngăn xếp ban đầu
  • Thực hiện theo nguyên tắc mở / đóng (ví dụ: Tôi có thể sử dụng các thông báo khác nhau cho các loại nhật ký khác nhau)
  • Trong các môi trường sản xuất, có thể có các vị trí khác (ví dụ: dbcontext khác) nơi DbEntityValidationException có thể được ném.

1

Đối với Hàm Azure, chúng tôi sử dụng tiện ích mở rộng đơn giản này cho 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);
    }
}

và ví dụ sử dụng:

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

Sử dụng khối thử trong mã của bạn như

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

Bạn có thể kiểm tra các chi tiết ở đây là tốt

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

  2. Xác nhận thất bại cho một hoặc nhiều thực thể. Xem thuộc tính 'EntityValidationErrors' để biết thêm chi tiết

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


Liên kết thứ ba của bạn là một bản sao của blog của câu trả lời được chấp nhận, nhưng trên một trang web khác. Liên kết thứ hai là một câu hỏi tràn ngăn xếp đã tham chiếu liên kết đầu tiên của bạn.
Eris

Vì vậy, cố gắng giúp ai đó có tài liệu tham khảo phù hợp là bất kỳ vấn đề ở đây?
Atta H.

Có, câu trả lời của bạn không nên chỉ chứa các liên kết. Tạo một bản tóm tắt trả lời câu hỏi và sau đó gửi liên kết ở cuối để đọc thêm.
ChrisO
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.