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

Примечание

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

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

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

Модель

Я продемонстрируем проверки с простой парой классов: Блог и 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; }
}

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

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

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

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

figure 1

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

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

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

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

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

Текучий API

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

Fluent конфигурации API применяются, так как код сначала создает модель из классов. Конфигурации можно внедрить, переопределив метод OnModelCreating класса DbContext. Ниже приведена конфигурация, указывающая, что свойство BloggerName не может содержать более 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 пытается сохранить блог с blogName, превышающим максимальное значение 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, представляющий имена членов, связанные с проверкой. Так как эта проверка проверяет и Title , и BloggerNameимена свойств, возвращаются.

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

figure 2

DbContext.ValidateEntity

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

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

DbEntityValidationResult размещает объект и DbEntityEntryICollection<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 для создания модели.
  • Правила очередности:
    • Fluent вызовы API переопределяют соответствующие заметки к данным
  • Порядок выполнения:
    • Проверка свойств происходит перед проверкой типа
    • Проверка типа происходит только в том случае, если проверка свойств завершается успешно
  • Если свойство является сложным, его проверка также будет включать:
    • Проверка на уровне свойств сложного типа
    • Проверка на уровне типа для сложного типа, включая IValidatableObject проверку сложного типа

Сводка

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

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