ASP.NET MVC 4,EF5,模型中的唯一属性 - 最佳实践?
Posted
技术标签:
【中文标题】ASP.NET MVC 4,EF5,模型中的唯一属性 - 最佳实践?【英文标题】:ASP.NET MVC 4, EF5, Unique property in model - best practice? 【发布时间】:2013-05-16 17:29:55 【问题描述】:ASP.NET MVC 4、EF5、代码优先、SQL Server 2012 Express
在模型中强制执行唯一值的最佳做法是什么?我有一个地方类,它有一个“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;
;
为什么不能在其上放置一个 [唯一] 数据注释?
我已经看到 1 或 2 次关于此的讨论,但没有谈到最佳实践。使用 Code First,您能以某种方式告诉数据库在数据库中的字段上设置唯一约束吗?
什么是最简单的方法 - 什么是最佳实践?
【问题讨论】:
我现在使用(并推荐)Dapper.net 和数据注释的价值 - 喜欢它。 我不再首先使用代码 - 这似乎是一个已经死掉的想法。 【参考方案1】:尽管听起来很疯狂,但如今的最佳做法是不使用内置验证,而是使用 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
【讨论】:
有趣。所以我只需要将它用于任何唯一属性 - 其他所有内容都可以保持原样,传统的 Code First 对吗?这是否涵盖所有基础、竞争条件、数据库上 100% 可靠的唯一性等? 我还需要安装fluentvalidation nuget包吗? 这个例子可以毫无问题地使用 POCO,我在我所有的项目中都使用它。如您所见,它将检查TryValidateModel()
上的数据库中的唯一性,之后您通常会调用_db.SaveChanges();
,因此基本上没有任何延迟。是的,你当然需要 nuget 包。
谢谢。我注意到您已将“名称”添加为不为空。你能不能只在类定义中放一个 [Required] 数据注释?或者这会取代所有数据注释/完全阻止它们工作 - 无论是对于这个模型还是任何模型?
谁的最佳实践?也有人可能会争辩说,这是为了获得最小收益而需要承担的大量代码【参考方案2】:
此链接可能会有所帮助: 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】:唯一的方法是在生成迁移后更新迁移(假设您正在使用它们),以便对列实施唯一约束。
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 ;
参考资料:
Repository - Fowler(Repository 原出处) Repostory - MSDN Tutorial: Repository in MVC (www.asp.net) Singleton in C# - SO脚注:
有很多要求 [Unique] 在代码中优先,但看起来它甚至没有制作版本 6:http://entityframework.codeplex.com/wikipage?title=Roadmap
您可以尝试在这里投票:http://data.uservoice.com/forums/72025-entity-framework-feature-suggestions/suggestions/1050579-unique-constraint-i-e-candidate-key-support
【讨论】:
我从未使用过存储库或工厂 - 任何链接/教程推荐?我想我也可以“手动”验证新地点的用户输入以确保 URL 是唯一的?如果验证存在缺陷,是否可以在对象级别强制执行 - 比如在 SQL Server 中设置唯一约束(也许你的建议涵盖了这一点?)似乎有更好的解决方案,因为这是一个常见的要求? 注意 - 这是一个规模相对较小的应用程序,目前只有一个开发人员 - 所以一个简单/快速的解决方案会很好。我还需要为用户强制执行唯一字段 - 例如在他们的 Twitter 句柄或电子邮件中。 @user2254951。使用提供的代码和链接进行编辑。你需要一个公共的地方来检查唯一性,因此我的单例建议因为它会在 IIS 中的 appdomain 的持续时间内存在于内存中,你只需要在第一次查询时播种它,但这很容易。 @user2254951。小型应用程序可能会一直等到您写入数据库,但创建工厂方法相对容易,并且可以节省额外的代码,以便在您保存时从重复和异常中恢复。 谢谢 - 会看的。我还发现了在 Seed 方法中直接向数据库添加 UNIQUE 约束的建议 - 首先破坏代码的纯度,但应该有效?!【参考方案4】:您可以在将数据保存到数据库表之前在代码级别进行此检查。
您可以尝试在您的视图模型上使用 Remote
数据注释来进行异步验证,以使 UI 响应更快。
public class CreatePlaceVM
[Required]
public string PlaceName set;get;
[Required]
[Remote("IsExist", "Place", ErrorMessage = "URL exist!")
public virtual string URL get; set;
确保您的Placecontroller
中有一个IsExists
操作方法,它接受URL
参数并再次检查您的表格并返回true 或false。
这个msdn link 有一个示例程序来展示如何实现远程属性来进行即时验证。
另外,如果您使用的是存储过程(出于某种原因),您可以在 INSERT
查询之前进行 EXISTS
检查。
【讨论】:
这看起来很酷 - 我是否必须实现其他任何东西才能使其工作?如果另一个用户同时执行相同的查询怎么办 - 有什么方法可以防止更深/数据库级别的重复? 执行两次(一次使用上述方式(指示用户更改重复值),然后在保存之前的 HttpPost 操作方法中执行第二次(检查数据库以查看是否有该项目存在与否)。除非你有一些巨大的性能问题(我猜你没有),否则你不会有任何问题 是的,这样的检查应该在与插入新 URL 的代码的事务中,以确保没有竞争条件。打开事务,是否存在?,插入,提交【参考方案5】:我解决了在您的验证流程中启用构造函数注入的一般问题,集成到正常的 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));
【讨论】:
【参考方案6】:在我的 ASP.NET Razor 页面项目中遇到了类似的问题。创建自定义 UniqueDataAttribute 不起作用,因为在编辑时,如果您不更改唯一字段,它将引发错误。
我需要唯一的书名。我就是这样解决的:
-
我通过 EF Core 迁移向数据库中的字段添加了唯一约束。在 ApplicationDbContext 类中添加以下内容,然后运行迁移。
代码:
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;
-
然后在 OnPost() 方法的 Create and Edit page 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();
编辑模型:
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>
【讨论】:
【参考方案7】:好吧,这很简单,但是如果这有效与否,请不客气。只需在添加新用户之前检查电子邮件是否已存在即可。
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";
【讨论】:
以上是关于ASP.NET MVC 4,EF5,模型中的唯一属性 - 最佳实践?的主要内容,如果未能解决你的问题,请参考以下文章
ASP.NET MVC 4、Internet 应用程序模板、SimpleMembership - 帐户模型 - 强制执行唯一的用户名和电子邮件
将 DbDataReader 的结果转换为 ASP.NET MVC 4 中的数据库模型,来自使用 ADO.NET 的存储过程 [重复]