在尝试使用EF CodeFirst/Mvc3为我的db实现唯一键验证的过程中,我发现了这篇文章http://blogs.msdn.com/b/adonet/archive/2011/05/27/ef-4-1-validation.aspx,其中给出了如何通过使用IValidateObject
为我的对象模型进行验证的示例:
public class Category : IValidatableObject
{
public int CategoryID { get; set; }
public string CategoryName { get; set; }
public string Description { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var testContext = (TestContext)validationContext.Items["Context"];
if (testContext.Categories.Any(
c => c.CategoryName == CategoryName && c.CategoryID != CategoryID))
{
yield return new ValidationResult("A category with the same name already exists!", new[] { "CategoryName" });
}
yield break;
}
}
和重写DbEntityValidationResult ValidateEntity
:
public class TestContext : DbContext
{
public DbSet<Test.Models.Category> Categories { get; set; }
protected override DbEntityValidationResult ValidateEntity( DbEntityEntry entityEntry, IDictionary<object, object> items)
{
var myItems = new Dictionary<object, object>();
myItems.Add("Context", this);
return base.ValidateEntity(entityEntry, myItems);
}
}
和控制器上的动作
[HttpPost]
public ActionResult Create(Category category)
{
if (ModelState.IsValid) {
categoryRepository.InsertOrUpdate(category);
categoryRepository.Save();
return RedirectToAction("Index");
} else {
return View();
}
}
但是我得到了错误:"The given key was not present in the dictionary."
为行
var testContext = (TestContext)validationContext.Items["Context"];
似乎对象上的Validate正在被调用,它在override ValidateEntity代码中设置之前访问"Context"。
起初我认为它可能是ModelState。Isvalid触发validate太早,但没有。
谁知道我在这里错过了什么或我做错了什么?
Model.IsValid
肯定触发得太早了,也许是别的什么。IValidatableObject
是MVC和EF使用的全局接口,但是DbContext
中的方法只有在上下文中调用SaveChanges
时才会调用,因此在调用SaveChanges
之前使用IValidatableObject
将导致异常。如果希望以这种方式验证实体,则必须使用另一种方法。例如,在HttpContext.Items
中存储上下文-您可以创建自定义动作过滤器,并在操作调用之前实例化和存储上下文,并在操作调用后处理上下文-希望它能解决所有问题。
我也面临着同样的问题…然后在谷歌上搜索了很多之后,我终于找到了这个:
练习3:使用IValidatableObject自定义验证
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
MusicStoreEntities storeDB = new MusicStoreEntities();
if (storeDB.Albums.Any(
a => a.Title.Trim().ToUpper() == this.Title.Trim().ToUpper() &&
a.ArtistId == (int)this.ArtistId))
{
yield return new ValidationResult("Existing Album", new string[] { "Title" });
}
}
正如你在他们的例子中看到的,他们实例化了一个新的Context
,因此不需要validationContext.Items["Context"];
。