使用动态数据进行自定义高级实体验证

14

我正在寻找一种解决方案,以在用户在Entity Framework的动态数据屏幕中保存更改时执行一些自定义实体验证(需要数据库访问,跨成员验证等),
因为验证比使用属性要复杂得多(需要访问数据库等),所以无法用属性完成。

您能拦截SaveChanges调用吗?
我尝试在DbContext对象中覆盖ValidateEntity,但是Dynamic Data似乎没有调用它(可能是因为它使用内部ObjectContext,不确定原因),覆盖SaveChanges也没有帮助。
我看不到任何可以订阅的事件...

文档应该会有所帮助:

通过覆盖OnValidate方法或处理Validate事件来自定义单个数据字段的验证,当更改任何数据字段时都会调用这些方法。此方法允许您为单个字段添加验证和业务逻辑。此方法比为单个字段添加验证更通用。当可以将相同的验证逻辑应用于多个数据字段时,它非常有用。它还允许您执行涉及多个字段的验证检查。

但我正在使用POCO Entity Framework 6类,因此没有要覆盖的OnValidate方法,并且根据我所读的内容,这适用于LinqToSql,我找不到他们提到的Validate事件。

我尝试在DbContext的构造函数中订阅内部ObjectContext的SavingChanges事件,以手动调用ValidateEntity,但我不知道该怎么处理结果。如果我抛出DbEntityValidationException(或像这篇文章中建议的ValidationException),ASPNET会像处理任何异常一样处理它(黄屏)。

实现IValidatableObject也无法解决问题。

我还尝试了实现自己的DynamicValidator,但是没有成功,它似乎处理了异常(如果我重写ValidateException并放置一个断点,我可以看到它),但它仍然被传递到默认的错误处理程序,并显示黄色屏幕。我一定漏掉了什么。

那么,在保存动态数据/ EF之前,如何执行复杂验证(跨字段,带查询等)?

3个回答

4

我认为像你试图执行的逻辑不应该出现在你的架构中的这个层次。让数据库执行它应该执行的约束条件,例如外键等,并将您的业务逻辑放在上一层。例如,在您想要验证实体的情况下,您可以添加一个IsValidForAddOrUpdate()方法,其中包含您将放入验证器中的逻辑。然后只需利用新方法:

if (entity.IsValidForAddOrUpdate())
{
    db.Set<Entity>().Add(entity);
    db.SaveChanges()
}
else throw new DbValidationException("Entity failed validation due to rule xyz.");

我同意这个说法,我也认为业务逻辑不应该与实体框架耦合。 - Eldho
1
这是有争议的。也许一个领域层对象可以拥有验证功能。只要不使用可怕的“EF前面的存储库层”模式... - James
我同意,但动态数据并没有提供太多的验证选项。但问题不在于此,问题在于 DynamicValidator 没有捕获 DbValidationException(尽管我找到的文档和文章都说它应该这样做),所以我得到了一个黄色的屏幕。 现在,我甚至没有试图拥有一个良好的架构,只是想要有一个能工作的东西 :(. - Julien N

3

实现 IDataErrorInfo 接口是实现此目的的一种方式,可以在您的实体上这样做:

public partial class MyEntity : IDataErrorInfo
{
    public MyEntity()
    {
    }

    ...

    #region IDataErrorInfo Members
    public string Error
    {
        get { throw new NotImplementedException(); }
    }
    public string this[string propertyName]
    {
        get
        {
            //Custom Validation logic
            return  MyValidator.ValidateProperty(this, propertyName);
        }
    }
    #endregion  
}

要从IDataErrorInfo方法访问当前的DBContext,您可以使用此答案。然后重写您上下文的SaveChanges方法:
    public override int SaveChanges()
    {
        this.ObjectContext.DetectChanges();

        // Get all the new and updated objects
        var objectsToValidate =
        ChangeTracker.Entries().Where(x => x.State == EntityState.Modified || x.State == EntityState.Added).
        Select(e => e.Entity);

        // Check each object for errors
        foreach (var obj in objectsToValidate)
        {
            if (obj is IDataErrorInfo)
            {
                // Check each property
                foreach (var property in obj.GetType().GetProperties())
                {
                    var columnError = (obj as IDataErrorInfo)[property.Name];
                    if (columnError != null) {
                    //Handle your validation errors
                    throw new DbEntityValidationException(columnError); }
                }
            }
        }

        return base.SaveChanges();
    }

另请参阅this answer,以使其与DataAnnotations一起使用。

您写道:

如果我抛出DbEntityValidationException(或像本文建议的ValidationException),ASPNET会像处理任何异常一样处理它(黄色屏幕)。

请参见this answer。当您调用SaveChanges时,需要捕获DbEntityValidationException(或ValidationException),如果您没有在控制器内捕获并处理它们,则它们将由默认错误处理程序处理。

或者您可以使用DynamicValidator控件来捕获ValidationExceptions:

    <!-- Capture validation exceptions -->
    <asp:DynamicValidator ID="ValidatorID" ControlToValidate="GridView1" 
        runat="server" /> 
< p > DynamicValidator 的问题在于它需要 ControlToValidate 属性,并且只捕获来自该控件的异常。同时,封装在其他异常中的异常可能会导致问题。有一个解决方法 - 您可以从 DynamicValidator 继承并覆盖其 ValidateException 方法 请参阅此博客

请参见本文


是的,正是我发现的,但DynamicValidator没有捕获异常,我不知道为什么。如果我调试,我会看到它步入ValidateException方法,并似乎检测到并处理它([源代码](http://referencesource.microsoft.com/#System.Web.DynamicData/DynamicData/DynamicValidator.cs,a9f61764351be893,references)),但异常仍然传递到默认错误处理程序。 我甚至尝试了来自Dynamic Data Futures包的ImprovedDynamicValidator,但也不起作用。 由于没有动态数据控制器,因此我无法在控制器中捕获它。 - Julien N
我明白了,这是个有趣的问题,如果在周四之前没有得到回答,我会去看一下它。 - Vojtěch Dohnal

0

我找到了一个解决方法,虽然不是很喜欢,但是它确实有效:

我的上下文仍然执行验证,并在必要时抛出ValidationException异常。

由于ListView似乎没有捕获和处理该异常,因此我通过处理ListViewOnItemUpdatedOnItemInserted事件来自行处理异常:

protected void ListView1_ItemUpdated(object sender, ListViewUpdatedEventArgs e)
{
    if (e.Exception != null)
    {
        ValidationError.DisplayError(e.Exception.Message);
        e.ExceptionHandled = true;
        e.KeepInEditMode = true;
    }
}

ValidationError 用于将异常消息添加到验证摘要中。它会添加一个“虚假”的、始终失败的验证器,并附带消息。

public class ValidationError : BaseValidator
{
    private ValidationError(string message)
        : base()
    {
        ErrorMessage = message;
        IsValid = false;
    }

    protected override bool EvaluateIsValid()
    {
        return false;
    }

    public static void DisplayError(string message, string validationGroup)
    {
        var currentPage = HttpContext.Current.Handler as Page;
        currentPage.Validators.Add(new ValidationError(message) { ValidationGroup = validationGroup });
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接