Dot-Net
在哪里為實體執行重複檢查
我正在尋找有關在 MVC 應用程序中使用 Entity Framework Code-First 時放置驗證邏輯的“最佳”位置的建議,例如對實體的重複檢查。
使用一個簡單的例子:
public class JobRole { public int Id { get; set; } public string Name { get; set; } }規則是“名稱”欄位必須是唯一的。
當我添加一個新的 JobRole 時,很容易在 Job Role Repository 中執行檢查 Name 不存在。
但是,如果使用者編輯了現有的 JobRole,並且不小心將 Name 設置為已經存在的名稱,我該如何檢查呢?
問題是儲存庫上不需要“更新”方法,因為工作角色實體會自動檢測更改,因此在嘗試保存之前沒有邏輯位置進行此檢查。
到目前為止,我已經考慮了兩種選擇:
- 覆蓋 DbContext 上的 ValidateEntry 方法,然後在使用 EntityState.Modified 保存 JobRole 實體時,執行重複檢查。
- 在嘗試保存之前創建某種從控制器呼叫的重複檢查服務。
兩者似乎都不理想。使用 ValidateEntry 似乎很晚(就在保存之前)並且很難測試。使用 Service 可能會導致有人忘記從 Controller 呼叫它,從而導致重複數據通過。
有沒有更好的辦法?
您對 ValidateEntity 的問題似乎是驗證發生在 SaveChanges 上,這對您來說為時已晚。但是在 Entity Framework 5.0 中,如果您希望使用DbContext.GetValidationErrors ,您可以更早地呼叫驗證。當然,您也可以直接呼叫DbContext.ValidateEntity。我就是這樣做的:
- 覆蓋上的
ValidateEntity方法DbContext:protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items) { //base validation for Data Annotations, IValidatableObject var result = base.ValidateEntity(entityEntry, items); //You can choose to bail out before custom validation //if (result.IsValid) // return result; CustomValidate(result); return result; } private void CustomValidate(DbEntityValidationResult result) { ValidateOrganisation(result); ValidateUserProfile(result); } private void ValidateOrganisation(DbEntityValidationResult result) { var organisation = result.Entry.Entity as Organisation; if (organisation == null) return; if (Organisations.Any(o => o.Name == organisation.Name && o.ID != organisation.ID)) result.ValidationErrors .Add(new DbValidationError("Name", "Name already exists")); } private void ValidateUserProfile(DbEntityValidationResult result) { var userProfile = result.Entry.Entity as UserProfile; if (userProfile == null) return; if (UserProfiles.Any(a => a.UserName == userProfile.UserName && a.ID != userProfile.ID)) result.ValidationErrors.Add(new DbValidationError("UserName", "Username already exists")); }
- 嵌入
Context.SaveChanges一個 try catch 並創建一個訪問方法Context.GetValidationErrors()。這是在我的UnitOfWork課上:public Dictionary<string, string> GetValidationErrors() { return _context.GetValidationErrors() .SelectMany(x => x.ValidationErrors) .ToDictionary(x => x.PropertyName, x => x.ErrorMessage); } public int Save() { try { return _context.SaveChanges(); } catch (DbEntityValidationException e) { //http://blogs.infosupport.com/improving-dbentityvalidationexception/ var errors = e.EntityValidationErrors .SelectMany(x => x.ValidationErrors) .Select(x => x.ErrorMessage); string message = String.Join("; ", errors); throw new DataException(message); } }
- 在我的控制器中,
GetValidationErrors()在將實體添加到上下文之後但之前呼叫SaveChanges():[HttpPost] public ActionResult Create(Organisation organisation, string returnUrl = null) { _uow.OrganisationRepository.InsertOrUpdate(organisation); foreach (var error in _uow.GetValidationErrors()) ModelState.AddModelError(error.Key, error.Value); if (!ModelState.IsValid) return View(); _uow.Save(); if (string.IsNullOrEmpty(returnUrl)) return RedirectToAction("Index"); return Redirect(returnUrl); }我的基礎儲存庫類實現
InsertOrUpdate如下:protected virtual void InsertOrUpdate(T e, int id) { if (id == default(int)) { // New entity context.Set<T>().Add(e); } else { // Existing entity context.Entry(e).State = EntityState.Modified; } }我仍然建議向數據庫添加唯一約束,因為這絕對可以保證您的數據完整性並提供可以提高效率的索引,但是覆蓋 ValidateEntry 可以控制驗證發生的方式和時間。