Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Best Ways of Implementing Uniqueness or Unique Key Attribute on a Model Property in ASP. NET MVC Code First: Part 1

0.00/5 (No votes)
26 Sep 2016 2  
There are lots of ways to implement Uniqueness or Unique Key attribute on a model property other than primary key, but here I am going to discuss two possible best and easiest ways to do so into two parts of this series. This is Part 1.

[Last Update Message]

Before this update, unfortunately there was a serious problem which went unnoticed that the method works fine in case of creating new entity but does not work properly in case of Edit/Updating any existing entity. It does not allow to pass the original value of unique field while updating the others. The problem is fixed in this update.

[Preface]

First, I wrote the article with the combination of two ways to implement Uniqueness or Unique Key attribute on a model property in ASP.NET MVC Code first. These ways were:

  1. Using IValidatableObject Interface and
  2. Using Remote Validation Attribute.

There is no problem with the first way, but suddenly I realized that there is a loophole in the second one. Because the second one is JavaSript based client side validation. So if any wicked user tried to insert duplicate values, then he would be capable of doing so by disabling the JavaScript from the browser. So I have to handle this situation by adding server side validation along with the existing client side validation in method 2.

To keep the article size reasonable, I have decided to break the article into two parts where:

  1. Part 1 will be the Method 1 that is by Using IValidatableObject Interface and
  2. Part 2 will be the Method 2 that is by Using Remote Validation Attribute along with custom Server Side validation (in case JavaScript is disabled)

Introduction

Sometimes, we are in need of not permitting the duplicate values of a column or property in a database table, such as: for a username column or property in database table, we should not allow user to insert a value that already exists in the database table because a username is a unique value.

Let's Start!

Suppose we have an Inventory where there is a Product table/ class which is used to track all products as a list of products. So it is rational to not permit the user to insert a product name that already exists in the table. Here is our product model class which will be used throughout this article:

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

        public string ProductName { get; set; }

        public int ProductQuantity { get; set; }

        public decimal UnitPrice { get; set; }
}

Method 1: Using IValidatableObject Interface

Step 1: Add "Index" Attribute with ''IsUnique'' parameter to the ProductName property. Remember that! with "Index" attribute you must use "StringLength" attribute also.

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

Prior to these changes, if you have a Product table of product class already in the database, then update the changes in database using code first migration or you can simply drop the table if there is no issue with existing data.

Now run the project and try to insert a product entity with a product name that already exists in the table. You will get an exception with the following error message:

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.

Which is meaningless to the client user. So you have to handle the exception to give a meaningful message to the client user.

Step 2: Now you have to inherit the IValidatableObject interface and provide the implementation of Validate() method of IValidatableObject interface.

After doing so, Product class will look like follows:

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

Till now, it works fine in case of only creating any new entity but does not work properly in case of Edit/Updating any existing entity. It does not allow to pass the original value of unique field while updating the others. To pass the original value of the unique field, modify the validate() method as follows:

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

You can also write the validate() method in a more simplified form as follows:

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

Before running the project, keep in mind that your db.SaveChanges() methods in the controller class must be in "ModelState.IsValid" checking. Your Create and Edit/Update methods should look like follows:

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

Now run the project and try to insert a product entity with a product name that already exists in the table. You will get the validation error message as follows:

Now you will also be able to Edit/Update any field of an existing entity/record satisfying the validation logic.

Part 1 Method 1 is done!! and here is Part 2 Method 2.

Conclusion

This is my first article on Code Project. So there might be some mistakes. Feel free to correct if any mistake is found. Don't forget to comment if you have any suggestions. Thank you for reading!!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here