在哪里对实体运行重复检查 [英] Where to run a duplicate check for an entity

查看:18
本文介绍了在哪里对实体运行重复检查的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在寻找关于在 MVC 应用程序中使用实体框架代码优先时放置验证逻辑的最佳"位置的建议,例如实体的重复检查.

I'm looking for advice on the "best" place to put validation logic, such as a duplicate check for an entity, when using Entity Framework Code-First, in an MVC application.

举个简单的例子:

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

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

The rule is that the "Name" field must be unique.

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

When I add a new JobRole, it is easy to run a check in the Job Role Repository that the Name doesn't already exist.

但是如果用户编辑一个现有的 JobRole,并且不小心将 Name 设置为已经存在的名称,我该如何检查?

But if a user edits an existing JobRole, and accidentally sets the Name to one that already exists, how can I check this?

问题是存储库上不需要更新"方法,因为工作角色实体会自动检测更改,因此在尝试保存之前没有进行此检查的合乎逻辑的地方.

The issue is that there doesn't need to be an "update" method on the Repository, as the Job Role Entity will automatically detect changes, so there isn't a logical place to do this check before attempting to save.

到目前为止,我已经考虑了两种选择:

I have considered two options so far:

  1. 覆盖 DbContext 上的 ValidateEntry 方法,然后当 JobRole 实体与 EntityState.Modified 一起保存时,然后运行重复检查.
  2. 在尝试保存之前创建某种从控制器调用的重复检查服务.

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

Neither really seems ideal. Using ValidateEntry seems rather late (just before save) and hard to test. Using a Service leaves the possibility that someone forgets to call it from a Controller, letting duplicate data through.

有更好的方法吗?

推荐答案

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

Your problem with ValidateEntity appears to be that the validation occurs on SaveChanges and this is too late for you. But in Entity Framework 5.0 you can call the validation earlier if you wish using DbContext.GetValidationErrors. And of course you could also just call DbContext.ValidateEntity directly. This is how I do it:

  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"));
}

  • 在 try catch 中嵌入 Context.SaveChanges 并创建访问 Context.GetValidationErrors() 的方法.这是在我的 UnitOfWork 类中:

  • Embed Context.SaveChanges in a try catch and create a method to access Context.GetValidationErrors(). This is in my UnitOfWork class:

    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);
        }
    }
    

  • 在我的控制器中,在将实体添加到上下文之后但在 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 如下:

    My base repository class implements InsertOrUpdate like this:

        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 可以控制验证发生的方式和时间.

    I still recommend adding a unique constraint to the database because that will absolutely guarantee your data integrity and provide an index that can improve the efficiency, but overriding ValidateEntry gives loads of control over how and when validation occurs.

    这篇关于在哪里对实体运行重复检查的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆