我正在寻找有关在 MVC 应用程序中使用 Entity Framework 代码优先时放置验证逻辑的“最佳”位置的建议,例如对实体的重复检查。
使用一个简单的例子:
public class JobRole
{
public int Id { get; set; }
public string Name { get; set; }
}
规则是“名称”字段必须是唯一的。
当我添加一个新的JobRole时,可以很容易地在Job Role Repository中运行检查来确定该名称尚不存在。
但是,如果用户编辑现有的 JobRole,并意外地将名称设置为已存在的名称,我该如何检查这一点?
问题是存储库上不需要有“更新”方法,因为作业角色实体会自动检测更改,因此在尝试保存之前没有逻辑位置可以执行此检查。
到目前为止我考虑过两种选择:
- 重写 DbContext 上的 ValidateEntry 方法,然后在使用 EntityState.Modified 保存 JobRole 实体时,运行重复检查。
- 在尝试保存之前创建某种重复检查服务,从 Controller 调用。
两者似乎都不理想。使用 ValidateEntry 似乎相当晚(就在保存之前)并且难以测试。使用服务可能会导致有人忘记从 Controller 调用它,从而导致重复数据通过。
有更好的方法吗?
最佳答案
您的 ValidateEntity 问题似乎是验证发生在 SaveChanges 上,这对您来说为时已晚。但在 Entity Framework 5.0 中,如果您希望使用 DbContext.GetValidationErrors ,您可以提前调用验证。 。当然您也可以调用DbContext.ValidateEntity直接地。我就是这样做的:
重写
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
类中: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); } }
在我的 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/