MVCと流fluentなNhibernateを使用して、ドメインオブジェクトにバインドして保存する前に、ViewModelの一意のフィールドを検証するにはどうすればよいですか?
質問
ユーザーが新しいパートレコードを作成できるWebサイトがあります。特定のフィールドの一意性を検証する最良の方法を見つけようとしています。そのPartNumberが別のPartに既に存在する場合、誰かがPartNumber 1234のPartを追加しようとしないようにします。
Webアプリケーションは、オブジェクトをデータベースにマッピングするために、流pなnHibernateでAsp.net MVCを使用しています。 ValidateNonEmpty、ValidateRangeなどのビューモデルでCastle検証を使用しています。 ValidateSelf メソッドを使用してリポジトリを照会し、そのパーツ番号が既に存在するかどうかを確認しますか? ViewModelでリポジトリを使用することについて何かが正しくないと感じています。
コントローラーアクションにそのロジックを配置する方が良いでしょうか? ViewModelはその時点で(ModelBindの間に)既に検証されていると予想されるため、これは正しくないようです。
または、上記のどれでもないかもしれません。これに関する助けてくれてありがとう。
更新 わかりました、これが役立つかどうかはわかりませんが、プロジェクトの典型的な作成アクションの場合の保存アクションは次のようになります。
public ActionResult Create(PartViewModel viewModel)
{
//I think I'd like to know if its Valid by this point, not on _repository.Save
if(ModelState.IsValid)
{
try
{
var part = _partCreateViewModelMap.MapToEntity(viewModel);
_repository.Save(part);
return Redirect("~/Part/Details/" + part.Id);
}
catch (Exception e)
{
// skip on down...
}
}
// return view to edit
return View(viewModel);
}
解決
この質問は何度も聞かれました。私の友人は、バリデータコードからデータアクセスを実行できるかどうか心配していました。答えは簡単です。これを行う必要がある場合は、実行する必要があります。通常、抽象化の各レベルでこのようなチェックを行う必要があります。そして、すべてのチェックの後、一意制約違反によって引き起こされた例外をキャッチする準備ができているはずです。
他のヒント
データベース内で一意の制約を定義する場合、一意の値が既にデータベースに存在するかどうかを確認する責任を委任してみませんか? NHibernateを使用すると、 NHibernate.Exceptions.ISQLExceptionConverter
インターフェイスを使用して、制約違反に関連する既知のエラーをキャプチャおよび変換できます。また、 NHibernate.Exceptions.IViolatedConstraintNameExtracter
インプリメンター( NHibernate.Exceptions.TemplatedViolatedConstraintNameExtracter
を参照)を使用して、データベース例外の汚れた詳細を取得し、ユーザー例外に変換することもできます。わかりやすいメッセージで、選択の検証例外として再パッケージ化し、関連するコントローラーでキャッチします。
私のプロジェクトの1つからの、非常に具体的で迅速かつダーティーな例外コンバーターの例:
Imports NHibernate
Imports NHibernate.Exceptions
Imports System.Data.SqlClient
Imports System.Data.Common
Namespace NHibernate
Public Class ConstraintViolationExceptionConverter
Implements ISQLExceptionConverter
Public Function Convert(ByVal adoExceptionContextInfo As Global.NHibernate.Exceptions.AdoExceptionContextInfo) As System.Exception Implements Global.NHibernate.Exceptions.ISQLExceptionConverter.Convert
Dim dbEx As DbException = ADOExceptionHelper.ExtractDbException(adoExceptionContextInfo.SqlException)
If TypeOf dbEx Is SqlException Then
Dim sqlError As SqlException = DirectCast(dbEx, SqlException)
Select Case sqlError.Number
Case 547
Return New ConstraintViolationException(adoExceptionContextInfo.Message, adoExceptionContextInfo.SqlException)
End Select
End If
Return SQLStateConverter.HandledNonSpecificException(adoExceptionContextInfo.SqlException, adoExceptionContextInfo.Message, adoExceptionContextInfo.Sql)
End Function
End Class
End Namespace
web.config / nhibernate-configuration / session-factory
プロパティ要素で設定:
<property name="sql_exception_converter">csl.NHibernate.ConstraintViolationExceptionConverter, csl</property>
編集: NHibernateの最近のバージョンでコンバーターインターフェイスが変更されたことに言及する必要があります。この例のインターフェイスはNHibernate.dll v2.1.0.4000のものです
通常、コントローラーとリポジトリの間にサービスレイヤーを配置します。
その後、サービス層が検証を処理し、リポジトリを呼び出します。
次に、サービス層に検証エラーがある場合、カスタム例外をスローし、コントローラーでキャッチし、モデル状態にエラーを挿入します。
あなたの質問に対する答えはありませんが、sharparchitecture.netサイトを確認できます。 asp.net mvcおよびnhibernateに最適な機能が含まれています。また、xvalプロジェクトと、データ注釈バリデーターによる検証に関するチュートリアルを確認することをお勧めします
私にとって有効な解決策は、
1。)エンティティが検証作業の実行に有効かどうかを尋ねます。
2.)これが完了したら、オブジェクトに有効か無効かを示すものが必要です(私の場合は、「壊れたルール」の概念のようなCSLAを使用します)。
3.)このようなものがある場合は、NHibernateが以下に示すように永続化を試みる前にオブジェクトが有効であることを確認できます。
このアプローチの唯一の問題は、検証が必要な各エンティティにインターフェースを実装する必要があることです。これを使用できる場合、NHibernateはルールに従って無効なオブジェクトの変更を保持しなくなります。
using System;
using NHibernate;
using NHibernate.Event;
using Validation.Entities.Interfaces;
using Persistence.SessionBuilder;
namespace Persistence.Validation
{
public class ValidationEventListener : IPreInsertEventListener, IPreUpdateEventListener
{
public bool OnPreInsert(NHibernate.Event.PreInsertEvent @event)
{
var entityToInsert = @event.Entity as IBusinessBase;
if (entityToInsert != null)
{
if (entityToInsert.BrokenRules != null)
{
RollbackTransactionBecauseTheEntityHasBrokenRules();
}
}
return false;
}
public bool OnPreUpdate(NHibernate.Event.PreUpdateEvent @event)
{
var entityToUpdate = @event.Entity as IBusinessBase;
if (entityToUpdate != null)
{
if (entityToUpdate.BrokenRules != null)
{
RollbackTransactionBecauseTheEntityHasBrokenRules();
}
}
return false;
}
private void RollbackTransactionBecauseTheEntityHasBrokenRules()
{
try
{
ISession session = SessionBuilderFactory.GetBuilder().CurrentSession;
if (session != null)
{
session.Transaction.Rollback();
}
}
catch (Exception ex)
{
//this will force a rollback if we don't have a session bound to the current context
throw new NotImplementedException();
}
}
}
}
これはあなたのアーキテクチャにとって重要だと思います。過去に行ったMVCアプリでは、ドメインのものをWebのものから抽象化し、自然に依存関係の注入を使用してハードな依存関係を回避します。
モデルをバインドするときにモデルを検証する場合は、サービス、リポジトリ、またはValidateSelfメソッドでアーキテクチャに次にあるものを簡単に使用できます。その依存関係はどうなるのかという疑問が浮かぶと思います。
覚えている場合は、依存性注入フレームワークを使用して、モデルの作成時に検証に必要なサービスをプラグインするカスタムバインダーを作成し、MVCのデフォルトバインダーを呼び出してオブジェクトを入力し、次に呼び出すことができます検証を行うCastle Validationのフレームワーク。これは完全に考えられた解決策ではありませんが、うまくいけば、いくつかのアイデアを誘発します。