Прочитать на английском

Поделиться через


Проверка данных

Примечание

ТОЛЬКО EF4.1 — функции, API и т. д., рассмотренные на этой странице, были представлены в Entity Framework 4.1. Если вы используете более раннюю версию, некоторые или все сведения не применяются.

Содержимое на этой странице адаптировано из статьи, первоначально написанной Джули Лерман (https://thedatafarm.com).

Entity Framework предоставляет множество функций проверки, которые могут передаваться в пользовательский интерфейс для проверки на стороне клиента или использоваться для проверки на стороне сервера. При использовании кода сначала можно указать проверки с помощью конфигураций заметок или свободного API. Дополнительные проверки и более сложные могут быть указаны в коде и будут ли работать модели от кода в первую очередь, модели в первую очередь или базы данных.

Модель

Я продемонстрировать проверки с простой парой классов: Блог и Post.

public class Blog
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string BloggerName { get; set; }
    public DateTime DateCreated { get; set; }
    public virtual ICollection<Post> Posts { get; set; }
}

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public DateTime DateCreated { get; set; }
    public string Content { get; set; }
    public int BlogId { get; set; }
    public ICollection<Comment> Comments { get; set; }
}

Заметки к данным

Код сначала использует заметки из System.ComponentModel.DataAnnotations сборки в качестве одного из средств настройки первых классов кода. Среди этих заметок есть те, которые предоставляют такие правила, как Requiredи MaxLength MinLength. Ряд клиентских приложений .NET также распознает эти заметки, например, ASP.NET MVC. С помощью этих заметок можно выполнить проверку на стороне клиента и сервера. Например, можно принудительно принудить свойство Title блога к обязательному свойству.

[Required]
public string Title { get; set; }

При отсутствии дополнительных изменений кода или разметки в приложении существующее приложение MVC будет выполнять проверку на стороне клиента, даже динамически создавая сообщение с помощью имен свойств и заметок.

figure 1

В методе обратной передачи этого представления создания Entity Framework используется для сохранения нового блога в базе данных, но проверка на стороне клиента MVC активируется до того, как приложение достигнет этого кода.

Проверка на стороне клиента не является маркером проверки. Пользователи могут повлиять на функции браузера или еще хуже, хакер может использовать некоторые трюки, чтобы избежать проверок пользовательского интерфейса. Но Entity Framework также распознает заметку Required и проверит ее.

Простой способ проверить это — отключить функцию проверки на стороне клиента MVC. Это можно сделать в файле веб-конфигурации приложения MVC. В разделе приложения Параметры есть ключ для ClientValidationEnabled. Если этот ключ задан как false, пользовательский интерфейс не будет выполнять проверки.

<appSettings>
    <add key="ClientValidationEnabled"value="false"/>
    ...
</appSettings>

Даже если проверка на стороне клиента отключена, вы получите тот же ответ в приложении. Сообщение об ошибке "Поле заголовка является обязательным" будет отображаться как раньше. Кроме того, теперь это будет результатом проверки на стороне сервера. Entity Framework выполнит проверку заметки Required (прежде чем даже беспокоить, чтобы создать INSERT команду для отправки в базу данных) и возвратит ошибку MVC, которая отобразит сообщение.

Текучий API

Вы можете использовать простой API кода вместо заметок, чтобы получить ту же проверку на стороне клиента и сервера. Вместо использования, я показываю Requiredвам это с помощью проверки MaxLength.

Конфигурации API Fluent применяются в качестве кода сначала создает модель из классов. Конфигурации можно внедрить, переопределив метод OnModelCreating класса DbContext. Ниже приведена конфигурация, указывающая, что свойство BlogName не может превышать 10 символов.

public class BlogContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
    public DbSet<Comment> Comments { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>().Property(p => p.BloggerName).HasMaxLength(10);
    }
}

Ошибки проверки, возникающие на основе конфигураций API Fluent, не будут автоматически достигать пользовательского интерфейса, но вы можете записать его в коде, а затем ответить на него соответствующим образом.

Ниже приведен код ошибки обработки исключений в классе BlogController приложения, который фиксирует ошибку проверки, когда Entity Framework пытается сохранить блог с именем блога, превышающим максимум 10 символов.

[HttpPost]
public ActionResult Edit(int id, Blog blog)
{
    try
    {
        db.Entry(blog).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    catch (DbEntityValidationException ex)
    {
        var error = ex.EntityValidationErrors.First().ValidationErrors.First();
        this.ModelState.AddModelError(error.PropertyName, error.ErrorMessage);
        return View();
    }
}

Проверка не автоматически передается в представление, поэтому используется дополнительный код, который используется ModelState.AddModelError . Это гарантирует, что сведения об ошибке делают его в представлении, которое затем будет использовать ValidationMessageFor Htmlhelper для отображения ошибки.

@Html.ValidationMessageFor(model => model.BloggerName)

IValidatableObject

IValidatableObject — это интерфейс, который живет в System.ComponentModel.DataAnnotations. Хотя он не является частью API Entity Framework, вы по-прежнему можете использовать его для проверки на стороне сервера в классах Entity Framework. IValidatableObjectValidate предоставляет метод, который Entity Framework будет вызывать во время SaveChanges или вызывать себя в любое время, когда вы хотите проверить классы.

Такие конфигурации, как Required и MaxLength проверка в одном поле. В методе Validate можно использовать еще более сложную логику, например сравнение двух полей.

В следующем примере Blog класс был расширен для реализации IValidatableObject , а затем укажите правило, которое Title не BloggerName удается сопоставить.

public class Blog : IValidatableObject
{
    public int Id { get; set; }

    [Required]
    public string Title { get; set; }

    public string BloggerName { get; set; }
    public DateTime DateCreated { get; set; }
    public virtual ICollection<Post> Posts { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Title == BloggerName)
        {
            yield return new ValidationResult(
                "Blog Title cannot match Blogger Name",
                new[] { nameof(Title), nameof(BloggerName) });
        }
    }
}

Конструктор ValidationResult принимает string сообщение об ошибке и массив strings, представляющий имена членов, связанные с проверкой. Так как эта проверка проверка как именаBloggerName, так Title и имена свойств возвращаются.

В отличие от проверки, предоставленной API Fluent, этот результат проверки распознается представлением и обработчиком исключений, используемым ранее для добавления ошибки ModelState в ненужную. Так как в поле "MVC HtmlHelpers" заданы оба имени ValidationResultсвойств, отображается сообщение об ошибке для обоих этих свойств.

figure 2

DbContext.ValidateEntity

DbContext имеет переопределимый метод ValidateEntity. При вызове SaveChangesEntity Framework вызовет этот метод для каждой сущности в своем кэше, состояние которого не Unchangedявляется. Логику проверки можно поместить непосредственно здесь или даже использовать этот метод для вызова, например метода, добавленного Blog.Validate в предыдущем разделе.

Ниже приведен пример ValidateEntity переопределения, который проверяет новые Postверсии, чтобы убедиться, что название записи еще не использовалось. Сначала проверка, чтобы узнать, является ли сущность записью и что его состояние добавлено. Если это так, то он выглядит в базе данных, чтобы узнать, есть ли уже запись с тем же заголовком. Если уже есть существующая запись, создается новое DbEntityValidationResult .

DbEntityValidationResult размещается и DbEntityEntry ICollection<DbValidationErrors> для одной сущности. В начале этого метода создается экземпляр, DbEntityValidationResult а затем все обнаруженные ошибки добавляются в свою ValidationErrors коллекцию.

protected override DbEntityValidationResult ValidateEntity (
    System.Data.Entity.Infrastructure.DbEntityEntry entityEntry,
    IDictionary<object, object> items)
{
    var result = new DbEntityValidationResult(entityEntry, new List<DbValidationError>());

    if (entityEntry.Entity is Post post && entityEntry.State == EntityState.Added)
    {
        // Check for uniqueness of post title
        if (Posts.Where(p => p.Title == post.Title).Any())
        {
            result.ValidationErrors.Add(
                    new System.Data.Entity.Validation.DbValidationError(
                        nameof(Title),
                        "Post title must be unique."));
        }
    }

    if (result.ValidationErrors.Count > 0)
    {
        return result;
    }
    else
    {
        return base.ValidateEntity(entityEntry, items);
    }
}

Явное активация проверки

Вызов SaveChanges для активации всех проверок, описанных в этой статье. Но вам не нужно полагаться SaveChanges. Вы можете выбрать проверку в другом месте приложения.

DbContext.GetValidationErrors активирует все проверки, определенные заметками или API Fluent, проверку, созданную IValidatableObject (например, Blog.Validate) и проверки, выполняемые в методе DbContext.ValidateEntity .

Следующий код вызовет GetValidationErrors текущий экземпляр объекта DbContext. ValidationErrors группируются по типу DbEntityValidationResultсущности. Код выполняет итерацию сначала по DbEntityValidationResultзначениям, возвращаемым методом, а затем через каждый из них DbValidationError .

foreach (var validationResult in db.GetValidationErrors())
{
    foreach (var error in validationResult.ValidationErrors)
    {
        Debug.WriteLine(
            "Entity Property: {0}, Error {1}",
            error.PropertyName,
            error.ErrorMessage);
    }
}

Другие рекомендации при использовании проверки

Ниже приведены некоторые другие моменты, которые следует учитывать при использовании проверки Entity Framework:

  • Отложенная загрузка отключена во время проверки
  • EF проверяет заметки данных в не сопоставленных свойствах (свойства, которые не сопоставлены со столбцом в базе данных)
  • Проверка выполняется после обнаружения изменений во время SaveChanges. При внесении изменений во время проверки ответственность за уведомление средства отслеживания изменений
  • DbUnexpectedValidationException возникает при возникновении ошибок во время проверки
  • Аспекты, которые Entity Framework включают в модель (максимальная длина, обязательный и т. д.), приведет к проверке, даже если в классах нет заметок данных и (или) использовался конструктор EF для создания модели.
  • Правила приоритета:
    • Вызовы API Fluent переопределяют соответствующие заметки данных
  • Порядок выполнения:
    • Проверка свойств происходит перед проверкой типа
    • Проверка типа возникает только в том случае, если проверка свойств завершается успешно
  • Если свойство является сложным, его проверка также будет включать:
    • Проверка уровня свойств для свойств сложного типа
    • Проверка уровня типа для сложного типа, включая IValidatableObject проверку на сложном типе

Итоги

API проверки в Entity Framework играет очень хорошо с проверкой на стороне клиента в MVC, но вам не нужно полагаться на проверку на стороне клиента. Entity Framework будет заботиться о проверке на стороне сервера для DataAnnotations или конфигураций, которые вы применили с кодом первый API Fluent.

Вы также видели ряд точек расширяемости для настройки поведения, используемого интерфейсом или касанием IValidatableObject DbContext.ValidateEntity метода. И эти два последних средства проверки доступны с помощью DbContextрабочего процесса Code First, Model First или Database First для описания концептуальной модели.