Пользовательская расширенная проверка объекта с помощью динамических данных

Я ищу решение для выполнения некоторой проверки пользовательских сущностей (для чего требуется доступ к базе данных, проверка соответствия между членами...), когда пользователь сохраняет свои изменения на экране динамических данных с помощью Entity Framework.
Проверка более сложна, чем то, что я могу сделать с атрибутами (для этого требуется доступ к базе данных и т.д.)

Вы можете перехватить вызов SaveChanges?
Я попытался переопределить ValidateEntity в объекте DbContext, но Dynamic Data, похоже, не вызывает его (возможно, потому, что он использует внутренний ObjectContext, не уверен, почему) и переопределение SaveChanges тоже не помогает.
Я не вижу никакого события, которое я мог бы подписаться на...

Документация должна помочь:

Настроить проверку для отдельного поля данных путем переопределения OnValidate или обрабатывать событие Validate, которое вызывается при изменении любого поля данных. Этот подход позволяет добавить подтверждение и бизнес-логику для отдельной области. Этот подход более чем добавление проверки для отдельного поля. Это полезно когда одна и та же логика проверки может применяться к нескольким данным поле. Он также позволяет выполнять проверки, которые включают несколько полей.

Но я использую классы POCO Entity Framework 6, поэтому нет метода OnValidate для переопределения, и из того, что я прочитал, это для LinqToSql, и я не могу найти событие Validate, которое они упоминают.

Я попытался подписаться на событие SavingChanges внутреннего ObjectContext в конструкторе моего DbContext, чтобы вручную вызвать ValidateEntity, но я не знаю, что делать с результатом. Если я выброшу DbEntityValidationException (или ValidationException, как предлагается в этой статье), ASPNET обрабатывает его как любое исключение (желтый экран).

Реализация IValidatableObject тоже не работает.

Я также попытался реализовать свой собственный DynamicValidator, чтобы увидеть, что происходит, но без успеха, похоже, обрабатывает исключение (если я переопределяю ValidateException и ставил точку останова, я это вижу), но он все еще пузырился к обработчику ошибок по умолчанию и отображает желтый экран. Мне что-то не хватает.

Итак, как вы должны выполнять сложную проверку (кросс-поле, с запросами и т.д.) для объектов перед сохранением в Dynamic Data/EF?

Ответ 1

Я нашел обходное решение, которое мне не нравится, но оно работает:

Мой контекст все еще выполняет валидацию и бросает a ValidationException, если это необходимо.

Поскольку ListView, похоже, не захватывает и не обрабатывает исключение, я делаю это сам, обрабатывая событие OnItemUpdated или OnItemInserted ListView:

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

Ответ 2

Я бы сказал, что логика, как вы пытаетесь выполнить, не входит в такой уровень в вашей архитектуре. Пусть база данных обеспечивает ограничения, которые она должна использовать, например, внешние ключи и т.д., И иметь свою бизнес-логику выше. Например, на вашей сущности, которую вы хотите проверить, вы можете добавить метод IsValidForAddOrUpdate(), который в любом случае содержит логику, которую вы положили в ваши валидаторы. Затем просто используйте новые методы:

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

Ответ 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  
}

Чтобы получить доступ к текущему методу DBContext из методов IDataErrorInfo, вы можете использовать этот ответ. Затем переопределите метод 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();
    }

См. также этот ответ, чтобы он работал с DataAnnotations.

Вы писали:

Если я выкидываю исключение DbEntityValidationException (или исключение ValidationException как это предлагается в этой статье), ASPNET обрабатывает его как любое исключение (желтый экран).

См. этот ответ. Когда вы вызываете SaveChanges, вам нужно поймать DbEntityValidationException (или ValidationException), если вы не поймаете их для обработки их в своем контроллере, они обрабатываются обработчиком ошибок по умолчанию.

Или вы можете использовать DynamicValidator для проверки ValidationExceptions:

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

Проблема с DynamicValidator заключается в том, что для этого требуется свойство ControlToValidate, и он захватывает только исключения, исходящие из этого элемента управления. Кроме того, исключения, заключенные в другие исключения, могут создавать проблемы. Существует обходное решение - вы можете наследовать от DynamicValidator и переопределить его метод ValidateException посмотреть этот блог.

См. в этой статье.