Triển khai unique key trên thuộc tính của model trong Asp.net MVC Code First (Phần 1)

Phần 1 sẽ sử dụng giao diện IValidatableObject

Và Phần 2 sẽ sử dụng Remote Validation Attribute cùng với việc validate phía server.

Giới thiệu

Đôi khi chúng ta không muốn cho phép sự trùng lặp giá trị một cột hay một thuộc tính của bảng, như cột username trong một bảng trong database không nên cho phép người dùng chèn thêm một giá trị mà đã tồn tại rồi...

Bắt đầu

Giả sử chùng ta có một kho hàng tồn nơi bảng Product sử dụng dể theo dõi tất cả sản phẩm . Nó ko cho phép user chèn thêm một tên sản phẩm mà đã tồn tại trong bảng rồi. Như mẫu dưới đây.

public  class Product
{
        public int Id { get; set; }

        public string ProductName { get; set; }

        public int ProductQuantity { get; set; }

        public decimal UnitPrice { get; set; }
}

Cách thứ 1 : sử dụng giao diện IValidatableObject

Bước 1:

Thêm "Index" thuộc tính với ''IsUnique'' tham số cho thuộc tính ProductName. Nên nhớ rằng với "Index" thuộc tính bạn phải dùng thuộc tính "StringLength".

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

namespace ImplementingUniqueKey.Models
{
   public class Product
   {
        public int Id { get; set; }

        [Required]
        [StringLength(50)]
        [Index("Ix_ProductName",Order =1,IsUnique =true)]
        public string ProductName { get; set; }

        public int ProductQuantity { get; set; }

        public decimal UnitPrice { get; set; }
    }

}

Trước khi có những thay đổi này đã có một bảng product trong database sau đó cập nhật những thay đổi cho database sử dụng code first migration hoặc đơn giản xóa bảng nếu ko có vấn đề về dữ liệu.

Bây giờ hãy chạy project và sau đó thử chèn vào product một tên mà đã tồn tại. Bạn sẽ nhận ngoại lệ với lỗi sau:

Cannot insert duplicate key row in object 'dbo.Products' with unique  index

'Ix_ProductName'. The duplicate key value is (ProductName). The statement

 has been terminated.

Bước 2:

Bạn phải thừa kế giao diện IValidatableObject và thực thi phương thức Validate() của giao diện IValidatableObject.

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;

namespace ImplementingUniqueKey.Models
{
    public class Product : IValidatableObject
    {
        public int Id { get; set; }

        [Required]
        [StringLength(50)]
        [Index("Ix_ProductName",Order =1,IsUnique =true)]
        public string ProductName { get; set; }

        public int ProductQuantity { get; set; }

        public decimal UnitPrice { get; set; }

        IEnumerable<ValidationResult> IValidatableObject.Validate(ValidationContext validationContext)
        {
            ProductDbContext db = new ProductDbContext();
            List<ValidationResult> validationResult = new List<ValidationResult>();
            var validateName = db.Products.FirstOrDefault(x => x.ProductName == ProductName);
            if (validateName != null)
            {
                ValidationResult errorMessage = new ValidationResult("Product name already exists.", new[] { "ProductName" });
                validationResult.Add(errorMessage);
            }

            return validationResult;
        }
    }

}

Cho đến bây giờ nó vẫn đúng trong trường hợp tạo mới.Nhưng không đúng trong trường hợp Edit/Updating một entity tồn tại. Nó không cho phép truyền một giá trị ban đầu unique trong khi cập nhật những cái khác. Để truyền một giá trị khởi đầu của trường unique, thay đổi phương thức validate như sau :

IEnumerable<ValidationResult> IValidatableObject.Validate(ValidationContext validationContext)
        {
            ProductDbContext db = new ProductDbContext();
            List<ValidationResult> validationResult = new List<ValidationResult>();
            var validateName = db.Products.FirstOrDefault(x => x.ProductName == ProductName && x.Id != Id);
            if (validateName != null)
            {
                ValidationResult errorMessage = new ValidationResult("Product name already exists.", new[] { "ProductName" });
                validationResult.Add(errorMessage);
                return validationResult;
            }

            else
            {
                return validationResult;
            }

        }

Sau cùng hãy viết đơn giản như sau :

       IEnumerable<ValidationResult> IValidatableObject.Validate(ValidationContext validationContext)
        {
            ProductDbContext db = new ProductDbContext();
            var validateName = db.Products.FirstOrDefault(x => x.ProductName == ProductName && x.Id != Id);
            if (validateName != null)
            {
                ValidationResult errorMessage = new ValidationResult("Product name already exists.", new[] { "ProductName" });
                yield return errorMessage;
            }
            else
            {
                yield return ValidationResult.Success;
            }

        }

Trước khi chạy project hãy nhớ rằng phương thức db.SaveChanges() trong controller phải là được check "ModelState.IsValid" . Create và Edit/Update phương thức nên như sau:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,ProductName,ProductQuantity,UnitPrice")] Product product)
{
    if (ModelState.IsValid)
    {
        db.Products.Add(product);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    return View(product);
}

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "Id,ProductName,ProductQuantity,UnitPrice")] Product product)
{
    if (ModelState.IsValid)
    {
        db.Entry(product).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(product);
}

Bây giờ hãy thử chạy project và chèn vào bảng product với một tên đã tồn tại trong bảng bạn sẽ nhận được validate như sau.

validationMessage.PNG

Còn tiếp phần 2.

Dịch từ nguồn : http://www.codeproject.com/Articles/1130113/Best-ways-of-implementing-Uniqueness-or-Unique-Key