DbEntityValidationException - Bagaimana saya bisa dengan mudah mengetahui apa yang menyebabkan kesalahan?

217

Saya punya proyek yang menggunakan Entity Framework. Saat memanggil SaveChangessaya DbContext, saya mendapatkan pengecualian berikut:

System.Data.Entity.Validation.DbEntityValidationException: Validasi gagal untuk satu entitas atau lebih. Lihat properti 'EntityValidationErrors' untuk detail lebih lanjut.

Ini semua bagus dan keren, tapi saya tidak ingin melampirkan debugger setiap kali pengecualian ini terjadi. Terlebih lagi, dalam lingkungan produksi saya tidak dapat dengan mudah melampirkan debugger jadi saya harus berusaha keras untuk mereproduksi kesalahan ini.

Bagaimana saya bisa melihat detail yang tersembunyi di dalam DbEntityValidationException?

Martin Devillers
sumber

Jawaban:

429

Solusi termudah adalah menimpa SaveChangeskelas entitas Anda. Anda dapat menangkap DbEntityValidationException, membuka bungkusan kesalahan aktual dan membuat yang baru DbEntityValidationExceptiondengan pesan yang ditingkatkan.

  1. Buat kelas parsial di sebelah file SomethingSomething.Context.cs Anda.
  2. Gunakan kode di bagian bawah posting ini.
  3. Itu dia. Implementasi Anda akan secara otomatis menggunakan SaveChanges yang diganti tanpa kerja refactor.

Pesan pengecualian Anda sekarang akan terlihat seperti ini:

System.Data.Entity.Validation.DbEntityValidationException: Validasi gagal untuk satu entitas atau lebih. Lihat properti 'EntityValidationErrors' untuk detail lebih lanjut. Kesalahan validasi adalah: Field PhoneNumber harus berupa tipe string atau array dengan panjang maksimum '12'; Kolom LastName wajib diisi.

Anda bisa menjatuhkan SaveChanges yang ditimpa di kelas apa pun yang mewarisi dari 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);
        }
    }
}

The DbEntityValidationExceptionjuga mengandung entitas yang menyebabkan kesalahan validasi. Jadi jika Anda memerlukan lebih banyak informasi, Anda dapat mengubah kode di atas untuk menampilkan informasi tentang entitas ini.

Lihat juga: http://devillers.nl/improving-dbentityvalidationexception/

Martin Devillers
sumber
6
Kelas Entitas yang dihasilkan sudah mewarisi dari DbContext sehingga Anda tidak perlu menambahkannya lagi pada kelas parsial. Anda tidak akan merusak atau mengubah apa pun dengan menambahkannya ke kelas parsial. Bahkan, jika Anda menambahkan warisan dari DbContext, Resharper akan menyarankan Anda menghapusnya: "Tipe dasar 'DbContext' sudah dikhususkan di bagian lain."
Martin Devillers
15
Mengapa ini bukan perilaku default SaveChanges?
John Shedletsky
4
"Mengapa ini bukan perilaku default SaveChanges?" - Itu pertanyaan yang sangat bagus. Ini adalah solusi yang luar biasa, ini menyelamatkan saya berjam-jam! Saya harus memasukkanusing System.Linq;
John August
1
Kesalahan Buat Tampilan saya di base.SaveChanges () yang ada di blok try. Itu tidak pernah melompat ke blok tangkap. Saya mendapat kode Anda untuk naik SaveChanges tetapi itu tidak pernah masuk ke Catch Block on error.
JustJohn
7
Anda harus mengatur pengecualian bagian dalam untuk menjaga jejak tumpukan.
dotjoe
48

Seperti yang ditunjukkan Martin, ada lebih banyak informasi dalam DbEntityValidationResult. Saya merasa berguna untuk mendapatkan nama kelas POCO dan nama properti saya di setiap pesan, dan ingin menghindari keharusan menulis ErrorMessageatribut khusus pada semua [Required]tag saya hanya untuk ini.

Tweak berikut untuk kode Martin menangani detail ini untuk saya:

// 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);
    }
}
Eric Hirst
sumber
1
Menggunakan SelectMany and Aggregatedi github oleh Daring Coders
Kiquenet
43

Untuk melihat EntityValidationErrorskoleksi, tambahkan ekspresi Tontonan berikut ke jendela Tontonan.

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

Saya menggunakan visual studio 2013

Shehab Fawzy
sumber
$ exception sangat brilian! itu berarti di jendela langsung saya dapat melakukan $ exception.EntityValidationErrors.SelectMany (x => x.ValidationErrors). Pilih (x => x.ErrorMessage);
chrispepper1989
13

Saat Anda berada dalam mode debug dalam catch {...}blok, buka jendela "QuickWatch" ( ctrl+ alt+ q) dan tempel di sana:

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

Ini akan memungkinkan Anda untuk menelusuri ValidationErrorspohon. Ini cara termudah yang saya temukan untuk mendapatkan wawasan instan tentang kesalahan ini.

Untuk pengguna Visual 2012+ yang hanya peduli tentang kesalahan pertama dan mungkin tidak memiliki catchblok, Anda bahkan dapat melakukan:

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

Untuk dengan cepat menemukan pesan kesalahan yang bermakna dengan memeriksa kesalahan selama debugging:

  • Tambahkan arloji cepat untuk:

    ((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors
  • Telusuri ke EntityValidationErrors seperti ini:

    (item koleksi misalnya [0])> ValidationErrors> (item koleksi misalnya [0])> ErrorMessage

Chris Halcrow
sumber
5

Sebenarnya, ini hanya masalah validasi, EF akan memvalidasi properti entitas terlebih dahulu sebelum membuat perubahan pada database. Jadi, EF akan memeriksa apakah nilai properti di luar kisaran, seperti ketika Anda mendesain meja. Table_Column_UserName adalah varchar (20). Tetapi, di EF, Anda memasukkan nilai yang lebih dari 20. Atau, dalam kasus lain, jika kolom tidak memungkinkan menjadi Null. Jadi, dalam proses validasi, Anda harus menetapkan nilai ke kolom bukan nol, tidak peduli apakah Anda akan melakukan perubahan padanya. Saya pribadi, suka jawaban Leniel Macaferi. Itu dapat menunjukkan kepada Anda detail masalah validasi

Calvin
sumber
4

Saya pikir "Kesalahan validasi aktual" mungkin mengandung informasi sensitif, dan ini bisa menjadi alasan mengapa Microsoft memilih untuk meletakkannya di tempat lain (properti). Solusi yang ditandai di sini praktis, tetapi harus diambil dengan hati-hati.

Saya lebih suka membuat metode ekstensi. Lebih banyak alasan untuk ini:

  • Simpan jejak tumpukan asli
  • Ikuti prinsip terbuka / tertutup (mis .: Saya dapat menggunakan pesan yang berbeda untuk jenis log yang berbeda)
  • Dalam lingkungan produksi mungkin ada tempat lain (yaitu .: dbcontext lainnya) di mana DbEntityValidationException dapat dilemparkan.
Luis Toapanta
sumber
1

Untuk Fungsi Azure kami menggunakan ekstensi sederhana ini ke 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);
    }
}

dan contoh penggunaan:

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);
}
Juri
sumber
0

Gunakan coba blok di kode Anda suka

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

Anda dapat memeriksa detailnya di sini juga

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

  2. Validasi gagal untuk satu entitas atau lebih. Lihat properti 'EntityValidationErrors' untuk detail lebih lanjut

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

Atta H.
sumber
Tautan ketiga Anda adalah salinan blog jawaban yang diterima, tetapi di situs lain. Tautan kedua adalah pertanyaan stack overflow yang sudah mereferensikan tautan pertama Anda.
Eris
Jadi, mencoba membantu seseorang dengan referensi yang tepat adalah masalah di sini?
Atta H.
Ya, jawaban Anda seharusnya tidak hanya berisi tautan. Buat ringkasan yang menjawab pertanyaan, lalu poskan tautan di bagian akhir untuk dibaca lebih lanjut.
ChrisO