Click here to Skip to main content
15,881,413 members
Articles / Web Development / HTML

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

Rate me:
Please Sign up or sign in to vote.
4.98/5 (12 votes)
26 Sep 2016CPOL4 min read 54.4K   259   10   9
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:

C#
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.

C#
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:

C#
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:

C#
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:

C#
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:

C#
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:

C#
[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:

Image 1

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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Bangladesh Bangladesh
A Software Engineer, Passionate Programmer and Technical Writer!!

Comments and Discussions

 
Praisegreat Pin
Member 285504717-Nov-17 9:37
Member 285504717-Nov-17 9:37 
QuestionMore a tip than an article Pin
User 110609793-Feb-17 4:03
User 110609793-Feb-17 4:03 
Questionnice Pin
Engr. Muhammad Enayet Hossain Abdullah 22-Oct-16 19:48
professionalEngr. Muhammad Enayet Hossain Abdullah 22-Oct-16 19:48 
PraiseMy vote of 5 Pin
Rohit Sardana28-Sep-16 0:52
professionalRohit Sardana28-Sep-16 0:52 
QuestionDoesn't seem necessary Pin
d.shapiro20-Sep-16 5:59
d.shapiro20-Sep-16 5:59 
AnswerRe: Doesn't seem necessary Pin
Tanvir Ahmad Arjel20-Sep-16 18:28
Tanvir Ahmad Arjel20-Sep-16 18:28 
AnswerRe: Doesn't seem necessary Pin
PersianAnna27-Sep-16 3:16
PersianAnna27-Sep-16 3:16 
GeneralMy vote of 5 Pin
Md. Rashidul Hasan Masum18-Sep-16 6:51
professionalMd. Rashidul Hasan Masum18-Sep-16 6:51 
GeneralRe: My vote of 5 Pin
Tanvir Ahmad Arjel19-Sep-16 5:10
Tanvir Ahmad Arjel19-Sep-16 5:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.