在哪里运行实体的重复检查

App*_*ere 8 .net validation asp.net-mvc design-patterns entity-framework

我正在寻找关于在MVC应用程序中使用Entity Framework Code-First时验证逻辑的"最佳"位置的建议,例如对实体的重复检查.

使用一个简单的例子:

public class JobRole
{
  public int Id { get; set; }        
  public string Name { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

规则是"名称"字段必须是唯一的.

当我添加一个新的JobRole时,很容易在Job Role Repository中运行一个名称尚不存在的检查.

但是,如果用户编辑现有JobRole,并意外将名称设置为已存在的名称,我该如何检查?

问题是存储库中不需要"更新"方法,因为作业角色实体将自动检测更改,因此在尝试保存之前没有合理的位置来执行此检查.

到目前为止,我考虑了两个选项:

  1. 覆盖DbContext上的ValidateEntry方法,然后在使用EntityState.Modified保存JobRole实体时,运行重复检查.
  2. 在尝试保存之前,创建一些从Controller调用的重复检查服务.

两者都不是真的很理想.使用ValidateEntry似乎相当晚(在保存之前)并且难以测试.使用服务可能会有人忘记从控制器调用它,让重复的数据通过.

有没有更好的办法?

Dar*_*rov 6

执行此逻辑的最可靠的地方是数据库本身,它通过在name列上声明一个唯一的字段约束.当有人尝试插入或更新现有实体并尝试将其名称设置为现有名称时,将抛出一个约束违例异常,您可以在数据访问层中捕获并解释该异常.


Col*_*lin 5

您对ValidateEntity的问题似乎是在SaveChanges上进行验证,这对您来说太迟了.但是,如果您希望使用DbContext.GetValidationErrors,则可以在Entity Framework 5.0中更早地调用验证.当然你也可以直接调用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"));
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 嵌入Context.SaveChangestry 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);
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 在我的控制器中,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);
    }
    
    Run Code Online (Sandbox Code Playgroud)

我的基础存储库类实现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;
        }      
    }
Run Code Online (Sandbox Code Playgroud)

我仍然建议为数据库添加一个唯一约束,因为这绝对可以保证您的数据完整性并提供可以提高效率的索引,但是重写ValidateEntry可以控制验证的发生方式和时间.