65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.98/5 (12投票s)

2016年9月18日

CPOL

4分钟阅读

viewsIcon

58807

downloadIcon

306

除了主键之外,还有很多方法可以为模型属性实现唯一性或唯一键属性,但我将在本系列的两部分中讨论两种可能最好且最简单的方法。这是第 1 部分。

[最后更新消息]

在此更新之前,很遗憾有一个严重的问题被忽略了,即该方法在创建新实体时工作正常,但在编辑/更新现有实体时工作不正常。它不允许在更新其他字段时传递唯一字段的原始值。此更新已修复此问题。

[前言]

首先,我撰写了一篇关于在 ASP.NET MVC Code First 中实现模型属性的唯一性或唯一键属性的两种方法的文章。这些方法是:

  1. 使用 IValidatableObject 接口 和
  2. 使用 Remote Validation Attribute。

第一种方法没有问题,但突然**我意识到第二种方法存在漏洞**。因为第二种方法是基于 JavaScript 的客户端验证。因此,如果任何恶意用户试图插入重复值,他可以通过禁用浏览器中的 JavaScript 来做到这一点。所以我必须通过在方法 2 中添加服务器端验证以及现有的客户端验证来处理这种情况。

为了使文章篇幅合理,我决定将文章分为两部分,其中:

  1. 第 1 部分是方法 1,即使用 IValidatableObject 接口,以及
  2. 第 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" 检查中。您的 CreateEdit/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 上的第一篇文章。所以可能有一些错误。如果发现任何错误,请随时纠正。如果您有任何建议,请不要忘记评论。感谢您的阅读!!

© . All rights reserved.