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可以以某种方式告诉数据库在数据库中的字段上设置唯一约束吗?
最简单的方法 - 什么是最佳做法?
答案 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
答案 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上,如果不更改唯一字段,则会引发错误。
我需要唯一的书名。这是我的解决方法:
代码:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Book>()
.HasIndex(u => u.Name)
.IsUnique();
}
代码:
// 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;
}
}
创建模型:
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";