WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

EF6 で PK / Unique 制約違反例外をキャッチ

by WebSurfer 8. March 2021 18:08

EF6 を利用して SQL Server のテーブルの CRUD 操作を行う ASP.NET MVC5 アプリで、新規レコードの Create の際に Primary / Unique キー制約違反例外をキャッチし、エラーメッセージを表示する方法を書きます。

Primary / Unique キー制約の検証

ADO.NET + SqlClient を使って SqlCommand.ExecuteNonQuery メソッドで INSERT 操作を行う場合は、例外が発生すると SqlException がスローされますので、try - catch 句でそれを補足して SqlException.Number プロパティを調べれば例外の内容が分かります。ちなみに、2627 が Primary キー制約違反、2601 が Unique キー制約違反になります。

しかしながら、EF6 の DbContext.SaveChanges メソッドで INSERT 操作を行う場合、スローされる例外は DbUpdateException, DbUpdateConcurrencyException などで、SqlException を直接補足することができません。

そこをどうするかですが、ググって調べた記事「EF6とSQL ServerでUniqueKey違反の例外をキャッチするにはどうすればよいですか?」によると、try - catch 句で DbUpdateException を補足し、その InnerException の InnerException から SqlException を取得できるとのことです。

そういうことなので、上の記事の 2 つ目の回答を参考に、Primary / Unique キー制約違反例外をキャッチし、エラーメッセージを表示する機能を実装してみました。

それが以下のコードです。上の画像は SQL Server のテーブルの ProductName 列に付与した Unique キー制約違反を補足してエラーメッセージを表示したものです。

エンティティクラス (Model)

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Mvc5App2.Models
{
    public class ProductModel
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int ProductId { get; set; }

        [Required]
        [StringLength(128)]
        [Index(IsUnique = true)]
        public string ProductName { get; set; }

        [Required]
        public decimal UnitPrice { get; set; }
    }
}

コンテキストクラス

using Mvc5App2.Models;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;

namespace Mvc5App2.DAL
{
    public class ProductContext : DbContext
    {
        public ProductContext() : base("name=DefaultConnection")
        {
        }

        public DbSet<ProductModel> Products { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
        }
    }
}

上のエンティティクラスとコンテキストクラスから、EF6 Code First の機能を利用して生成された SQL Server のテーブルの構造は以下の通りです。

生成されたテーブルの構造

Controller / Action Method

スキャフォールディング機能を利用して生成した controller のコードの中の Create アクションメソッドに、上に紹介した記事の Primary / Unique キー制約違反例外を補足するコードを実装したものです。

using System.Data.Entity;
using System.Threading.Tasks;
using System.Net;
using System.Web.Mvc;
using Mvc5App2.DAL;
using Mvc5App2.Models;
using System.Data.Entity.Infrastructure;
using System.Data.SqlClient;

namespace Mvc5App2.Controllers
{
    public class ProductModelsController : Controller
    {
        private ProductContext db = new ProductContext();

        // ・・・中略・・・

        // GET: ProductModels/Create
        public ActionResult Create()
        {
            return View();
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> Create(
                [Bind(Include = "ProductId,ProductName,UnitPrice")] 
                ProductModel productModel)
        {
            if (ModelState.IsValid)
            {
                db.Products.Add(productModel);

                try
                {
                    await db.SaveChangesAsync();
                }
                catch (DbUpdateException e)
                when (e.InnerException?.InnerException is SqlException sqlEx &&
                        (sqlEx.Number == 2601 || sqlEx.Number == 2627))
                {
                    if (sqlEx.Number == 2627)
                    {
                        ModelState.AddModelError("ProductId", "PK 制約違反");
                    }

                    if (sqlEx.Number == 2601)
                    {
                        ModelState.AddModelError("ProductName", "Unique 制約違反");
                    }

                    return View(productModel);
                }
                return RedirectToAction("Index");
            }

            return View(productModel);
        }
    }

    // ・・・中略・・・
}

View

スキャフォールディング機能を利用して生成した create.cshtml のコードのままで手は加えていません。

@model Mvc5App2.Models.ProductModel

@{
    ViewBag.Title = "Create";
}

<h2>Create</h2>

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>ProductModel</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="form-group">
            @Html.LabelFor(model => model.ProductId,
                htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.ProductId,
                    new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.ProductId, "",
                    new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.ProductName,
                htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.ProductName,
                    new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.ProductName, "",
                    new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.UnitPrice,
                htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.UnitPrice,
                    new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.UnitPrice, "",
                    new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </div>
    </div>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

Primary / Unique キー両方に制約違反がある場合は Primary キーの制約違反だけしか補足できません。両方補足して両方のエラーメッセージを表示する方法は今のところ分かりません。今後の課題ということで。

Tags: , , ,

MVC

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  April 2021  >>
MoTuWeThFrSaSu
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789

View posts in large calendar