在 ASP.NET MVC Code First 中为模型属性实现唯一性或唯一键属性的最佳方法:第 1 部分






4.98/5 (12投票s)
除了主键之外,还有很多方法可以为模型属性实现唯一性或唯一键属性,但我将在本系列的两部分中讨论两种可能最好且最简单的方法。这是第 1 部分。
[最后更新消息]
在此更新之前,很遗憾有一个严重的问题被忽略了,即该方法在创建新实体时工作正常,但在编辑/更新现有实体时工作不正常。它不允许在更新其他字段时传递唯一字段的原始值。此更新已修复此问题。
[前言]
首先,我撰写了一篇关于在 ASP.NET MVC Code First 中实现模型属性的唯一性或唯一键属性的两种方法的文章。这些方法是:
- 使用
IValidatableObject
接口 和 - 使用 Remote Validation Attribute。
第一种方法没有问题,但突然**我意识到第二种方法存在漏洞**。因为第二种方法是基于 JavaScript 的客户端验证。因此,如果任何恶意用户试图插入重复值,他可以通过禁用浏览器中的 JavaScript 来做到这一点。所以我必须通过在方法 2 中添加服务器端验证以及现有的客户端验证来处理这种情况。
为了使文章篇幅合理,我决定将文章分为两部分,其中:
- 第 1 部分是方法 1,即使用
IValidatableObject
接口,以及 - 第 2 部分是方法 2,即使用 Remote Validation Attribute 以及自定义服务器端验证(在禁用 JavaScript 的情况下)。
引言
有时,我们需要不允许数据库表中某个列或属性出现重复值,例如:对于数据库表中的用户名列或属性,我们不应允许用户插入数据库表中已存在的用户名,因为用户名是唯一值。
开始吧!
假设我们有一个库存,其中有一个 Product
表/类,用于跟踪所有 product
作为 product
列表。因此,不允许用户插入表中已存在的 product
名称是合理的。以下是我们将在本文中使用的 product
模型类。
public class Product
{
public int Id { get; set; }
public string ProductName { get; set; }
public int ProductQuantity { get; set; }
public decimal UnitPrice { get; set; }
}
方法 1:使用 IValidatableObject 接口
步骤 1:向 ProductName 属性添加带有 "IsUnique" 参数的 "Index" 属性。请记住!使用 "Index" 属性时,还必须使用 "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; }
}
}
在此更改之前,如果数据库中已存在 Product
类的 Product
表,则使用 code first migration 更新数据库中的更改,或者如果现有数据没有问题,可以直接删除表。
现在运行项目,尝试插入一个 product
名称已存在于表中的 product
实体。您将收到一个异常,错误消息如下:
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.
这对客户端用户来说毫无意义。因此,您必须处理异常,以便向客户端用户提供有意义的消息。
步骤 2:现在您必须继承 IValidatableObject 接口,并提供 IValidatableObject 接口的 Validate() 方法的实现。
完成此操作后,Product
类将如下所示:
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;
}
}
}
到目前为止,在仅创建新实体的情况下,它工作正常,但在编辑/更新现有实体时工作不正常。它不允许在更新其他字段时传递唯一字段的原始值。要传递唯一字段的原始值,请按如下方式修改 validate() 方法:
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;
}
}
您也可以使用更简化的形式来编写 validate()
方法,如下所示:
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;
}
}
在运行项目之前,请记住,您的 controller
类中的 db.SaveChanges()
方法必须在 "ModelState.IsValid
" 检查中。您的 Create
和 Edit
/Update
方法应如下所示:
[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);
}
现在运行项目,尝试插入一个 product
名称已存在于表中的 product
实体。您将收到如下验证错误消息:
现在,您还将能够编辑/更新现有实体/记录的任何字段,同时满足验证逻辑。
第 1 部分方法 1 完成!这里是 第 2 部分方法 2。
结论
这是我在 Code Project 上的第一篇文章。所以可能有一些错误。如果发现任何错误,请随时纠正。如果您有任何建议,请不要忘记评论。感谢您的阅读!!