CSLA .NETを介したリポジトリパターン

モジュール間に低結合システムを作成すると、ソフトウェア開発で多くの利点が得られます。 CSLA .NETフレームワークを使用して記述されたアプリケーションでは、標準テンプレートを使用して依存関係を解消することは必ずしも明らかではありません。



この記事では、リポジトリテンプレートを使用してビジネスレイヤーからデータアクセスレイヤー(DAL)を分離するオプションを検討し、CSLA .NETビジネスオブジェクトにうつ病注入を実装する最も一般的な方法について説明します。 CSLAバージョン4.1を使用。





そのため、ルートビジネスオブジェクトPersonを作成しましょう。 このビジネスオブジェクトには、サブジェクト領域のいくつかの単純なプロパティ、Ordersオブジェクトの子コレクション、および子オブジェクトAddressがあります。



//Person.cs [Serializable] public sealed partial class Person : BusinessBase<Person> { private Person( ) { } public static Person NewPerson( ) { return DataPortal.Create<Person>( ); } public static Person GetPerson( int personId ) { return DataPortal.Fetch<Person>( new SingleCriteria<Person, int>( personId ) ); } public static void RemovePerson( int personId ) { DataPortal.Delete<Person>( new SingleCriteria<Person, int>( personId ) ); } private static readonly PropertyInfo<int> IdProperty = RegisterProperty<int>( c => c.Id ); public int Id { get { return GetProperty( IdProperty ); } private set { LoadProperty( IdProperty, value ); } } public static readonly PropertyInfo<string> FirstNameProperty = RegisterProperty<string>( c => c.FirstName ); public string FirstName { get { return GetProperty( FirstNameProperty ); } set { SetProperty( FirstNameProperty, value ); } } public static readonly PropertyInfo<string> SecondNameProperty = RegisterProperty<string>( c => c.SecondName ); public string SecondName { get { return GetProperty( SecondNameProperty ); } set { SetProperty( SecondNameProperty, value ); } } public static readonly PropertyInfo<int> AgeProperty = RegisterProperty<int>( c => c.Age ); public int Age { get { return GetProperty( AgeProperty ); } set { SetProperty( AgeProperty, value ); } } public static readonly PropertyInfo<string> CommentProperty = RegisterProperty<string>( c => c.Comment ); public string Comment { get { return GetProperty( CommentProperty ); } set { SetProperty( CommentProperty, value ); } } public static readonly PropertyInfo<Orders> OrdersProperty = RegisterProperty<Orders>( c => c.Orders, RelationshipTypes.Child | RelationshipTypes.LazyLoad ); public Orders Orders { get { if ( !FieldManager.FieldExists( OrdersProperty ) ) { Orders = Orders.NewOrders( ); } return GetProperty( OrdersProperty ); } private set { LoadProperty( OrdersProperty, value ); OnPropertyChanged( OrdersProperty ); } } public static readonly PropertyInfo<Address> AddressProperty = RegisterProperty<Address>( c => c.Address, RelationshipTypes.Child | RelationshipTypes.LazyLoad); public Address Address { get { if ( !FieldManager.FieldExists( AddressProperty ) ) { Address = Address.NewAddress( ); } return GetProperty( AddressProperty ); } private set { LoadProperty( AddressProperty, value ); OnPropertyChanged( AddressProperty ); } } }
      
      





子クラスコード
 //Orders.cs [Serializable] public sealed partial class Orders : BusinessListBase<Orders, Order> { private Orders( ) { } public static Orders NewOrders( ) { return DataPortal.CreateChild<Orders>( ); } } //Order.cs [Serializable] public sealed partial class Order : BusinessBase<Order> { private Order( ) { } public static Order NewOrder( ) { return DataPortal.CreateChild<Order>( ); } public static readonly PropertyInfo<int> IdProperty = RegisterProperty<int>( c => c.Id ); public int Id { get { return GetProperty( IdProperty ); } set { LoadProperty( IdProperty, value ); } } public static readonly PropertyInfo<string> DescriptionProperty = RegisterProperty<string>( c => c.Description ); public string Description { get { return GetProperty( DescriptionProperty ); } set { SetProperty( DescriptionProperty, value ); } } } //Address.cs [Serializable] public partial class Address : BusinessBase<Address> { private Address( ) { } public static Address NewAddress( ) { return DataPortal.CreateChild<Address>( ); } private static readonly PropertyInfo<int> IdProperty = RegisterProperty<int>( c => c.Id ); private int Id { get { return GetProperty( IdProperty ); } set { LoadProperty( IdProperty, value ); } } public static readonly PropertyInfo<string> FirstAddressProperty = RegisterProperty<string>( c => c.FirstAddress ); public string FirstAddress { get { return GetProperty( FirstAddressProperty ); } set { SetProperty( FirstAddressProperty, value ); } } public static readonly PropertyInfo<string> SecondAddressProperty = RegisterProperty<string>( c => c.SecondAddress ); public string SecondAddress { get { return GetProperty( SecondAddressProperty ); } set { SetProperty( SecondAddressProperty, value ); } } }
      
      







結果のクラス図を図に示します:







ビジネスクラスは部分修飾子でマークされており、読みやすさを向上させるために、データアクセスコード全体がクラスの他の部分に移動されていることに注意してください。 ここで、DALアクセスコードを見てみましょう。



DALとの標準的な相互作用



最初に、Personオブジェクトの作成に関連するコードを見てみましょう。 ご覧のとおり、このクラスにはパラメーターのない単一のコンストラクターがあります。 CSLAクライアントデータポータルにアクセスするファクトリメソッドは、オブジェクトの作成と取得を担当します。 クライアントデータポータルは、サーバーデータポータルにアクセスします。 後者はリフレクションを介して、PersonクラスのプライベートメソッドDataPortal_CreateまたはDataPortal_Fetchを参照します。それぞれ、オブジェクトを作成または取得します。









Personオブジェクトの挿入、更新、削除にも同じスキームが適用されますが、それぞれDataPortal_Insert、DataPortal_Update、およびDataPortal_DeleteSelfメソッドが使用されます。







Saveメソッド(CSLAインフラストラクチャメソッド)がサーバーデータポータルで作成された新しいビジネスオブジェクトを実際に返すことを思い出させてください。そのため、クライアントコードで保存されたオブジェクトへのすべての参照を更新する必要があります(いわゆるモバイルビジネスオブジェクト、モバイルオブジェクトの概念)パターン):







最後に、強制的な削除には、DataPortal_Deleteメソッドが使用されます。







したがって、DataPortal_XYZメソッドには、データの作成と取得だけでなく、DALのPersonオブジェクトの更新と削除のためのすべてのデータアクセスロジックが含まれています。 これらのメソッドには、データへのアクセスに通常使用される特定のロジックがあります。SQLクエリコード、データベースへの接続、トランザクションの作成による子オブジェクトの更新などです。



 //Person.Server.cs public partial class Person { public static readonly PropertyInfo<object> LastChangedProperty = RegisterProperty<object>( c => c.LastChanged ); public object LastChanged { get { return ReadProperty( LastChangedProperty ); } private set { LoadProperty( LastChangedProperty, value ); } } protected override void DataPortal_Create( ) { BusinessRules.CheckRules( ); } private void DataPortal_Fetch( SingleCriteria<Person, int> idCriteria ) { const string query = @"SELECT id ,first_name ,second_name ,age ,comment ,last_changed ,address_id ,first_address ,second_address FROM All_persons WHERE id = :p_id"; using ( var manager = ConnectionManager<OracleConnection>.GetManager( "CSLAPROJECT" ) ) { using ( var command = manager.Connection.CreateCommand( ) ) { command.CommandType = CommandType.Text; command.CommandText = query; command.Parameters.AddWithValue( "p_id", idCriteria.Value ); using ( var reader = new SafeDataReader( command.ExecuteReader( CommandBehavior.SingleRow ) ) ) { if ( reader.Read( ) ) { FetchFromReader( reader ); } } } LoadProperty( OrdersProperty, Orders.GetOrders( this ) ); } } private void FetchFromReader( SafeDataReader reader ) { LoadProperty( FirstNameProperty, reader.GetString( "first_name" ) ); LoadProperty( SecondNameProperty, reader.GetString( "second_name" ) ); LoadProperty( CommentProperty, reader.GetString( "comment" ) ); LoadProperty( AddressProperty, Address.GetAddress( reader ) ); Id = reader.GetInt32( "Id" ); LastChanged = reader[ "last_changed" ]; } protected override void DataPortal_Insert( ) { using ( var manager = ConnectionManager<OracleConnection>.GetManager( "CSLAPROJECT" ) ) { using ( var transaction = manager.Connection.BeginTransaction( ) ) { try { using ( var command = manager.Connection.CreateCommand( ) ) { command.CommandType = CommandType.StoredProcedure; command.CommandText = "csla_project.add_person"; var personParameters = GetPersonParameters( ); command.Parameters.AddRange( personParameters ); command.Transaction = transaction; command.ExecuteNonQuery( ); Id = ( int )command.Parameters[ "p_id" ].Value; LastChanged = command.Parameters[ "p_last_changed" ]; } FieldManager.UpdateChildren( this, transaction ); transaction.Commit( ); } catch { transaction.Rollback( ); throw; } } } } protected override void DataPortal_Update( ) { using ( var manager = ConnectionManager<OracleConnection>.GetManager( "CSLAPROJECT" ) ) { using ( var transaction = manager.Connection.BeginTransaction( ) ) { try { using ( var command = manager.Connection.CreateCommand( ) ) { command.CommandType = CommandType.StoredProcedure; command.CommandText = "csla_project.add_person"; var personParameters = GetPersonParameters( ); command.Parameters.AddRange( personParameters ); command.Transaction = transaction; command.ExecuteNonQuery( ); LastChanged = command.Parameters[ "p_last_changed" ]; } FieldManager.UpdateChildren( this, transaction ); transaction.Commit( ); } catch { transaction.Rollback( ); throw; } } } } private void DataPortal_Delete( SingleCriteria<Person, int> idCriteria ) { DoDelete( idCriteria.Value ); } protected override void DataPortal_DeleteSelf( ) { DoDelete( Id ); } private void DoDelete( int personId ) { using ( var manager = ConnectionManager<OracleConnection>.GetManager( "CSLAPROJECT" ) ) { using ( var transaction = manager.Connection.BeginTransaction( ) ) { try { using ( var command = manager.Connection.CreateCommand( ) ) { command.CommandType = CommandType.StoredProcedure; command.CommandText = "csla_project.delete_person"; command.Parameters.AddWithValue( "p_id", personId ); command.Transaction = transaction; command.ExecuteNonQuery( ); } transaction.Commit( ); } catch { transaction.Rollback( ); throw; } } } } private OracleParameter[] GetPersonParameters( ) { return new[] { new OracleParameter( "p_id", Id ) {Direction = ParameterDirection.InputOutput}, new OracleParameter( "p_first_name", FirstName ), new OracleParameter( "p_second_name", SecondName ), new OracleParameter( "p_age", Age ), new OracleParameter( "p_comment", Comment ), new OracleParameter( "p_last_changed", OracleType.Int32 ) {Direction = ParameterDirection.Output} }; } }
      
      





Personの子オブジェクトのDALアクセスコードは似ていますが、子オブジェクトを更新するときに、Personはそれ自体へのリンクとADO .NETトランザクションのインスタンスをデータポータルに渡します。したがって、子オブジェクトの対応するDataPortal_XYZメソッドには追加の引数が含まれます。



子クラスコード:
 //Orders.Server.cs public partial class Orders { internal static Orders GetOrders( Person person ) { return DataPortal.FetchChild<Orders>( person ); } private void Child_Fetch( Person person ) { const string query = @"SELECT id ,description FROM All_orders WHERE person_id = :p_person_id"; using ( var manager = ConnectionManager<OracleConnection>.GetManager( "CSLAPROJECT" ) ) { using ( var command = manager.Connection.CreateCommand( ) ) { command.CommandType = CommandType.Text; command.CommandText = query; command.Parameters.AddWithValue( "p_person_id", person.Id ); using ( var reader = new SafeDataReader( command.ExecuteReader( CommandBehavior.SingleRow ) ) ) { RaiseListChangedEvents = false; while ( reader.Read( ) ) { Add( Order.GetOrder( reader ) ); } RaiseListChangedEvents = true; } } } } private void Child_Update( Person person, OracleTransaction transaction ) { base.Child_Update( person, transaction ); } }
      
      







 //Order.Server.cs public partial class Order { internal static Order GetOrder( SafeDataReader reader ) { return DataPortal.FetchChild<Order>( reader ); } private void Child_Fetch( SafeDataReader reader ) { LoadProperty( IdProperty, reader.GetInt32( "id" ) ); LoadProperty( DescriptionProperty, reader.GetString( "description" ) ); } private void Child_Insert( Person person, OracleTransaction transaction ) { using ( var manager = ConnectionManager<OracleConnection>.GetManager( "CSLAPROJECT" ) ) { using ( var command = manager.Connection.CreateCommand( ) ) { command.CommandType = CommandType.StoredProcedure; command.CommandText = "csla_project.add_order"; command.Transaction = transaction; command.Parameters.AddRange( GetParameters( ) ); command.Parameters.AddWithValue( "person_id", person.Id ); command.ExecuteNonQuery( ); Id = ( int )command.Parameters[ "p_id" ].Value; } } } private void Child_Update( Person person, OracleTransaction transaction ) { using ( var manager = ConnectionManager<OracleConnection>.GetManager( "CSLAPROJECT" ) ) { using ( var command = manager.Connection.CreateCommand( ) ) { command.CommandType = CommandType.StoredProcedure; command.CommandText = "csla_project.edit_order"; command.Transaction = transaction; command.Parameters.AddRange( GetParameters( ) ); command.Parameters.AddWithValue( "person_id", person.Id ); command.ExecuteNonQuery( ); } } } private void Child_DeleteSelf( Person person, OracleTransaction transaction ) { using ( var manager = ConnectionManager<OracleConnection>.GetManager( "CSLAPROJECT" ) ) { using ( var command = manager.Connection.CreateCommand( ) ) { command.CommandType = CommandType.StoredProcedure; command.CommandText = "csla_project.remove_order"; command.Transaction = transaction; command.Parameters.AddWithValue( "p_person_id", person.Id ); command.Parameters.AddWithValue( "p_order_id", Id ); command.ExecuteNonQuery( ); } } } private OracleParameter[] GetParameters( ) { return new[] { new OracleParameter( "p_id", Id ) {Direction = ParameterDirection.InputOutput}, new OracleParameter( "p_description", Description ) }; } }
      
      







 //Address.Server.cs public sealed partial class Address { internal static Address GetAddress( SafeDataReader reader ) { return DataPortal.Fetch<Address>( reader ); } private void Child_Fetch( SafeDataReader reader ) { using ( BypassPropertyChecks ) { LoadProperty( IdProperty, reader.GetInt32( "address_id" ) ); LoadProperty( FirstAddressProperty, reader.GetString( "first_address" ) ); LoadProperty( SecondAddressProperty, reader.GetString( "second_address" ) ); } } private void Child_Insert( Person person, OracleTransaction transaction ) { using ( var manager = ConnectionManager<OracleConnection>.GetManager( "CSLAPROJECT" ) ) { using ( var command = manager.Connection.CreateCommand( ) ) { command.CommandType = CommandType.StoredProcedure; command.CommandText = "csla_project.add_address"; command.Parameters.AddRange( GetParameters( ) ); command.Parameters.AddWithValue( "p_person_id", person.Id ); command.Transaction = transaction; command.ExecuteNonQuery( ); Id = ( int )command.Parameters[ "p_id" ].Value; } } } private void Child_Update( Person person, OracleTransaction transaction ) { using ( var manager = ConnectionManager<OracleConnection>.GetManager( "CSLAPROJECT" ) ) { using ( var command = manager.Connection.CreateCommand( ) ) { command.CommandType = CommandType.StoredProcedure; command.CommandText = "csla_project.edit_address"; command.Parameters.AddRange( GetParameters( ) ); command.Parameters.AddWithValue( "p_person_id", person.Id ); command.Transaction = transaction; command.ExecuteNonQuery( ); } } } private void Child_DeleteSelf( Person person, OracleTransaction transaction ) { using ( var manager = ConnectionManager<OracleConnection>.GetManager( "CSLAPROJECT" ) ) { using ( var command = manager.Connection.CreateCommand( ) ) { command.CommandType = CommandType.StoredProcedure; command.CommandText = "csla_project.remove_address"; command.Parameters.AddWithValue( "p_address_id", Id ); command.Parameters.AddWithValue( "p_person_id", person.Id ); command.Transaction = transaction; command.ExecuteNonQuery( ); } } } private OracleParameter[] GetParameters( ) { return new[] { new OracleParameter( "p_id", Id ) {Direction = ParameterDirection.InputOutput}, new OracleParameter( "p_first_address", FirstAddress ), new OracleParameter( "p_second_address", SecondAddress ) }; } }
      
      







リポジトリパターン



これまで、CSLA .NETを使用して開発された多くのレガシープログラムのコードでよく見られる、CSLAビジネスオブジェクトの標準的な実装を見てきました。 コードからわかるように、データアクセス層とビジネスロジック層は互いに密接に関連しています。 データアクセス層から抽象化する場合、ビジネスロジックの低レベルの詳細とそれらが持つ特定の依存関係を取り除きます。 これにより、ビジネスオブジェクトの単体テストが簡素化され、ビジネスレベルのロジックに関係なくデータアクセスのロジックを変更できるようになります。 したがって、ビジネスロジックとデータアクセスレイヤーの間に追加レベルの間接参照を作成するというタスクが発生します。 たとえば、有名なリポジトリテンプレートを使用できます。 必要なメソッドでインターフェースを作成します。



 //IPersonRepository.cs public interface IPersonRepository { PersonData FindPerson( int id ); void AddPerson( PersonData newPerson, out int newId, out object lastChanged ); void EditPerson( PersonData existingPerson, out object lastChanged ); void RemovePerson( int personId ); }
      
      





PersonDataクラスは、単純なデータ転送オブジェクト(DTO)です。



 //PersonData.cs public sealed class PersonData { public int Id { get; set; } public string FirstName { get; set; } public string SecondName { get; set; } public int Age { get; set; } public string Comment { get; set; } public object LastChanged { get; set; } }
      
      





ADO .NETコードからトランザクションサポートを抽象化するために、単純なIContextおよびITransactionインターフェイスを作成します。



 //IContext.cs public interface IContext { ITransaction BeginTransaction( ); } //ITransaction.cs public interface ITransaction : IDisposable { void Commit( ); void Rollback( ); }
      
      





ここで、PersonクラスのDataPortal_XYZメソッドの特定のDALコードの代わりに、上記で定義されたインターフェイスのメソッドを見たいと思います。 この問題は、Personビジネスオブジェクトに依存関係を注入することで発生します。 古典的な方法は、CSLAの制限(ファクトリメソッドを使用)のためにPersonコンストラクターを通じて依存関係をプッシュすることです。この問題を解決する他の可能な方法を見てみましょう。



セッターメソッドインジェクション



タイプIPersonRepositoryのプライベートフィールドをPersonクラスに追加します。



 [NonSerialized] [NotUndoable] private IPersonRepository _personRepository;
      
      





[NonSerialized] [NotUndoable]属性の使用に注意してください-ある物理ノードから別のノード(モバイルオブジェクトパターン)にPersonオブジェクトを移動し、CSLA(NレベルUndo)のマルチレベルキャンセルに参加する場合、IPersonRepository依存関係をシリアル化しないでください。



次に、リポジトリ設定メソッドを追加します。



 [Inject] //   DI- Ninject private void Configure( IPersonRepository personRepository ) { _personRepository = personRepository; }
      
      





構成メソッドの代わりに、PersonRepositoryプロパティ(いわゆるProperty Setterインジェクション)を使用できます。



 [Inject] [EditorBrowsable( EditorBrowsableState.Never )] private IPersonRepository PersonRepository { get { return _personRepository; } set { _personRepository = value; } }
      
      





この場合、PersonRepositoryプロパティはデータポータルのサーバー側でのみ使用されるため、EditorBrowsable属性を使用してIntellisenceから非表示にできることに注意してください。



一般に、BusinessBase抽象クラスの次のアンカーを使用して、CSLAインフラストラクチャによって既に作成されたビジネスオブジェクトに依存関係を注入できます。



DataPortal_OnDataPortalInvoke、Child_OnDataPortalInvoke、およびOnDeserialized。 最初の2つのメソッドは、ビジネスオブジェクトのDataPortal_XXXメソッドを呼び出す前に、データポータルのサーバー側によって呼び出されます。 最初のメソッド-ビジネスオブジェクトがルートの場合、2番目のメソッド-子の場合。 3番目のメソッドは、サーバーまたはクライアント上のモバイルオブジェクトを逆シリアル化した後に呼び出されます。 リポジトリテンプレート内で再定義する必要はありません(依存関係はサーバー上でのみ使用されます)が、CSLAビジネスオブジェクトへの依存関係注入の一般的な場合には必要です。



したがって、3つのメソッドすべてで呼び出されるInjectメソッドを記述し、CSLAによって作成されたビジネスオブジェクトに依存関係を注入できます。 上記に基づいて、新しいInjectableBusinessBaseステレオタイプを作成します。これは、標準のBusinessBaseステレオタイプの代替として機能します。



 //InjectableBusinessBase.cs [Serializable] public abstract class InjectableBusinessBase<T> : BusinessBase<T> where T : BusinessBase<T> { protected override void DataPortal_OnDataPortalInvoke( DataPortalEventArgs e ) { Inject( ); base.DataPortal_OnDataPortalInvoke( e ); } protected override void Child_OnDataPortalInvoke( DataPortalEventArgs e ) { Inject( ); base.Child_OnDataPortalInvoke( e ); } protected override void OnDeserialized( System.Runtime.Serialization.StreamingContext context ) { Inject( ); base.OnDeserialized( context ); } private void Inject( ) { //         . } }
      
      





CSLAビジネスオブジェクトの各ステレオタイプについて、アンカーを再定義して新しいステレオタイプを作成する必要があることに注意してください。 新しいステレオタイプのコードは、上記のコードに似ています。 依存関係を持つビジネスレベルのクラスは、新しいステレオタイプを継承する必要があります。



 [Serializable] public partial class Person : InjectableBusinessBase<Person> { //… } [Serializable] public partial class Orders : InjectableBusinessListBase<Orders, Order> { //… } [Serializable] public partial class Address : InjectableBusinessBase<Address> { //… }
      
      





Injectメソッドを実装するには、次のファサードクラスをDIコンテナに追加します( Ninjectを使用)。



 //Container.cs public static class Container { private static readonly object SyncRoot = new object( ); private static volatile IKernel _kernel; //   DI-  Ninject public static IKernel Kernel { get { if ( _kernel == null ) { lock ( SyncRoot ) { if ( _kernel == null ) { ConfigureKernel( ); } } } return _kernel; } } //      . public static void InjectInto( object target ) { Kernel.Inject( target ); } private static void ConfigureKernel( ) { //   } public static void InjectKernel( IKernel kernel ) { lock ( SyncRoot ) { _kernel = kernel; } } }
      
      





これまでのところ、DIコンテナ自体を直接構成する方法は重要ではありませんが、InjectIntoメソッドのみが重要です。 これで、InjectableBusinessBaseステレオタイプのInject()メソッドに戻ることができます。



 private void Inject( ) { Container.InjectInto( this ); }
      
      





最後に、IPersonRepositoryプロパティとIContextプロパティをPersonクラスのサーバー側に追加し、DataPortal_XYZメソッドを次のように書き換えます。



 //Person.Server.cs public partial class Person { public static readonly PropertyInfo<object> LastChangedProperty = RegisterProperty<object>( c => c.LastChanged ); public object LastChanged { get { return ReadProperty( LastChangedProperty ); } private set { LoadProperty( LastChangedProperty, value ); } } [NonSerialized] [NotUndoable] private IPersonRepository _personRepository; [Inject] [EditorBrowsable( EditorBrowsableState.Never )] private IPersonRepository PersonRepository { get { return _personRepository; } set { _personRepository = value; } } [NonSerialized][NotUndoable] private IContext _context; [Inject] [EditorBrowsable(EditorBrowsableState.Never)] private IContext Context { get { return _context; } set { _context = value; } } protected override void DataPortal_Create( ) { BusinessRules.CheckRules( ); } private void DataPortal_Fetch( SingleCriteria<Person, int> idCriteria ) { var personData = PersonRepository.FindPerson( idCriteria.Value ); if ( personData != null ) { CopyValuesFrom( personData ); LoadProperty( OrdersProperty, Orders.GetOrders( personData ) ); LoadProperty( AddressProperty, Address.GetAddress( personData ) ); } } private void CopyValuesFrom( PersonData personData ) { using ( BypassPropertyChecks ) { DataMapper.Map( personData, this ); } } protected override void DataPortal_Insert( ) { using ( var transaction = Context.BeginTransaction( ) ) { try { var personData = GetPersonData( ); int newId; object lastChanged; PersonRepository.AddPerson( personData, out newId, out lastChanged ); Id = newId; LastChanged = lastChanged; FieldManager.UpdateChildren( personData); transaction.Commit( ); } catch { transaction.Rollback( ); throw; } } } protected override void DataPortal_Update( ) { using ( var transaction = Context.BeginTransaction( ) ) { try { var personData = GetPersonData( ); object lastChanged; PersonRepository.EditPerson( personData, out lastChanged ); LastChanged = lastChanged; FieldManager.UpdateChildren( personData ); transaction.Commit( ); } catch { transaction.Rollback( ); throw; } } } private PersonData GetPersonData( ) { //  ,        //- Person  DTO PersonData ,    // CSLA DataMapper    DTO,     . var personData = new PersonData( ); DataMapper.Map( this, personData, OrdersProperty.Name, AddressProperty.Name ); return personData; } private void DataPortal_Delete( SingleCriteria<Person, int> idCriteria ) { PersonRepository.RemovePerson( idCriteria.Value ); } protected override void DataPortal_DeleteSelf( ) { PersonRepository.RemovePerson( Id ); } }
      
      





子クラスコード:
 //Orders.Server.cs public partial class Orders { [NonSerialized] [NotUndoable] private IOrderRepository _orderRepository; [Inject] [EditorBrowsable( EditorBrowsableState.Never )] protected IOrderRepository OrderRepository { get { return _orderRepository; } set { _orderRepository = value; } } internal static Address GetAddress( Person person ) { return DataPortal.FetchChild<Address>( person ); } protected void Child_Fetch( PersonData person) { var data = OrderRepository.FindOrders( person.Id ); RaiseListChangedEvents = false; AddRange( data.Select( Order.GetOrder ) ); RaiseListChangedEvents = true; } protected void Child_Update( PersonData person ) { base.Child_Update( person, OrderRepository ); } }
      
      





 //Order.Server.cs public partial class Order { internal static Order GetOrder( OrderData orderData ) { return DataPortal.FetchChild<Order>( orderData ); } protected void Child_Fetch( OrderData orderData ) { using ( BypassPropertyChecks ) { DataMapper.Map( orderData, this ); } } protected void Child_Insert( PersonData person, IOrderRepository orderRepository ) { var data = GetOrderData( ); Id = orderRepository.AddOrder( person.Id, data ); } protected void Child_Update( PersonData person, IOrderRepository orderRepository ) { var data = GetOrderData( ); orderRepository.EditOrder( person.Id, data ); } protected void Child_DeleteSelf( PersonData person, IOrderRepository orderRepository ) { orderRepository.RemoveOrder( person.Id, Id ); } private OrderData GetOrderData( ) { var orderData = new OrderData( ); DataMapper.Map( this, orderData ); return orderData; } }
      
      







 //Address.Server.cs public partial class Address { [NonSerialized, NotUndoable] private IAddressRepository _addressRepository; [Inject] [EditorBrowsable( EditorBrowsableState.Never )] protected IAddressRepository AddressRepository { get { return _addressRepository; } set { _addressRepository = value; } } internal static Address GetAddress( PersonData person ) { return DataPortal.FetchChild<Address>( person ); } protected void Child_Fetch( PersonData personData ) { using ( BypassPropertyChecks ) { var addressData = AddressRepository.FindAddress( personData.Id ); DataMapper.Map( addressData, this ); } } protected void Child_Insert( PersonData person ) { var data = GetAddressData( ); Id = AddressRepository.AddAddress( person.Id, data ); } protected void Child_Update( PersonData person ) { var data = GetAddressData( ); AddressRepository.EditAddress( person.Id, data ); } protected void Child_DeleteSelf( PersonData person ) { AddressRepository.RemoveAddress( person.Id, Id ); } private AddressData GetAddressData( ) { var addressData = new AddressData( ); DataMapper.Map( this, addressData ); return addressData; } }
      
      







特定のDAL呼び出しコードが消えました。 現在、DataPortal_XYZメソッドにアクセスする前に、オーバーライドされたアンカーを使用するサーバーデータポータルはPersonクラスのすべての依存関係を解決し、リポジトリの実装でDALアクセスコード全体をカプセル化できるようになります。



この方法のアイデアは、Johny Bekkumブログから引用されています。 彼はCSLAContribライブラリを作成しました。 このライブラリには、CSLAへの多くの便利な追加が含まれています。 特に、Property Setter InjectionをサポートするCSLAステレオタイプのセットがあります。 これらのステレオタイプのコードは、上記のコードに似ています。 DIコンテナとして、.NET Framework 4.0で導入されたMEF (Managed Extensibility Framework)が使用されます。



コンテナを直接使用するだけで、すべての問題を回避できることに注意してください。



 private IPersonRepository GetPersonRepository { return Core.Container.Kernel.Get<IPersonRepository>(); }
      
      





この場合、ContainerクラスはService Locatorとして使用されますが、これはしばしばantipatternとして定義されます。 ただし、この方法は、特に大規模なレガシーCSLAプロジェクトを伴う場合にも役立ちます。



リポジトリパターンを介したCSLA



リポジトリテンプレートの実装をさらに詳しく検討しましょう。 RepositoryBase基本クラスの将来のすべてのリポジトリに共通するOracleデータベースを操作するためのコードを取り出します。



 //RepositoryBase.cs internal class RepositoryBase { private readonly string _databaseName; protected RepositoryBase( string databaseName ) { _databaseName = databaseName; } protected virtual void ExecuteProcedure( string procName, params OracleParameter[] parameters ) { using ( var manager = TransactionManager<OracleConnection, OracleTransaction>.GetManager( _databaseName ) ) { using ( var command = manager.Connection.CreateCommand( ) ) { command.CommandType = CommandType.StoredProcedure; command.CommandText = procName; command.Transaction = manager.Transaction; if ( parameters != null ) { command.Parameters.AddRange( parameters ); } command.ExecuteNonQuery( ); if ( manager.RefCount == 1 ) { manager.Commit( ); } } } } protected virtual IEnumerable<T> GetRows<T>( string query, Func<SafeDataReader, T> fetchFromReader, params OracleParameter[] parameters ) { using ( var manager = ConnectionManager<OracleConnection>.GetManager( _databaseName ) ) { using ( var command = manager.Connection.CreateCommand( ) ) { command.CommandType = CommandType.Text; command.CommandText = query; if ( parameters != null ) { command.Parameters.AddRange( parameters ); } using ( var reader = new SafeDataReader( command.ExecuteReader( ) ) ) { while ( reader.Read( ) ) { yield return fetchFromReader( reader ); } } } } } }
      
      





次に、RepositoryBaseを使用してIPersonRepositoryコントラクトを実装します。



 //PersonRepository.cs internal sealed class PersonRepository : RepositoryBase, IPersonRepository { public PersonRepository( ) : base( "CSLAPROJECT" ) { } public PersonData FindPerson( int id ) { const string query = @"SELECT id ,first_name ,second_name ,age ,comment ,last_changed FROM All_persons WHERE id = :p_id"; return GetRows( query, FetchFromReader, new OracleParameter( "p_id", id ) ).First( ); } public void AddPerson( PersonData newPerson, out int newId, out object lastChanged ) { var parameters = GetPersonParameters( newPerson ); ExecuteProcedure( "csla_project.add_person", parameters ); newId = ( int )parameters.First( ).Value; lastChanged = parameters.Last( ).Value; } public void EditPerson( PersonData existingPerson, out object lastChanged ) { var parameters = GetPersonParameters( existingPerson ); ExecuteProcedure( "csla_project.update_person", parameters ); lastChanged = parameters.Last( ).Value; } public void RemovePerson( int personId ) { ExecuteProcedure( "csla_project.delete_person", new OracleParameter("p_id", personId) ); } private PersonData FetchFromReader( SafeDataReader reader ) { return new PersonData { Id = reader.GetInt32( "id" ), FirstName = reader.GetString( "first_name" ), SecondName = reader.GetString( "second_name" ), Age = reader.GetInt32( "age" ), Comment = reader.GetString( "comment" ), LastChanged = reader["last_changed"] }; } private OracleParameter[] GetPersonParameters( PersonData personData ) { return new[] { new OracleParameter( "p_id", personData.Id ){Direction = ParameterDirection.InputOutput}, new OracleParameter( "p_first_name", personData.FirstName ), new OracleParameter( "p_second_name", personData.SecondName ), new OracleParameter( "p_age", personData.Age ), new OracleParameter( "p_comment", personData.Comment ), new OracleParameter( "p_last_changed", OracleType.Int32 ) { Value = personData.LastChanged, Direction = ParameterDirection.Output } }; } }
      
      





トランザクションサポートクラスは次のとおりです。



 //Context.cs internal sealed class Context : IContext { ITransaction IContext.BeginTransaction( ) { return new Transaction( ); } } //Transaction.cs internal sealed class Transaction : ITransaction { private readonly TransactionManager<OracleConnection, OracleTransaction> _manager = TransactionManager<OracleConnection, OracleTransaction>.GetManager( "CSLAPROJECT" ); void ITransaction.Commit( ) { _manager.Commit( ); Dispose( ); } void ITransaction.Rollback( ) { Dispose( ); } public void Dispose( ) { _manager.Dispose( ); } }
      
      





この場合、トランザクションを保証するために、CSLA TransactionManagerクラスが使用されました。これにより、1つのデータポータル操作を実行するときに、ビジネスオブジェクトグラフ全体で同じ接続とそれに関連付けられたADO .NETトランザクションを使用できます。AddressRepositoryおよびOrde​​rRepositoryクラスは、上記のPersonRepositoryに似ています。



すべての変更後のクラス図は次のとおりです







。さまざまなプロジェクトのビジネスオブジェクト、リポジトリ、およびコントラクト配布します。最終的なパッケージ図は次のようになります。







最後に、CslaProject.DataAccess.OracleDbでDAL構成のNinjectモジュールを追加します。



 //Module.cs public class Module : NinjectModule { public override void Load( ) { Bind<IPersonRepository>( ).To<PersonRepository>( ).InSingletonScope( ); Bind<IOrderRepository>( ).To<OrderRepository>( ).InSingletonScope( ); Bind<IAddressRepository>( ).To<AddressRepository>( ).InSingletonScope( ); Bind<IGroupRepository>( ).To<GroupRepository>( ).InSingletonScope( ); Bind<IContext>( ).To<Context>( ).InSingletonScope( ); } }
      
      





コンテナ構成



ファサードクラスContainerに戻り、完全を期すために、ルートプロジェクトのDAL構成コードを提供します。



 private static void ConfigureKernel( ) { // InjectNonPublic = true, ..   var kernel = new StandardKernel( new NinjectSettings {InjectNonPublic = true} ); //   . var dependencyCatalogs = GetDependencyCatalogs( ); if ( dependencyCatalogs.Any( ) ) { foreach ( var values in dependencyCatalogs.Select( d => ConfigurationManager.AppSettings[ d ].Split( ';' ) ) ) { var catalogPath = Path.Combine( AppDomain.CurrentDomain.BaseDirectory, values[ 0 ] ); IEnumerable<string> depedencyLibNames; if ( values.Count( ) > 1 ) { var searchPattern = values[ 1 ]; depedencyLibNames = Directory.GetFiles( catalogPath, searchPattern ); } else { depedencyLibNames = Directory.GetFiles( catalogPath ); } foreach ( var file in depedencyLibNames ) { var dependency = Assembly.LoadFile( Path.Combine( catalogPath, file ) ); kernel.Load( dependency ); } } } else { //   ,   //    ,     CslaProject kernel.Load("CslaProject*.dll"); } _kernel = kernel; } private static string[] GetDependencyCatalogs( ) { return ConfigurationManager.AppSettings.AllKeys.Where( p => p.StartsWith( "CslaProject.Depencies", true, CultureInfo.InvariantCulture ) ).ToArray( ); }
      
      





App.Configは次のようになります。



 <?xml version="1.0"?> <configuration> <appSettings> <add key="CslaProject.Dependencies" value="Dependencies;CslaProject*.dll"/> </appSettings> <connectionStrings> <!-- <add name="CSLAPROJECT" providerName="System.Data.OracleClient" connectionString="user id=test_user;password=12345;data source=testdb;" /> --> </connectionStrings> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/> </startup> </configuration>
      
      





プロジェクトの依存関係の構成は、CslaProject.Dependenciesキーを持つオプションのappSettingsセクションで設定されます。オプションの値は、依存関係の実装を含むライブラリが配置されているアプリケーションのルートディレクトリ内のディレクトリの名前です。



起動したプロジェクトにCslaProject.Dependenciesキーを持つApp.Configがない場合(たとえば、CslaProject.UnitTestsユニットテストを持つプロジェクトからビジネスロジックコードが呼び出される場合)、名前がCslaProjectで始まるすべてのアセンブリが読み込まれます。そのため、テストリポジトリでDALを構成する単体テストを含むプロジェクトにNinjectモジュールを追加すると、DataPortal_XYZメソッドで使用されます。



おわりに



データアクセス層をCSLAビジネスクラスから分離するための考慮された方法は、完全に機能します。ただし、欠点もあります。





おそらくそれだけです。 ご清聴ありがとうございました。フォースが一緒にいてくれますように。



All Articles