عنوان:

‫استثناهایی که باید حین استفاده از EF Code first بررسی شوند


نویسنده: وحید نصیری
تاریخ: ۱۳۹۱/۰۳/۳۱ ۱۹:۰
آدرس: www.dntips.ir
سه نوع استثنای مهم ممکن است حین ذخیره سازی تغییرات در EF code first رخ دهند که بررسی جزئیات آن‌ها می‌تواند راهنمای خوبی برای کاربر و همچنین برنامه نویس در عیب یابی سیستم باشد. این استثناءها باید به صورت مستقل و جداگانه بررسی شوند ونه اینکه از حالت عمومی catch Exception استفاده شود.
این سه نوع استثناء شامل موارد DbEntityValidationException، DbUpdateConcurrencyException و DbUpdateException هستند که به صورت خلاصه به شکل زیر باید تعریف شوند:

try
{
    context.SaveChanges();
}
catch (DbEntityValidationException validationException)
{
   //...
}
catch (DbUpdateConcurrencyException concurrencyException)
{
   //...
}
catch (DbUpdateException updateException)
{
   //...
}


توضیحات تکمیلی

در حالت DbEntityValidationException به جزئیات خطاهای حاصل از اعتبار سنجی اطلاعات خواهیم رسید. برای مثال اگر قرار است طول فیلدی 30 حرف باشد و کاربر 40 حرف را وارد کرده است، نام خاصیت و همچنین پیغام خطای درنظر گرفته شده را دقیقا در اینجا می‌توان دریافت کرد و به نحو مقتضی به کاربر نمایش داد:

catch (DbEntityValidationException validationException)
{
    foreach (var error in validationException.EntityValidationErrors)
    {
        var entry = error.Entry;
        foreach (var err in error.ValidationErrors)
        {
            Debug.WriteLine(err.PropertyName + " " + err.ErrorMessage);
        }
    }
}

نوع استثنای DbUpdateConcurrencyException به مسایل همزمانی و به روز رسانی یک رکورد توسط دو یا چند کاربر در شبکه مرتبط می‌شود که در قسمت سوم سری EF code first با معرفی ویژگی‌های ConcurrencyCheck و Timestamp در مورد آن بحث شد. در اینجا به کلیه موجودیت‌های تداخل دار توسط خاصیت concurrencyException.Entries خواهیم رسید و همچنین به کمک متد GetDatabaseValues می‌توان موارد جدید ثبت شده مرتبط با این موجودیت تداخل دار را از بانک اطلاعاتی نیز دریافت کرد:

catch (DbUpdateConcurrencyException concurrencyException)
{
    //بررسی مورد اول
    var dbEntityEntry = concurrencyException.Entries.First();
    var dbPropertyValues = dbEntityEntry.GetDatabaseValues();
}

و یا کلا ممکن است حین به روز رسانی بانک اطلاعاتی مشکلی رخ داده باشد که در اینجا عموما پیغام حاصل را باید در InnerException تولیدی یافت و همچنین در اینجا لیست موجودیت‌های مشکل دار نیز قابل دریافت و بررسی هستند:

catch (DbUpdateException updateException)
{
    if (updateException.InnerException != null) 
        Debug.WriteLine(updateException.InnerException.Message);

    foreach (var entry in updateException.Entries)
    {
        Debug.WriteLine(entry.Entity);
    }
}

بنابراین بررسی catch exception کلی در EF Code first مناسب نبوده و نیاز است بیشتر به جزئیات ذکر شده، وارد و دقیق شد.

یک نکته:
بهتر است یک کلاس پایه عمومی مشتق شده از DbContext را ایجاد و متد SaveChanges آن را تحریف کرد. سپس سه حالت فوق را به آن اعمال نمود. اکنون می‌توان از این کلاس پایه بارها استفاده کرد بدون اینکه نیازی به تکرار کدهای آن در هرجایی که قرار است از متد SaveChanges استفاده شود، باشد. شبیه به این کار را در قسمت 14 سری EF code first مشاهده نموده‌اید.
 

نظرات

  • علی علوی تبار در ۱۳۹۱/۰۳/۳۱ ۲۲:۸
    با سلام و تشکر مثل همیشه عالی
  • Mona در ۱۳۹۱/۰۳/۳۱ ۲۳:۱۶
    یک خواهش، بدلیل اینکه سطوح علمی مراجعین متفاوت است، پیشنهاد میکنم در صورت امکان مثل codeproject یک فایل Sample هم برای پست‌ها آپلود شود.
    ممنون
  • مهمان در ۱۳۹۱/۰۴/۰۴ ۲۲:۳۷
    آیا می‌توان به جای نمایش یک پیام انگلیسی هنگام وقوع خطای اعتبارسنجی مثلا از
    [Required(ErrorMessage = "لطفا نام نویسنده را مشخص نمائید")] 
    استفاده کرد؟
    • وحید نصیری در ۱۳۹۱/۰۴/۰۴ ۲۲:۳۹
      بله. اگر از ASP.NET MVC استفاده کنید به صورت خودکار تبدیل به پیغام‌های اعتبار سنجی سمت کاربر هم می‌شود. ولی در کل اثر بررسی سمت سرور هم دارد و همین پیغام‌ها توسط DbEntityValidationException قابل دریافت هستند.
      • مهمان در ۱۳۹۱/۰۴/۰۴ ۲۲:۵۹
        من یک پیام خطا برای یکی از Propertyها تعیین کردم.وقتی می‌خوام به طریق زیر پیام رو دریافت کنم بازم همون خطای انگلیسی رو مشاهده می‌کنم!؟
         catch (DbEntityValidationException validationException)
                        {
                            var errors = validationException.EntityValidationErrors.First();
                            foreach (var propertyError in errors.ValidationErrors)
                            {
                                Console.WriteLine(propertyError.PropertyName + "----" + propertyError.ErrorMessage);
                            }
                        }
        • وحید نصیری در ۱۳۹۱/۰۴/۰۴ ۲۳:۳
          شما از متد First اینجا استفاده کردید. به عبارتی فقط اولین مورد گزارش داده شده رو دارید بررسی می‌کنید و از بقیه موارد صرفنظر کردید.
          • مهمان در ۱۳۹۱/۰۴/۰۴ ۲۳:۵
            درسته چونکه میخواستم فقط همین خطا رو که عمدی ایجاد کردم ببینم ولی نشد.
            • وحید نصیری در ۱۳۹۱/۰۴/۰۴ ۲۳:۱۴
              کد کاملی که من در مطلب فوق نوشتم چنین چیزی رو نشون می‌ده:


              • مهمان در ۱۳۹۱/۰۴/۰۴ ۲۳:۳۵
                [MaxLength(2), Required(ErrorMessage = "طول فیلد بیش از حد مجاز است")]
                public string ProductName { get; set; }

                من از MySQL استفاده کردم. میتونه مشکل از این باشه؟ 

                • وحید نصیری در ۱۳۹۱/۰۴/۰۴ ۲۳:۴۰
                  خیر. علت این است که به ازای تک تک ویژگی‌هایی که تعریف می‌کنید باید ErrorMessage مقدار دهی شود. مثلا در اینجا MaxLength تعریف شده دارای پیغام خطا نیست و آن‌را از پیغام خطای Required دریافت نمی‌کند.
                  • مهمان در ۱۳۹۱/۰۴/۰۴ ۲۳:۴۴
                    بله همینطوره.باید به این شکل می‌نوشتم.
                    [MaxLength(2, ErrorMessage = "طول فیلد بیش از حد مجاز است")]
                    خیلی ممنون آقای نصیری.
  • مهمان در ۱۳۹۱/۰۴/۰۶ ۱۹:۳۸
    بهتر است یک کلاس پایه عمومی مشتق شده از DbContext را ایجاد و متد SaveChanges آن را تحریف کرد. سپس سه حالت فوق را به آن اعمال نمود. اکنون می‌توان از این کلاس پایه بارها استفاده کرد بدون اینکه نیازی به تکرار کدهای آن در هرجایی که قرار است از متد SaveChanges استفاده شود، باشد.  
    چطور میشه از این استثناها در override متد savechange استفاده کرد؟
    • وحید نصیری در ۱۳۹۱/۰۴/۰۶ ۲۱:۲۹
      به این شکل کلی
      try
      {
         return base.SaveChanges();
      }
      catch (DbEntityValidationException validationException)
      {
         //...
      }
      //...
      

      • مهمان در ۱۳۹۱/۰۴/۰۶ ۲۱:۴۴
        منظورم اینه که متد savechange فقط مقدار int برمیگردونه.اگر استثنایی رخ داد فقط می‌تونیم یه عدد خاص برگردونیم که نشانه‌ی وقوع استثنا هست.در این صورت چطور می‌تونیم پبام  خطاهای اعتبارسنجی رو کنترل کنیم؟
        • وحید نصیری در ۱۳۹۱/۰۴/۰۶ ۲۱:۵۵
          اینکه شما چه کاری با این اطلاعات انجام می‌دید به طراحی سیستم خودتون مرتبط است. یکی یک پیغام popup نمایش می‌ده یکی یک messagebox یکی هم فقط این‌ها رو لاگ می‌کنه برای بررسی بعدی و یک پیغام کلی به کاربر نمایش می‌ده که مشکلی رخ داده.
          من این‌ها رو لاگ می‌کنم و پیغام کلی نمایش می‌دم.
          • مهمان در ۱۳۹۱/۰۸/۰۹ ۲۲:۲۳
            بسم الله الرحمن الرحیم
            با سلام
            چه جوری میشه بعد از درآوردن اعتبارسنجی‌های مربوط به تک تک   پراپرتی‌ها پیام اونها رو در جلوی تکس باکس مربوطه نوشت.
            راهی که به ذهن بنده میرسه به شکل زیر است:
            ICollection<DbValidationError> ValidationProperty = 
                                            context.Entry(product).Property(p => p.Name).GetValidationErrors();
            
                                        
                                        if (ValidationProperty.Count() > 0)
                                        {
                                            
                                                DbValidationError Error=ValidationProperty.First();
                                                errorProvider1.SetError(txtName, Error.ErrorMessage);                                    
                                        }
            خواستم ببینم راه بهترو سریعتری وجود داره؟ 
            با سپاس فراوان
            شب خوش
  • سارا کیانی در ۱۳۹۳/۰۱/۲۷ ۱۵:۱۳
    سلام
    چنانچه قصد داشته باشیم در Context خودمان استثنای Connect به سرویس دهنده رو هم در نظر گرفته تا در صورت قطع ارتباط، پیغام خاصی به کاربر نمایش دهیم با کدام Exception در Code First تشخیص داده خواهد شد؟
    • وحید نصیری در ۱۳۹۳/۰۱/۲۷ ۱۵:۳۰
      مانند دوران ADO.NET است:
      catch (System.Data.SqlClient.SqlException ex)
      {
                  foreach (SqlError error in ex.Errors)
                  {
                      switch (error.Number)
                      {
                          case 1205:
                              System.Diagnostics.Debug.WriteLine("SQL Error: Deadlock condition.");
                              return true;
      
                          case -2:
                              System.Diagnostics.Debug.WriteLine("SQL Error: Timeout expired.");
                              return true;
      
                          case -1:
                              System.Diagnostics.Debug.WriteLine("SQL Error: Timeout expired.");
                              return true;
                      }
                  }
      ضمنا یک سری مباحث به نام اتصال بهبودپذیر و مقاوم به EF 6 در این زمینه اضافه شده:
      Connection Resiliency Spec 
      +
      نحوه راه اندازی مجدد یک دیتابیس اس کیوال سرور پس از پر شدن هارد دیسک   
  • علی یگانه مقدم در ۱۴۰۱/۰۲/۰۶ ۲۰:۲۱
    در سری EF Core کتابخانه ای ( EntityFramework.Exceptions ) جهت ساده سازی و مدیریت استثناها نیز ارائه شده‌است.