注
EF4.1 以降のみ - このページで説明されている機能、API などは、Entity Framework 4.1 で導入されました。 以前のバージョンを使用している場合、一部またはすべての情報は適用されません
このページの内容は、Julie Lerman (https://thedatafarm.com) が執筆した記事に基づいて作成されています。
Entity Framework には、クライアント側の検証のためにユーザー インターフェイスにフィードしたり、サーバー側の検証に使用したりできるさまざまな検証機能が用意されています。 最初にコードを使用する場合は、注釈または fluent API 構成を使用して検証を指定できます。 追加の検証およびより複雑な検証はコードで設定することができ、モデルがコードファースト、モデルファースト、データベースファーストのいずれから来たものでも機能します。
そのモデル
ここでは、簡単なクラスのペアである Blog と 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 では、コード ファースト クラスを構成する 1 つの手段として、 System.ComponentModel.DataAnnotations アセンブリからの注釈が使用されます。 これらの注釈の中には、 Required、 MaxLength 、 MinLengthなどのルールを提供するものがあります。 多くの .NET クライアント アプリケーションも、MVC ASP.NET など、これらの注釈を認識します。 これらの注釈を使用して、クライアント側とサーバー側の両方の検証を実現できます。 たとえば、強制的に Blog Title プロパティを必須プロパティにすることができます。
[Required]
public string Title { get; set; }
アプリケーションで追加のコードやマークアップを変更しなくても、既存の MVC アプリケーションはクライアント側の検証を実行し、プロパティ名と注釈名を使用してメッセージを動的に作成することもできます。
この作成ビューのポストバック メソッドでは、Entity Framework を使用して新しいブログをデータベースに保存しますが、アプリケーションがそのコードに到達する前に MVC のクライアント側検証がトリガーされます。
ただし、クライアント側の検証は完全ではありません。 ユーザーはブラウザーの機能に影響を与えたり、さらに悪化したりする可能性があります。ハッカーは、UI の検証を回避するためにいくつかのトリッキーを使用する可能性があります。 ただし、Entity Framework では、 Required 注釈も認識され、検証されます。
これをテストする簡単な方法は、MVC のクライアント側検証機能を無効にすることです。 これは、MVC アプリケーションの web.config ファイルで行うことができます。 appSettings セクションには、ClientValidationEnabled のキーがあります。 このキーを false に設定すると、UI が検証を実行できなくなります。
<appSettings>
<add key="ClientValidationEnabled"value="false"/>
...
</appSettings>
クライアント側の検証が無効になっている場合でも、アプリケーションで同じ応答が返されます。 "タイトル フィールドは必須です" というエラー メッセージが以前と同様に表示されます。 現時点では、サーバー側の検証の結果になります。 Entity Framework は、(データベースに送信するRequiredコマンドをビルドする前に) INSERT注釈に対して検証を実行し、メッセージを表示するエラーを MVC に返します。
流暢なAPI
注釈の代わりにコードファーストの fluent API を使用して、同じクライアント側とサーバー側の検証を取得できます。
Requiredを使用するのではなく、MaxLength 検証を使用してこれを示します。
Fluent API 構成は、最初にクラスからモデルを構築するコードとして適用されます。 DbContext クラスの OnModelCreating メソッドをオーバーライドすることで、構成を挿入できます。 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);
}
}
Fluent API の構成に基づいてスローされた検証エラーは自動的に UI に到達しませんが、コードでキャプチャし、それに応じて応答することができます。
Entity Framework が最大 10 文字を超える BloggerName を使用してブログを保存しようとしたときに、その検証エラーをキャプチャする、アプリケーションの BlogController クラスの例外処理エラー コードを次に示します。
[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)
IValidatableオブジェクト
IValidatableObject は、 System.ComponentModel.DataAnnotationsに存在するインターフェイスです。 これは Entity Framework API の一部ではありませんが、Entity Framework クラスのサーバー側検証に利用できます。
IValidatableObject には、Entity Framework が SaveChanges 中に呼び出す Validate メソッドが用意されています。また、クラスを検証する場合は、いつでも自分で呼び出すことができます。
RequiredやMaxLengthなどの構成では、1 つのフィールドに対して検証が実行されます。
Validateメソッドでは、2 つのフィールドを比較するなど、さらに複雑なロジックを使用できます。
次の例では、 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と、検証に関連付けられているメンバー名を表すstringの配列を受け取ります。 この検証では、 Title と BloggerNameの両方がチェックされるため、両方のプロパティ名が返されます。
Fluent API によって提供される検証とは異なり、この検証結果はビューによって認識され、エラーを ModelState に追加するために前に使用した例外ハンドラーは不要です。
ValidationResultで両方のプロパティ名を設定するため、MVC HtmlHelpers は両方のプロパティのエラー メッセージを表示します。
DbContext.ValidateEntity (データベースコンテキスト.ValidateEntity機能)
DbContext には、 ValidateEntity と呼ばれるオーバーライド可能なメソッドがあります。
SaveChangesを呼び出すと、状態がUnchangedされていないキャッシュ内の各エンティティに対して、Entity Framework によってこのメソッドが呼び出されます。 ここで検証ロジックを直接配置することも、前のセクションで追加した Blog.Validate メソッドなどの呼び出しにもこのメソッドを使用できます。
新しいValidateEntityを検証して投稿タイトルがまだ使用されていないことを確認するPostオーバーライドの例を次に示します。 最初に、エンティティが投稿であり、その状態が「追加済み」であるかどうかを確認します。 その場合は、データベース内で同じタイトルの投稿が既に存在するかどうかを確認します。 既存の投稿が既にある場合は、新しい DbEntityValidationResult が作成されます。
DbEntityValidationResult には、1 つのエンティティの 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 では、すべての検証、注釈または Fluent API によって定義された検証、 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 Designer を使用してモデルを作成した場合でも、検証が発生します
- 優先順位ルール:
- Fluent API 呼び出しは、対応するデータ注釈をオーバーライドします
- 実行順序:
- プロパティの検証は、型の検証の前に行われます
- 型の検証は、プロパティの検証が成功した場合にのみ行われます
- プロパティが複雑な場合、その検証には次も含まれます。
- 複合型プロパティに対するプロパティ レベルの検証
- 複合型の型レベルの検証 (複合型の
IValidatableObject検証を含む)
概要
Entity Framework の検証 API は、MVC でのクライアント側の検証で非常にうまく機能しますが、クライアント側の検証に依存する必要はありません。 Entity Framework は、最初の Fluent API コードで適用した DataAnnotation または構成の検証をサーバー側で処理します。
また、 IValidatableObject インターフェイスを使用するか、 DbContext.ValidateEntity メソッドを使用するかに関係なく、動作をカスタマイズするための機能拡張ポイントも多数見ました。 最後の 2 つの検証方法は、概念モデルを記述するために Code First ワークフロー、Model First ワークフロー、Database First ワークフローのいずれを使用する場合でも、 DbContextを通じて利用できます。
.NET