(C# ASP.NET Core) Strategy to handle a DbUpdateException

SaveChanges can fail if a constraint fails, if a connection error occurs or if any generic error occurs. So it should always be wrapped in a try-catch handler to gracefully handle the DbUpdateException. But how to inform the user about such a failure? One way is to use the built-in server side validation to artificially push the error as a validation failure.

Categories | About |     |  

Parveen,

Explanation of the Problem

Consider this model where a field called Name has been marked as required.

public class Product
{

  // auto-increment Primary ID key 
  [Key]
  [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
  public int ID { get; set; }

  // this is a required field 
  [Required]
  public String Name { get; set; }
}


// ProductContext not shown for simplicity 
// (see the previous tutorial for the code) 

Consider this FORM based on the above model. Notice that we have added a validation summary so that we can show validation failures.


@page

@model Modular.Pages.IndexModel

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<form method="post">

  // textbox 
  Name: <input asp-for="Prod.Name" />

  // validation summary 
  <div asp-validation-summary="All"></div>

  // submit button 
  <input type="submit" />

  <p>(run with blank form)</p>

</form>

Also consider the following handler on the backing class

public class IndexModel : PageModel
{

  private readonly ProductContext _ctx;

  // dependency injection of the ProductContext 
  public IndexModel(ProductContext ctx)
  {

    _ctx = ctx;

  }


  // 2-way binding 
  [BindProperty]
  public Product Prod { get; set; }

  // OnPost handler 
  public ActionResult OnPost()
  {

    // commented so that 
    // the exception is thrown 
    // on context SaveChanges 
    // if (!ModelState.IsValid) 
    // { 
    //  return Page() 
    // } 

    // attach the item to 
    // the ProductContext 
    _ctx.Products.Add(Prod);

    ----> FAILS      _ctx.SaveChanges();

    // redirect and show the 
    // form again 
    return RedirectToPage();

  }

}

When the above form is submitted with a blank textbox, an exception is thrown because the [Required] constraint fails.

Video Explanation

Please watch the following youtube video:

Suggested Solution

Follow these steps-

  1. Wrap SaveChanges in a try-catch
  2. Call ModelState.AddModelError to artificially add an error to the affected property in the catch block. You can show the actual inner exception or some message. The model error will appear in a validation summary control as shown below.
using DBaseCon;

using Microsoft.AspNetCore.Mvc;

using Microsoft.AspNetCore.Mvc.RazorPages;

using Microsoft.EntityFrameworkCore;

namespace Modular.Pages
{

  public class IndexModel : PageModel
  {

    private readonly ProductContext _ctx;

    // dependency injection of the ProductContext 
    public IndexModel(ProductContext ctx)
    {

      _ctx = ctx;

    }

    // 2-way binding 
    [BindProperty]
    public Product Prod { get; set; }
    public ActionResult OnPost()
    {

      // commented so that 
      // the exception is thrown 
      // on context SaveChanges 
      // if (!ModelState.IsValid) 
      // { 
      //  return Page() 
      // } 

      // attach the item to 
      // the ProductContext 
      _ctx.Products.Add(Prod);

      try
      {

        _ctx.SaveChanges();

      }

      catch (DbUpdateException e)
      {

        ModelState.Clear();

        ModelState.AddModelError("Prod.Name", e.InnerException.Message);

        return Page();

      }

      // redirect and show the 
      // form again 
      return RedirectToPage();

    }

  }

}

This is a sample output:


This Blog Post/Article "(C# ASP.NET Core) Strategy to handle a DbUpdateException" by Parveen is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

Comments and Discussion