.net - 在哪里对实体运行重复检查

标签 .net asp.net-mvc entity-framework validation design-patterns

我正在寻找有关在 MVC 应用程序中使用 Entity Framework 代码优先时放置验证逻辑的“最佳”位置的建议,例如对实体的重复检查。

使用一个简单的例子:

public class JobRole
{
  public int Id { get; set; }        
  public string Name { get; set; }
}

规则是“名称”字段必须是唯一的。

当我添加一个新的JobRole时,可以很容易地在Job Role Repository中运行检查来确定该名称尚不存在。

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

问题是存储库上不需要有“更新”方法,因为作业角色实体会自动检测更改,因此在尝试保存之前没有逻辑位置可以执行此检查。

到目前为止我考虑过两种选择:

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

两者似乎都不理想。使用 ValidateEntry 似乎相当晚(就在保存之前)并且难以测试。使用服务可能会导致有人忘记从 Controller 调用它,从而导致重复数据通过。

有更好的方法吗?

最佳答案

您的 ValidateEntity 问题似乎是验证发生在 SaveChanges 上,这对您来说为时已晚。但在 Entity Framework 5.0 中,如果您希望使用 DbContext.GetValidationErrors ,您可以提前调用验证。 。当然您也可以调用DbContext.ValidateEntity直接地。我就是这样做的:

  1. 重写 DbContext 上的 ValidateEntity 方法:

    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"));
    }
    
  2. 在 try catch 中嵌入 Context.SaveChanges 并创建一个方法来访问 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);
        }
    }
    
  3. 在我的 Controller 中,在将实体添加到上下文之后但在 SaveChanges() 之前调用 GetValidationErrors():

    [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 可以对验证的方式和时间进行大量控制。

关于.net - 在哪里对实体运行重复检查,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9603131/

相关文章:

c# - 在没有 Entity Framework 和迁移的 ASP.NET Core MVC 应用程序中使用 ASP.NET Identity

C# 抽象类命名约定

asp.net-mvc - vNext 和 System.Web 依赖项中的 ASP.NET MVC

c# - WCF,更改端点的 baseAdress

c# - 在 .NET MVC 应用程序中处理 JWT 过期

asp.net - MVC ApiController JSON 对象根

c# - 为什么 Entity Framework 会检测已修改但已重置的属性的更改?

c# - 使用 Linq to Entities 读取 byte[] 或 float

C# 抽象类,使用匿名而不是声明具体类?

c# - 用于共享第 3 方 DLL 的 NuGet 或程序集文件夹?