ASP.NET MVC 4,EF5,模型中的独特属性 - 最佳实践?

时间:2013-05-21 20:09:31

标签: c# asp.net-mvc entity-framework asp.net-mvc-4 entity-framework-5

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

在模型中强制使用唯一值的最佳做法是什么?我有一个具有'url'属性的place类,对于每个地方都应该是唯一的。

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; }
};

为什么不存在可以放置的[唯一]数据注释?

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

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

6 个答案:

答案 0 :(得分:27)

听起来很疯狂,现在最好的做法是使用内置验证,而是使用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
    }
}

答案 1 :(得分: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-i-e-candidate-key-support

答案 2 :(得分:3)

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

您可以尝试在viewmodel上使用Remote数据注释进行异步验证,以使用户界面更具响应性。

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 link有一个示例程序,用于显示如何实现Remote属性以进行即时验证。

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

答案 3 :(得分:1)

我解决了在验证流程中启用构造函数注入的一般问题,将其集成到正常的DataAnnotations机制中,而不依赖于this answer中的框架,从而使其能够编写:

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));

答案 4 :(得分:0)

在我的ASP.NET Razor Page项目中遇到类似的问题。创建自定义的UniqueDataAttribute不起作用,因为在Edit上,如果不更改唯一字段,则会引发错误。

我需要唯一的书名。这是我的解决方法:

  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视图应在表单中启用“模型验证”功能,以捕获并显示错误。

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

以及下面针对该字段的验证。

<span asp-validation-for="Book.Name" class="text-danger"></span>

答案 5 :(得分:0)

这很简单,但是如果有效,则idk。只需在添加新用户之前检查电子邮件是否已经存在。

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";