Entity Frameworkを使用するプロジェクトがあります。私のSaveChanges
でDbContext
を呼び出している間に、私は次の例外が出ます。
System.Data.Entity.Validation.DbEntityValidationException:1つ以上のエンティティの検証に失敗しました。詳細については、 'EntityValidationErrors'プロパティを参照してください。
これは大丈夫で面倒ですが、この例外が発生するたびにデバッガをアタッチする必要はありません。さらに、実稼働環境ではデバッガを簡単にアタッチできないため、これらのエラーを再現するには非常に長い努力が必要です。
DbEntityValidationException
内に隠された詳細を見るにはどうすればいいですか?
最も簡単な解決策は、エンティティクラスのSaveChanges
をオーバーライドすることです。あなたはDbEntityValidationException
をキャッチし、実際のエラーをアンラップし、そして改善されたメッセージで新しいDbEntityValidationException
を作成することができます。
あなたの例外メッセージは次のようになります。
System.Data.Entity.Validation.DbEntityValidationException:1つ以上のエンティティの検証に失敗しました。詳細については、 'EntityValidationErrors'プロパティを参照してください。検証エラーは次のとおりです。フィールドPhoneNumberは、最大長 '12'のストリングまたは配列タイプでなければなりません。 LastNameフィールドは必須です。
オーバーライドされたSaveChangesは、DbContext
から継承した任意のクラスにドロップできます。
public partial class SomethingSomethingEntities
{
public override int SaveChanges()
{
try
{
return base.SaveChanges();
}
catch (DbEntityValidationException ex)
{
// Retrieve the error messages as a list of strings.
var errorMessages = ex.EntityValidationErrors
.SelectMany(x => x.ValidationErrors)
.Select(x => x.ErrorMessage);
// Join the list to a single string.
var fullErrorMessage = string.Join("; ", errorMessages);
// Combine the original exception message with the new one.
var exceptionMessage = string.Concat(ex.Message, " The validation errors are: ", fullErrorMessage);
// Throw a new DbEntityValidationException with the improved exception message.
throw new DbEntityValidationException(exceptionMessage, ex.EntityValidationErrors);
}
}
}
DbEntityValidationException
には、検証エラーを引き起こしたエンティティも含まれています。そのため、さらに情報が必要な場合は、上記のコードを変更してこれらのエンティティに関する情報を出力することができます。
次も参照してください: http://devillers.nl/improving-dbentityvalidationexception/
Martinが指摘したように、DbEntityValidationResult
にはより多くの情報があります。それぞれのメッセージで私のPOCOクラス名とプロパティ名の両方を取得するのが便利で、このためだけにすべての[Required]
タグにカスタムのErrorMessage
属性を書く必要がないようにしたいと思いました。
以下のMartinのコードの微調整は、私にとってこれらの詳細の面倒を見ました:
// Retrieve the error messages as a list of strings.
List<string> errorMessages = new List<string>();
foreach (DbEntityValidationResult validationResult in ex.EntityValidationErrors)
{
string entityName = validationResult.Entry.Entity.GetType().Name;
foreach (DbValidationError error in validationResult.ValidationErrors)
{
errorMessages.Add(entityName + "." + error.PropertyName + ": " + error.ErrorMessage);
}
}
EntityValidationErrors
コレクションを表示するには、次のWatch式を[Watch]ウィンドウに追加します。
((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors
私はビジュアルスタジオ2013を使っています
catch {...}
ブロック内でデバッグモードに入っている間、 "QuickWatch"ウィンドウを開きます(ctrl+alt+q)を貼り付けます。
((System.Data.Entity.Validation.DbEntityValidationException)ex).EntityValidationErrors
これにより、ValidationErrors
ツリーにドリルダウンできます。それは私がこれらのエラーに対する即時の洞察を得るために私が見つけた最も簡単な方法です。
最初のエラーだけを気にしていて、catch
ブロックを持っていない可能性があるVisual 2012+ユーザーの場合は、次のようにすることもできます。
((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors.First().ValidationErrors.First().ErrorMessage
デバッグ中にエラーを調べて意味のあるエラーメッセージをすばやく見つけるには
クイックウォッチを追加する:
((System.Data.Entity.Validation.DbEntityValidationException)$exception).EntityValidationErrors
このようにEntityValidationErrorsにドリルダウンします。
(コレクションアイテム[0])> ValidationErrors>(コレクションアイテム[0])> ErrorMessage
実際には、これは単なる検証の問題であり、EFはデータベースに変更を加える前にまずエンティティのプロパティを検証します。そのため、テーブルを設計したときのように、EFはプロパティの値が範囲外かどうかを確認します。 Table_Column_UserNameはvarchar(20)です。しかし、EFでは、20より長い値を入力しました。あるいは、列がNULLになることを許可していない場合もあります。そのため、検証プロセスでは、変更を加えるかどうかにかかわらず、null以外の列に値を設定する必要があります。私は個人的には、Leniel Macaferiの回答が好きです。検証の問題の詳細を表示できます
「実際の検証エラー」には機密情報が含まれている可能性があります。そのため、マイクロソフトはそれらを別の場所(プロパティ)に配置することを選択しました。ここでマークされた解決策は実用的ですが、慎重に行われるべきです。
私は拡張メソッドを作成したいと思います。これに対するさらなる理由:
Azure関数の場合は、この単純な拡張子をMicrosoft.Extensions.Logging.ILoggerに使用します。
public static class LoggerExtensions
{
public static void Error(this ILogger logger, string message, Exception exception)
{
if (exception is DbEntityValidationException dbException)
{
message += "\nValidation Errors: ";
foreach (var error in dbException.EntityValidationErrors.SelectMany(entity => entity.ValidationErrors))
{
message += $"\n * Field name: {error.PropertyName}, Error message: {error.ErrorMessage}";
}
}
logger.LogError(default(EventId), exception, message);
}
}
そして使用例:
try
{
do something with request and EF
}
catch (Exception e)
{
log.Error($"Failed to create customer due to an exception: {e.Message}", e);
return await StringResponseUtil.CreateResponse(HttpStatusCode.InternalServerError, e.Message);
}
次のようにコードでtryブロックを使用してください。
try
{
// Your code...
// Could also be before try if you know the exception occurs in SaveChanges
context.SaveChanges();
}
catch (DbEntityValidationException e)
{
foreach (var eve in e.EntityValidationErrors)
{
Console.WriteLine("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
eve.Entry.Entity.GetType().Name, eve.Entry.State);
foreach (var ve in eve.ValidationErrors)
{
Console.WriteLine("- Property: \"{0}\", Error: \"{1}\"",
ve.PropertyName, ve.ErrorMessage);
}
}
throw;
}
あなたもここで詳細をチェックすることができます