Dot-Net

在哪里為實體執行重複檢查

  • January 22, 2014

我正在尋找有關在 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 設置為已經存在的名稱,我該如何檢查呢?

問題是儲存庫上不需要“更新”方法,因為工作角色實體會自動檢測更改,因此在嘗試保存之前沒有邏輯位置進行此檢查。

到目前為止,我已經考慮了兩種選擇:

  1. 覆蓋 DbContext 上的 ValidateEntry 方法,然後在使用 EntityState.Modified 保存 JobRole 實體時,執行重複檢查。
  2. 在嘗試保存之前創建某種從控制器呼叫的重複檢查服務。

兩者似乎都不理想。使用 ValidateEntry 似乎很晚(就在保存之前)並且很難測試。使用 Service 可能會導致有人忘記從 Controller 呼叫它,從而導致重複數據通過。

有沒有更好的辦法?

您對 ValidateEntity 的問題似乎是驗證發生在 SaveChanges 上,這對您來說為時已晚。但是在 Entity Framework 5.0 中,如果您希望使用DbContext.GetValidationErrors ,您可以更早地呼叫驗證。當然,您也可以直接呼叫DbContext.ValidateEntity。我就是這樣做的:

  1. 覆蓋上的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"));
}
  1. 嵌入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);
   }
}
  1. 在我的控制器中,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 可以控制驗證發生的方式和時間。

引用自:https://stackoverflow.com/questions/9603131