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

时间:2022-09-18 08:10:31

ASP.NET MVC 4, EF5, Code First, SQL Server 2012 Express

ASP.NET MVC 4,EF5,Code First,SQL Server 2012 Express

What is best practice to enforce a unique value in a model? I have a places class that has a 'url' property that should be unique for every place.

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

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. Using Code First can you somehow tell the database to set a unique constraint on the field in the database?

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

What is easiest way - and what is best practice?

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

5 个解决方案

#1


23  

As crazy as it might sound the best practice nowadays is to not use built-in validation and instead use 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.

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

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

#2


6  

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

#3


4  

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 = Routemap

You could try voting for it here: http://data.uservoice.com/forums/72025-entity-framework-feature-suggestions/suggestions/1050579-unique-constraint-i-e-candidate-key-support

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

#4


3  

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.

您可以尝试在viewmodel上使用远程数据注释进行异步验证,以使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.

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

This msdn link has a sample program to show how to implement Remote attribute to do instant validation.

此msdn链接有一个示例程序,用于显示如何实现Remote属性以进行即时验证。

Also, If you are using a Stored procedure (For some reason), you can do an EXISTS check there before the INSERT query.

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

#5


1  

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 e.g. 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));

#1


23  

As crazy as it might sound the best practice nowadays is to not use built-in validation and instead use 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.

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

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

#2


6  

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

#3


4  

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 = Routemap

You could try voting for it here: http://data.uservoice.com/forums/72025-entity-framework-feature-suggestions/suggestions/1050579-unique-constraint-i-e-candidate-key-support

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

#4


3  

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.

您可以尝试在viewmodel上使用远程数据注释进行异步验证,以使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.

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

This msdn link has a sample program to show how to implement Remote attribute to do instant validation.

此msdn链接有一个示例程序,用于显示如何实现Remote属性以进行即时验证。

Also, If you are using a Stored procedure (For some reason), you can do an EXISTS check there before the INSERT query.

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

#5


1  

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 e.g. 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));