简体   繁体   English

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

[英]ASP.NET MVC 4, EF5, Unique property in model - best practice?

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

What is best practice to enforce a unique value in a model?在 model 中实施唯一值的最佳做法是什么? I have a places class that has a 'url' property that should be unique for every place.我有一个地方 class,它有一个“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; }
};

Why isn't there just a [Unique] data annotation you can place on it?为什么不能在上面放置一个 [Unique] 数据注释?

I have seen 1 or 2 discussions on this, but no talk of best practice.我已经看到过 1 或 2 次讨论,但没有讨论最佳实践。 Using Code First can you somehow tell the database to set a unique constraint on the field in the database?使用 Code First,您能以某种方式告诉数据库在数据库中的字段上设置唯一约束吗?

What is easiest way - and what is best practice?什么是最简单的方法 - 什么是最佳实践?

As crazy as it might sound the best practice nowadays is to not use built-in validation and instead use FluentValidation .尽管听起来很疯狂,但现在的最佳实践是使用内置验证,而是使用FluentValidation Then the code will be very easy to read and super-maintainable since validation will be managed on separate class meaning less spaghetti code.然后代码将非常易于阅读和超级可维护,因为验证将在单独的类上进行管理,这意味着更少的意大利面条式代码。

Pseudo-example of what you are trying to achieve.您要实现的目标的伪示例。

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

This link might help: https://github.com/fatihBulbul/UniqueAttribute此链接可能会有所帮助: 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; }
}

The only way is to update your migration once you generate it, assuming you are using them, so that it enforces a unique constraint on the column.唯一的方法是在生成迁移后更新迁移(假设您正在使用它们),以便它对列实施唯一约束。

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

The hard bit, though, is enforcing the constraint at the code level before you get to the database.但是,难点在于在访问数据库之前在代码级别强制执行约束。 For that you might need a repository that contains the complete list of unique values and makes sure that new entities don't violate that through a factory method.为此,您可能需要一个包含唯一值完整列表的存储库,并确保新实体不会通过工厂方法违反该列表。

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

References:参考:

Footnote:脚注:

There are a lot of requests for [Unique] in code first, but it looks like it isn't even making version 6: http://entityframework.codeplex.com/wikipage?title=Roadmap首先在代码中有很多对 [Unique] 的请求,但看起来它甚至没有制作第 6 版: http : //entityframework.codeplex.com/wikipage?title=Roadmap

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

You may do this checking in the code level before saving the data to the Database tables.您可以在将数据保存到数据库表之前在代码级别执行此检查。

You can try using the Remote data annotation on your viewmodel to do an asynchronous validation to make the UI more responsive.您可以尝试在您的视图模型上使用Remote数据注释来进行异步验证,以使 UI 更具响应性。

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

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

Make sure you have an IsExists action method in your Placecontroller which accepts a URL paramtere and check it againist your table and return true or false.请确保你有一个IsExists在你的操作方法Placecontroller它接受一个URL paramtere并检查它againist你的表并返回true或false。

This msdn link has a sample program to show how to implement Remote attribute to do instant validation.这个msdn 链接有一个示例程序来展示如何实现远程属性来进行即时验证。

Also, If you are using a Stored procedure ( For some reason ), you can do an EXISTS check there before the INSERT query.此外,如果您正在使用存储过程(出于某种原因),您可以在INSERT查询之前在那里进行EXISTS检查。

I solved the general problem of enabling constructor injection in your Validation flow, integrating into the normal DataAnnotations mechanism without resorting to frameworks in this answer , enabling one to write:我解决了在验证流程中启用构造函数注入的一般问题,集成到正常的 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);
    }
}

With some helper classes (look over there), you wire it up eg in ASP.NET MVC like so in the Global.asax :-使用一些辅助类(看那边),您将它连接起来,例如在 ASP.NET MVC 中,就像在Global.asax :-

DataAnnotationsModelValidatorProvider.RegisterAdapterFactory(
    typeof(ValidatorServiceAttribute),
    (metadata, context, attribute) =>
        new DataAnnotationsModelValidatorEx(metadata, context, attribute, true));

Faced similar issue in my ASP.NET Razor Page Project.在我的 ASP.NET Razor Page 项目中遇到了类似的问题。 Creating custom UniqueDataAttribute didn't work, because on Edit, it would throw an error if you're not changing unique field.创建自定义 UniqueDataAttribute 不起作用,因为在编辑时,如果您不更改唯一字段,则会引发错误。

I needed unique Book Name.我需要独特的书名。 This is how I resolved:我是这样解决的:

  1. I added unique constraint to the field in database via EF Core migrations.我通过 EF Core 迁移向数据库中的字段添加了唯一约束。 Added following in ApplicationDbContext class and then ran migration.在 ApplicationDbContext 类中添加以下内容,然后运行迁移。

Code:代码:

protected override void OnModelCreating(ModelBuilder builder)
        {
            builder.Entity<Book>()
                .HasIndex(u => u.Name)
                .IsUnique();
        }
  1. Next, created helper/extension method as follows.接下来,创建助手/扩展方法如下。

Code:代码:

        // 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. Then used it in Create and Edit page model in OnPost() method as follows.然后在 OnPost() 方法中的创建和编辑页面模型中使用它,如下所示。

Create model:创建模型:

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

Edit Model:编辑模型:

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: Your Razor view should've Model validation set on in the form to capture and display the error. PS:您的 Razor 视图应该在表单中设置模型验证以捕获和显示错误。

ie, IE,

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

and below validation against the field.和以下针对该领域的验证。

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

well it's simple but idk if this is efficient or not.好吧,这很简单,但不知道这是否有效。 Just check before adding a new user whether the email already exists or not.在添加新用户之前检查电子邮件是否已经存在。

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

To achieve Unique for multiple Columns实现多列Unique

            modelBuilder.Entity<DataClass>()
            .HasIndex(u => new
            {
                u.col1,
                u.col2
            })
            .IsUnique();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM