C# ASP.NET MVC 4、EF5、模型中的唯一属性 - 最佳实践?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16678625/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
ASP.NET MVC 4, EF5, Unique property in model - best practice?
提问by niico
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? I have a places class that has a 'url' property that should be unique for every place.
在模型中强制执行唯一值的最佳做法是什么?我有一个地方类,它有一个 '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. 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?
什么是最简单的方法 - 什么是最佳实践?
采纳答案by Stan
As crazy as it might sound the best practice nowadays is to notuse 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
}
}
回答by Andy Brown
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:
参考:
- Repository - Fowler(the original source of Repository)
- Repostory - MSDN
- Tutorial: Repository in MVC(www.asp.net)
- Singleton in C# - SO
- Repository - Fowler(Repository的原始来源)
- 存储库 - MSDN
- 教程:MVC 中的存储库(www.asp.net)
- C# 中的单例 - SO
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-i-e-candidate-key-support
您可以尝试在这里投票:http: //data.uservoice.com/forums/72025-entity-framework-feature-suggestions/suggestions/1050579-unique-constraint-ie-candidate-key-support
回答by Shyju
You may do this checking in the code level before saving the data to the Database tables.
您可以在将数据保存到数据库表之前在代码级别执行此检查。
You can try using the Remotedata 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 IsExistsaction method in your Placecontrollerwhich accepts a URLparamtere and check it againist your table and return true or false.
确保你有一个IsExists动作方法,Placecontroller它接受一个URL参数并再次检查你的表并返回真或假。
This msdn linkhas 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 EXISTScheck there before the INSERTquery.
此外,如果您使用的是存储过程(出于某种原因),您可以EXISTS在INSERT查询之前进行检查。
回答by Fatih Bülbül
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; }
}
回答by Ruben Bartelink
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));
回答by Meer Hussain Abrar
Faced similar issue in my ASP.NET Razor Page Project. Creating custom UniqueDataAttribute didn't work, because on Edit, it would throw an error if you're not changing unique field.
在我的 ASP.NET Razor Page 项目中遇到了类似的问题。创建自定义 UniqueDataAttribute 不起作用,因为在编辑时,如果您不更改唯一字段,则会引发错误。
I needed unique Book Name. This is how I resolved:
我需要独特的书名。我是这样解决的:
- I added unique constraint to the field in database via EF Core migrations. Added following in ApplicationDbContext class and then ran migration.
- 我通过 EF Core 迁移向数据库中的字段添加了唯一约束。在 ApplicationDbContext 类中添加以下内容,然后运行迁移。
Code:
代码:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Book>()
.HasIndex(u => u.Name)
.IsUnique();
}
- 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;
}
}
- 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 视图应该在表单中设置模型验证以捕获和显示错误。
i.e,
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>

