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

Custom advanced entity validation with Dynamic Data

我正在寻找一种解决方案来执行一些自定义实体验证(这需要数据库访问、跨成员验证...)当用户在动态数据屏幕中保存其更改时,Entity Framework.
验证比我可以用属性做的更复杂(它需要访问数据库等)

你能拦截 SaveChanges 调用吗?
我试图覆盖 DbContext 对象中的 ValidateEntity,但动态数据似乎没有调用它(可能是因为它使用了内部 ObjectContext,不确定为什么),并且覆盖 SaveChanges 也无济于事。
我没有看到任何可以订阅的活动...

documentation 应该有帮助:

Customize validation for an individual data field by overriding the OnValidate method or handling the Validate event, which are invoked when any data field is changed. This approach lets you add validation and business logic for an individual field. This approach is more general than adding validation for an individual field. It is useful when the same validation logic can be applied to more than one data field. It also lets you perform validation checks that involve multiple fields.

但我使用的是 POCO Entity Framework 6 类 所以没有 OnValidate 方法可以覆盖,据我所知,这是针对 LinqToSql 的,我找不到他们提到的 Validate 事件。

我试图在我的 DbContext 的构造函数中订阅内部 ObjectContextSavingChanges 事件,手动调用 ValidateEntity,但我不知道该怎么做处理结果。如果我抛出 DbEntityValidationException(或 this article 中建议的 ValidationException),ASPNET 会像处理任何异常一样处理它(黄色屏幕)。

实施 IValidatableObject 也不起作用。

我也尝试实现我自己的 DynamicValidator 看看会发生什么,但没有成功,它似乎处理了异常(如果我覆盖 ValidateException,并放置一个断点,我看到了) 但它仍然冒泡到默认错误处理程序并显示黄色屏幕。我一定是遗漏了什么。

那么,在保存到动态数据/EF 之前,您应该如何对实体执行复杂的验证(跨字段、查询等)?

我认为像您尝试执行的逻辑不属于您架构中的此类级别。让数据库强制执行它应该执行的约束,例如外键等,并将您的业务逻辑放在上面。例如,在你想要验证的实体上,你可以添加一个 IsValidForAddOrUpdate() 方法,它包含你无论如何都会放入验证器的逻辑。然后使用新方法:

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

实现此目的的一种方法是在您的实体上实现 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,您可以使用 this answer。 然后覆盖您的 Context 的 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 一起使用。

您写道:

If I throw a DbEntityValidationException (or a ValidationException like suggested in this article), ASPNET handle it like any exception (yellow screen).

参见 this answer。当您调用 SaveChanges 时,您需要捕获 DbEntityValidationException(或 ValidationException),如果您不捕获它们以在您的 Controller 中处理它们,它们将由默认错误处理程序处理。

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

    <!-- Capture validation exceptions -->
    <asp:DynamicValidator ID="ValidatorID" ControlToValidate="GridView1" 
        runat="server" /> 

DynamicValidator 的问题在于它需要 ControlToValidate 属性 并且它只捕获来自该控件的异常。封装在其他异常中的异常也会产生问题。有一个解决方法 - 您可以继承 DynamicValidator 并覆盖其 ValidateException 方法 see this blog.

this article

我找到了一个我不喜欢的解决方法,但它有效:

My Context 仍在执行验证并在必要时抛出 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 用于将异常消息添加到验证摘要中。它添加了一个 "fake",总是失败的验证器和消息。

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