[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:
- Using
IValidatableObject
Interface and
- 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:
- Part 1 will be the Method 1 that is by Using
IValidatableObject
Interface and
- 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 product
s as a list of product
s. 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!!