[英]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:
我是这样解决的:
Code:代码:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Book>()
.HasIndex(u => u.Name)
.IsUnique();
}
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;
}
}
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.