23

ASP.NET MVC 4、EF5、代码优先、SQL Server 2012 Express

在模型中强制执行唯一值的最佳实践是什么?我有一个地方类,它有一个“url”属性,每个地方都应该是唯一的。

public class Place
{
      [ScaffoldColumn(false)]
      public virtual int PlaceID { get; set; }

      [DisplayName("Date Added")]
      public virtual DateTime DateAdded { get; set; }

      [Required(ErrorMessage = "Place Name is required")]
      [StringLength(100)]
      public virtual string Name { get; set; }

      public virtual string URL { get; set; }
};

为什么不能在其上放置一个 [Unique] 数据注释?

我已经看到了 1 或 2 次关于此的讨论,但没有谈论最佳实践。使用 Code First,您能以某种方式告诉数据库在数据库中的字段上设置唯一约束吗?

什么是最简单的方法 - 什么是最佳实践?

4

7 回答 7

35

尽管听起来很疯狂,但如今最好的做法是使用内置验证,而是使用FluentValidation。然后代码将非常易于阅读和超级维护,因为验证将在单独的类上进行管理,这意味着更少的意大利面条代码。

您要实现的目标的伪示例。

[Validator(typeof(PlaceValidator))]
class Place
{
    public int Id { get; set; }
    public DateTime DateAdded { get; set; }
    public string Name { get; set; }
    public string Url { get; set; }
}

public class PlaceValidator : AbstractValidator<Place>
{
    public PlaceValidator()
    {
        RuleFor(x => x.Name).NotEmpty().WithMessage("Place Name is required").Length(0, 100);
        RuleFor(x => x.Url).Must(BeUniqueUrl).WithMessage("Url already exists");
    }

    private bool BeUniqueUrl(string url)
    {
        return new DataContext().Places.FirstOrDefault(x => x.Url == url) == null
    }
}
于 2013-05-21T20:52:36.940 回答
7

此链接可能会有所帮助: https ://github.com/fatihBulbul/UniqueAttribute

[Table("TestModels")]
public class TestModel
{

    [Key]
    public int Id { get; set; }

    [Display(Name = "Some", Description = "desc")]
    [Unique(ErrorMessage = "This already exist !!")]
    public string SomeThing { get; set; }
}
于 2015-09-10T19:12:51.330 回答
5

唯一的方法是在生成迁移后更新迁移,假设您正在使用它们,以便它对列强制执行唯一约束。

public override void Up() {
  // create table
  CreateTable("dbo.MyTable", ...;
  Sql("ALTER TABLE MyTable ADD CONSTRAINT U_MyUniqueColumn UNIQUE(MyUniqueColumn)");
}
public override void Down() {
  Sql("ALTER TABLE MyTable DROP CONSTRAINT U_MyUniqueColumn");
}

然而,最困难的一点是在访问数据库之前在代码级别强制执行约束。为此,您可能需要一个包含唯一值完整列表的存储库,并确保新实体不会通过工厂方法违反它。

// Repository for illustration only
public class Repo {
  SortedList<string, Entity1> uniqueKey1 = ...; // assuming a unique string column 
  public Entity1 NewEntity1(string keyValue) {
    if (uniqueKey1.ContainsKey(keyValue) throw new ArgumentException ... ;
    return new Entity1 { MyUniqueKeyValue = keyValue };
  }
}

参考:

脚注:

在代码中首先有很多对 [Unique] 的请求,但看起来它甚至没有制作版本 6:http ://entityframework.codeplex.com/wikipage?title=Roadmap

您可以尝试在这里投票:http: //data.uservoice.com/forums/72025-entity-framework-feature-suggestions/suggestions/1050579-unique-constraint-ie-candidate-key-support

于 2013-05-21T20:16:53.133 回答
3

您可以在将数据保存到数据库表之前在代码级别进行此检查。

您可以尝试使用视图模型上的Remote数据注释进行异步验证,以使 UI 响应更快。

public class CreatePlaceVM
{
  [Required]
  public string PlaceName { set;get;}

  [Required]
  [Remote("IsExist", "Place", ErrorMessage = "URL exist!")
  public virtual string URL { get; set; }
}

确保您的IsExists操作方法中有一个Placecontroller接受URL参数的操作方法,并再次检查您的表并返回 true 或 false。

这个msdn 链接有一个示例程序来展示如何实现 Remote 属性来进行即时验证。

此外,如果您使用的是存储过程(出于某种原因),您可以在查询EXISTS之前在那里进行检查INSERT

于 2013-05-21T20:24:40.913 回答
2

我解决了在验证流程中启用构造函数注入的一般问题,集成到正常的 DataAnnotations 机制中,而无需在此答案中求助于框架,从而可以编写:

class MyModel 
{
    ...
    [Required, StringLength(42)]
    [ValidatorService(typeof(MyDiDependentValidator), ErrorMessage = "It's simply unacceptable")]
    public string MyProperty { get; set; }
    ....
}

public class MyDiDependentValidator : Validator<MyModel>
{
    readonly IUnitOfWork _iLoveWrappingStuff;

    public MyDiDependentValidator(IUnitOfWork iLoveWrappingStuff)
    {
        _iLoveWrappingStuff = iLoveWrappingStuff;
    }

    protected override bool IsValid(MyModel instance, object value)
    {
        var attempted = (string)value;
        return _iLoveWrappingStuff.SaysCanHazCheez(instance, attempted);
    }
}

使用一些帮助类(看那里),您可以在 ASP.NET MVC 中将其连接起来,如下所示Global.asax:-

DataAnnotationsModelValidatorProvider.RegisterAdapterFactory(
    typeof(ValidatorServiceAttribute),
    (metadata, context, attribute) =>
        new DataAnnotationsModelValidatorEx(metadata, context, attribute, true));
于 2016-02-05T10:17:53.107 回答
0

在我的 ASP.NET Razor Page Project 中遇到了类似的问题。创建自定义 UniqueDataAttribute 不起作用,因为在编辑时,如果您不更改唯一字段,它将引发错误。

我需要唯一的书名。我就是这样解决的:

  1. 我通过 EF Core 迁移向数据库中的字段添加了唯一约束。在 ApplicationDbContext 类中添加了以下内容,然后运行迁移。

代码:

protected override void OnModelCreating(ModelBuilder builder)
        {
            builder.Entity<Book>()
                .HasIndex(u => u.Name)
                .IsUnique();
        }
  1. 接下来,创建如下的帮助器/扩展方法。

代码:

        // Validate uniqueness of Name field in database.
        // If validation is done on existing record, pass the id of the record.
        // Else, if validating new record Name, then id is set to dummy key integer -1
        public static bool UniqueNameInDb(this string data, ApplicationDbContext db, int id = -1)
        {
            var duplicateData = from o in db.Book
                                where o.Name == data && o.Id != id
                                select o;
            if(duplicateData.Any())
            {
                return false;
            }
            return true;
        }
    }
  1. 然后在 OnPost() 方法中的创建和编辑页面模型中使用它,如下所示。

创建模型:

public async Task<IActionResult> OnPost()
        {
            if(ModelState.IsValid)
            {
                if (!Book.Name.UniqueNameInDb(_db)) //<--Uniqueness validation
                {
                    ModelState.AddModelError("Book.Name", "Name already exist"); //<-- Add error to the ModelState, that would be displayed in view.
                    return Page();
                }

                await _db.Book.AddAsync(Book);
                await _db.SaveChangesAsync();

                return RedirectToPage("Index");

            }
            else
            {
                return Page();
            }
        }

编辑模型:

public async Task<IActionResult> OnPost()
        {
            if(ModelState.IsValid)
            {
                var bookFromDb = await _db.Book.FindAsync(Book.Id);
                if (!Book.Name.UniqueNameInDb(_db, Book.Id)) //<--Uniqueness validation
                {
                    ModelState.AddModelError("Book.Name", "Name already exist"); //<-- Add error to the ModelState, that would be displayed in view.
                    return Page();
                }
                bookFromDb.Name = Book.Name;
                bookFromDb.Author = Book.Author;

                await _db.SaveChangesAsync();

                return RedirectToPage("Index");
            }

            return Page();
        }

PS:您的 Razor 视图应该在表单中设置模型验证以捕获并显示错误。

IE,

<div class="text-danger" asp-validation-summary="ModelOnly"></div>

并低于针对该领域的验证。

<span asp-validation-for="Book.Name" class="text-danger"></span>
于 2020-04-17T06:49:26.620 回答
0

好吧,这很简单,但是如果这有效与否,我想知道。只需在添加新用户之前检查电子邮件是否已经存在。

if (!db.Users.Any(x => x.Email == data.Email))
 // your code for adding
else
 // send a viewbag to the view
 //  ViewBag.error = "Email Already Exist";
于 2020-08-31T11:50:11.040 回答