📜 ⬆️ ⬇️

Repository Pattern via CSLA .NET

Creating low-coupling systems (Low Coupling) between modules provides many advantages when developing software. In applications written using the CSLA .NET framework , the use of standard templates for breaking dependencies may not always be obvious.

This article will look at the option of separating the data access layer (Data Access Layer, DAL) from the Business Logic layer (Business Layer) using the Repository template and describe the most common way to integrate dependencies (Depency Injection) into CSLA .NET business objects. Used by CSLA version 4.1.

Example


So let us have the root business object Person. This business object has several simple properties from the subject area, a child collection of Order objects, and a child Address object:

//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 ); } } } 

Child Class Code
 //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 ); } } } 


The resulting class diagram is shown in the figure:
')


Note that business classes are marked with the partial modifier, and the entire data access code has been moved to other parts of the classes to improve readability. Let's now look at the DAL access code.

Standard interaction with DAL


First we turn to the code related to the creation of the Person object. As you can see, the class has a single constructor without parameters. The creation and retrieval of an object is answered by factory methods that access the CSLA client data portal. The data client portal, in turn, accesses the server data portal. The latter refers, through reflection, to the private methods of the DataPortal_Create or DataPortal_Fetch of the Person class - to create or retrieve an object, respectively:




The same scheme is used for inserting, updating, and deleting the Person object, but the DataPortal_Insert, DataPortal_Update and DataPortal_DeleteSelf methods are used, respectively:



Remember that the Save method (CSLA infrastructure method) actually returns a new business object created on the server data portal, which is why you should update all references to the saved object in the client code (the concept of the so-called mobile business objects, Mobile Object Pattern):



Finally, for the force removal, the DataPortal_Delete method is used:



Thus, the DataPortal_XYZ methods contain all the data access logic — not only for creating and extracting data, but also for updating and deleting a Person object in the DAL. In these methods, we see specific logic that is commonly used to access data: SQL query code, connect to a database, create transactions to update child objects, etc.

 //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} }; } } 

The DAL access code for child objects of Person is the same, except that when updating child objects, Person sends a link to the data portal to itself and an ADO .NET transaction instance, so the corresponding DataPortal_XYZ methods of the child objects contain additional arguments.

Child class code:
 //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 ) }; } } 


Repository pattern


So far we have seen the standard implementation of CSLA business objects, which can often be seen in the code of many legacy programs developed using CSLA .NET. As can be seen from the code, the data access layer and the business logic layer are closely related to each other. If we abstract from the data access layer, then we will get rid of the low-level details in the business logic and the specific dependencies that they have. This will simplify the unit testing of our business objects and allow us to change the data access logic regardless of the logic of the business level. Therefore, the task of creating an additional level of indirection between business logic and the data access layer arises. For example, you can use the famous template Repository . Create an interface with the necessary methods:

 //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 ); } 

The PersonData class is a simple data transfer object (DTO, Data Transfer Object):

 //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; } } 

To abstract from transactional ADO .NET code, create simple IContext and ITransaction interfaces:

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

Now I would like the methods of the interfaces defined above to be used in the DataPortal_XYZ methods of the Person class instead of the specific DAL code. The challenge is to inject dependencies into the Person business object. The classic way is to push dependencies through the Person constructor because of the limitations of the CSLA (using factory methods), so consider other possible ways to solve this problem.

Setter method injection


Add a private field of type IPersonRepository to the Person class:

 [NonSerialized] [NotUndoable] private IPersonRepository _personRepository; 

Note the use of attributes [NonSerialized] [NotUndoable] - the IPersonRepository dependency should not be serialized when moving a Person object from one physical node to another (Mobile Object Pattern) and participate in a multi-level CSLA (N-Level Undo) cancellation.

Next, add the repository configuration method:

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

Instead of the configuration method, you can use the PersonRepository property (the so-called Property Setter Injection):

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

Note that in our case, the PersonRepository property will be used only on the server side of the data portal, so you can hide it from Intellisence using the EditorBrowsable attribute.

In general, you can embed dependencies into a business object already created by the CSLA infrastructure using the following anchors of the BusinessBase abstract class:

DataPortal_OnDataPortalInvoke, Child_OnDataPortalInvoke and OnDeserialized. The first two methods are called by the server side of the data portal before calling the DataPortal_XXX methods of the business object. The first method is if the business object is the root, the second is if the child. The third method is called after the mobile object is deserialized on the server or client. As part of the Repository template, it is not necessary to override it (dependencies are used only on the server), but it is necessary for the general case of dependency injection in the CSLA business objects.

Thus, we can write the Inject method, which will be called in all three methods and inject dependencies into the business object created by the CSLA. Based on the foregoing, we will write a new stereotype InjectableBusinessBase, which will serve as a substitute for the standard stereotype of 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( ) { //         . } } 

Note that for each stereotype of the CSLA business objects, a new stereotype with redefined anchors must be created. The code of new stereotypes will be similar to the code above. Now, business classes with dependencies should inherit new stereotypes:

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

To implement the Inject method, add the following front DI container class (using 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; } } } 

We do not care how to configure the DI container itself right now, but only the InjectInto method is important. Now you can return to the Inject () method of the InjectableBusinessBase stereotype:

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

Finally, add the IPersonRepository and IContext properties to the server part of the Person class and rewrite the DataPortal_XYZ methods as follows:

 //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 ); } } 

Child class code:
 //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; } } 


The specific DAL reference code has disappeared. Now, before accessing the DataPortal_XYZ methods, the server data portal using the overridden anchors will resolve all dependencies of the Person class, which will allow encapsulating all DAL access code in the repository implementation.

The idea of ​​this method is taken from the blog Johny Bekkum . He created the CSLAContrib library, which contains many useful additions to the CSLA. In particular, it has a set of CSLA stereotypes that support Property Setter Injection. The code for these stereotypes is similar to the code above. The MI (Managed Extensibility Framework), introduced in the .NET Framework 4.0, is used as a DI container.

Note that all difficulties can be avoided simply by using the Container directly:

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

In this case, the Container class is used as a Service Locator, which is often referred to as an anti-pattern . However, this method can also be useful, especially when accompanied by large legacy projects on the CSLA.

CSLA via Repository Pattern


Let us consider in more detail the implementation of the Repository template. We'll take out the code common for all future repositories for working with Oracle databases to the base class RepositoryBase:

 //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 ); } } } } } } 

IPersonRepository, RepositoryBase:

 //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 , ADO .NET . AddressRepository OrderRepository PersonRepository.

:



-, . :



, CslaProject.DataAccess.OracleDb Ninject DAL:

 //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> 

appSettings CslaProject.Dependencies. , .

App.Config CslaProject.Dependencies (, - - CslaProject.UnitTests), , CslaProject. , - Ninject, DAL , DataPortal_XYZ .

Conclusion


- CSLA . :


On this, perhaps, everything. , .

Source: https://habr.com/ru/post/314110/


All Articles