📜 ⬆️ ⬇️

Dependency injection and unit implementation using Castle Windsor and NHibernate

In this article, I will demonstrate the implementation of dependency injection, repository, and unit of work using Castle Windsor as a DI container and NHibernate as an object-relational mapping tool (ORM).



Download source code - 962 Kb

Dependency injection is a software development pattern that allows you to remove hard-coded dependencies, as well as change them during execution and compilation [1] .
A repository is an intermediary between a domain and levels of data mapping, which uses a special interface to gain access to domain objects [2] .
A unit of work is a pattern that is used to define and control the transactional functions of your application [4] .
')
There are many articles, lessons and other resources on the topic of introducing entities and units of work, therefore I will not give them a definition. This article is dedicated to solving the difficulties that we will discuss later.

Difficulties


The development of a data-driven application should be carried out in accordance with some principles. It is about these principles that I want to talk about.

How to open and close connections

Of course, connections are best managed at the database level (in repositories). For example, you can open a connection, execute a command in the database and close the connection in each call to the repository method. But this option will be ineffective if we need to use the same connection for different methods within the same repository or for different methods in different repositories (think of a transaction using methods of different repositories).

When creating a site (with ASP.NET MVC or web forms), you can open a connection using Application_BeginRequest and close it using Application_EndRequest. But this approach has disadvantages:


The difficulties described above may seem insignificant to you, but for me they are a big problem. But what to do if you are developing not a website, but a Windows service that runs many threads that use the database for a while? Where in this case, open and close connections?

How to manage transactions

If your application (like most applications) uses transactions when working with a database, in which case you should start, commit or roll back the transaction? It is not possible to do this in repository methods — a transaction can include many different repository method calls. Therefore, all these operations can be performed by the domain layer. But this approach also has disadvantages:


As I mentioned above, you can manage transactions using Application_BeginRequest and Application_EndRequest. Here the same difficulties arise: you start or commit unnecessary transactions. In addition, it is necessary to roll back some of them, if necessary, after correcting errors.

If you are developing not a website, but an application, it will not be easy to find a good place to start, commit, and rollback transactions.

Therefore, it is best to start a transaction when you really need it, to fix it in the event that all your operations are successful, and roll back it only if any of your operations failed. It is this principle that I will be guided further.

Implementation


My application is a phone book created using ASP.NET MVC (as a web framework), Sql Server (as a DBMS), NHibernate (as an ORM), and Castle Windsor (as a dependency injection container).

Entities

In my implementation, the entity is converted to a table entry in the database. An entity in a domain-specific design is a persistent object with a unique identifier . In this case, all entities are derived from the Entity class below:

public interface IEntity<TPrimaryKey> { TPrimaryKey Id { get; set; } } public class Entity<TPrimaryKey> : IEntity<TPrimaryKey> { public virtual TPrimaryKey Id { get; set; } } 

An entity has a unique identifier - a primary key, which can have different types (int, long, guid, etc.). Accordingly, we are dealing with a generic class, and the entities People, Phone, City, etc., are derived from it. Here is the definition of the People class:

 public class Person : Entity<int> { public virtual int CityId { get; set; } public virtual string Name { get; set; } public virtual DateTime BirthDay { get; set; } public virtual string Notes { get; set; } public virtual DateTime RecordDate { get; set; } public Person() { Notes = ""; RecordDate = DateTime.Now; } } 

As you can see, the primary key for Person is int.

Entity transformation

Object-relational mapping tools, such as the Entity framework and NHibernate, require the definition of transforming entities into database tables. There are many ways to do this. I, for example, used the NHibernate Fluent API. You need to define a transformative class for all entities, as shown below using the People entity as an example:

 public class PersonMap : ClassMap<Person> { public PersonMap() { Table("People"); Id(x => x.Id).Column("PersonId"); Map(x => x.CityId); Map(x => x.Name); Map(x => x.BirthDay); Map(x => x.Notes); Map(x => x.RecordDate); 

Repositories (DB Level)


Repositories are used to create a database level to separate the data access logic from the upper levels. A repository class is usually created for each entity or aggregation — a group of entities. I created a repository for each entity. First, I defined an interface that should be implemented by all classes of the repository:

 /// <summary> /// This interface must be implemented by all repositories to ensure UnitOfWork to work. /// </summary> public interface IRepository { } /// <summary> /// This interface is implemented by all repositories to ensure implementation of fixed methods. /// </summary> /// <typeparam name="TEntity">Main Entity type this repository works on</typeparam> /// <typeparam name="TPrimaryKey">Primary key type of the entity</typeparam> public interface IRepository<TEntity, TPrimaryKey> : IRepository where TEntity : Entity<TPrimaryKey> { /// <summary> /// Used to get a IQueryable that is used to retrive entities from entire table. /// </summary> /// <returns>IQueryable to be used to select entities from database</returns> IQueryable<TEntity> GetAll(); /// <summary> /// Gets an entity. /// </summary> /// <param name="key">Primary key of the entity to get</param> /// <returns>Entity</returns> TEntity Get(TPrimaryKey key); /// <summary> /// Inserts a new entity. /// </summary> /// <param name="entity">Entity</param> void Insert(TEntity entity); /// <summary> /// Updates an existing entity. /// </summary> /// <param name="entity">Entity</param> void Update(TEntity entity); /// <summary> /// Deletes an entity. /// </summary> /// <param name="id">Id of the entity</param> void Delete(TPrimaryKey id); } 

Thus, all classes of the repository must implement the above methods. But NHibernate has a practically similar implementation of these methods. It turns out that you can define a base class for all repositories without applying the same logic to all of them. The following is the definition of NhRepositoryBase:

 /// <summary> /// Base class for all repositories those uses NHibernate. /// </summary> /// <typeparam name="TEntity">Entity type</typeparam> /// <typeparam name="TPrimaryKey">Primary key type of the entity</typeparam> public abstract class NhRepositoryBase<TEntity, TPrimaryKey> : IRepository<TEntity, TPrimaryKey> where TEntity : Entity<TPrimaryKey> { /// <summary> /// Gets the NHibernate session object to perform database operations. /// </summary> protected ISession Session { get { return NhUnitOfWork.Current.Session; } } /// <summary> /// Used to get a IQueryable that is used to retrive object from entire table. /// </summary> /// <returns>IQueryable to be used to select entities from database</returns> public IQueryable<TEntity> GetAll() { return Session.Query<TEntity>(); } /// <summary> /// Gets an entity. /// </summary> /// <param name="key">Primary key of the entity to get</param> /// <returns>Entity</returns> public TEntity Get(TPrimaryKey key) { return Session.Get<TEntity>(key); } /// <summary> /// Inserts a new entity. /// </summary> /// <param name="entity">Entity</param> public void Insert(TEntity entity) { Session.Save(entity); } /// <summary> /// Updates an existing entity. /// </summary> /// <param name="entity">Entity</param> public void Update(TEntity entity) { Session.Update(entity); } /// <summary> /// Deletes an entity. /// </summary> /// <param name="id">Id of the entity</param> public void Delete(TPrimaryKey id) { Session.Delete(Session.Load<TEntity>(id)); } } 

The session property is used to get a session object (a database connection object in NHibernate) from NhUnitOfWork.Current.Session, which receives the correct Session object for the current transaction. Therefore, it is not necessary to decide how to open and close a connection or transaction. Then I will try to elaborate on the description of this mechanism.

All CRUD operations are implemented by default for all repositories. Now you can create a PersonRepository with the ability to select, update and delete entries. To do this, you need to declare two types, as shown below.

 public interface IPersonRepository : IRepository<Person, int> { } public class NhPersonRepository : NhRepositoryBase<Person, int>, IPersonRepository { } 

The same can also be done for Phone and City entities. If you need to add a special repository method, you can do this in the repository of the corresponding entity. For example, add a new method to PhoneRepository so that you can delete the phones of a specific person:

 public interface IPhoneRepository : IRepository<Phone, int> { /// <summary> /// Deletes all phone numbers for given person id. /// </summary> /// <param name="personId">Id of the person</param> void DeletePhonesOfPerson(int personId); } public class NhPhoneRepository : NhRepositoryBase<Phone, int>, IPhoneRepository { public void DeletePhonesOfPerson(int personId) { var phones = GetAll().Where(phone => phone.PersonId == personId).ToList(); foreach (var phone in phones) { Session.Delete(phone); } } } 

Unit of work

The work unit is used to define and control the transactional functions of the application. First you need to define the interface IUnitOfWork:

 /// <summary> /// Represents a transactional job. /// </summary> public interface IUnitOfWork { /// <summary> /// Opens database connection and begins transaction. /// </summary> void BeginTransaction(); /// <summary> /// Commits transaction and closes database connection. /// </summary> void Commit(); /// <summary> /// Rollbacks transaction and closes database connection. /// </summary> void Rollback(); } 

The implementation of IUnitOfWork for NHibernate is shown below:

 /// <summary> /// Implements Unit of work for NHibernate. /// </summary> public class NhUnitOfWork : IUnitOfWork { /// <summary> /// Gets current instance of the NhUnitOfWork. /// It gets the right instance that is related to current thread. /// </summary> public static NhUnitOfWork Current { get { return _current; } set { _current = value; } } [ThreadStatic] private static NhUnitOfWork _current; /// <summary> /// Gets Nhibernate session object to perform queries. /// </summary> public ISession Session { get; private set; } /// <summary> /// Reference to the session factory. /// </summary> private readonly ISessionFactory _sessionFactory; /// <summary> /// Reference to the currently running transcation. /// </summary> private ITransaction _transaction; /// <summary> /// Creates a new instance of NhUnitOfWork. /// </summary> /// <param name="sessionFactory"></param> public NhUnitOfWork(ISessionFactory sessionFactory) { _sessionFactory = sessionFactory; } /// <summary> /// Opens database connection and begins transaction. /// </summary> public void BeginTransaction() { Session = _sessionFactory.OpenSession(); _transaction = Session.BeginTransaction(); } /// <summary> /// Commits transaction and closes database connection. /// </summary> public void Commit() { try { _transaction.Commit(); } finally { Session.Close(); } } /// <summary> /// Rollbacks transaction and closes database connection. /// </summary> public void Rollback() { try { _transaction.Rollback(); } finally { Session.Close(); } } } 

The static Current property is key for the entire class. It gets and sets up the _current field, labeled ThreadStatic . That way, I can use the same unit of work object on the same thread. This means that several objects can share a single connection or transaction. Finally, I define the attribute used to mark the method to be transactional:

 /// <summary> /// This attribute is used to indicate that declaring method is transactional (atomic). /// A method that has this attribute is intercepted, a transaction starts before call the method. /// At the end of method call, transaction is commited if there is no exception, othervise it's rolled back. /// </summary> [AttributeUsage(AttributeTargets.Method)] public class UnitOfWorkAttribute : Attribute { } 

If a particular method must be transactional, it must be marked with the UnitOfWork attribute. Then I intercept these methods using dependency injection, as shown below.

Service level

In domain-specific design, domain services are used to implement domain logic and can use repositories to perform database tasks. For example, here’s the definition of a PersonService:

 public class PersonService : IPersonService { private readonly IPersonRepository _personRepository; private readonly IPhoneRepository _phoneRepository; public PersonService(IPersonRepository personRepository, IPhoneRepository phoneRepository) { _personRepository = personRepository; _phoneRepository = phoneRepository; } public void CreatePerson(Person person) { _personRepository.Insert(person); } [UnitOfWork] public void DeletePerson(int personId) { _personRepository.Delete(personId); _phoneRepository.DeletePhonesOfPerson(personId); } //... some other methods are not shown here since it's not needed. See source codes. } 

Note the use of the UnitOfWork attribute defined above. The DeletePerson method is marked as UnitOfWork. It calls two different repository methods, and these method calls must be transactional. On the other hand, the CreatePerson method is not marked as UnitOfWork, because it calls only one repository method, Insert for the person repository, which can manage its own transaction: open and close it. Next we will see how this is implemented.

Dependency Injection (DI)

DI containers, such as Castle Windsor, are used to manage dependencies and the life cycles of an application object. This allows you to create loosely coupled components and modules in your application. In an ASP.NET application, the DI container is usually initialized in the global.asax file. This usually happens at startup.

 public class MvcApplication : System.Web.HttpApplication { private WindsorContainer _windsorContainer; protected void Application_Start() { InitializeWindsor(); // Other startup logic... } protected void Application_End() { if (_windsorContainer != null) { _windsorContainer.Dispose(); } } private void InitializeWindsor() { _windsorContainer = new WindsorContainer(); _windsorContainer.Install(FromAssembly.This()); ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(_windsorContainer.Kernel)); } } 

The WindsowContainer object, which is the key to dependency injection, is created when the application starts and is deleted at the end. To implement dependencies, you also need to change the default factory settings for the MVC controllers in the InitializeWindsor method. Whenever a Controller MVC on ASP.NET is required (in each web request), it creates it using dependency injection. More information about Castle Windsor can be found here . Here is the controller factory:

 public class WindsorControllerFactory : DefaultControllerFactory { private readonly IKernel _kernel; public WindsorControllerFactory(IKernel kernel) { _kernel = kernel; } public override void ReleaseController(IController controller) { _kernel.ReleaseComponent(controller); } protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { if (controllerType == null) { throw new HttpException(404, string.Format("The controller for path '{0}' could not be found.", requestContext.HttpContext.Request.Path)); } return (IController)_kernel.Resolve(controllerType); } } 

It is quite simple and understandable even at first glance. You should implement our own object dependencies using the PhoneBookDependencyInstaller class. Castle Windsor automatically finds this class thanks to the IWindsorInstaller implementation. Recall the _windsorContainer.Install line (FromAssembly.This ()); in the global.asax file.

 public class PhoneBookDependencyInstaller : IWindsorInstaller { public void Install(IWindsorContainer container, IConfigurationStore store) { container.Kernel.ComponentRegistered += Kernel_ComponentRegistered; //Register all controllers container.Register( //Nhibernate session factory Component.For<ISessionFactory>().UsingFactoryMethod(CreateNhSessionFactory).LifeStyle.Singleton, //Unitofwork interceptor Component.For<NhUnitOfWorkInterceptor>().LifeStyle.Transient, //All repoistories Classes.FromAssembly(Assembly.GetAssembly(typeof(NhPersonRepository))).InSameNamespaceAs<NhPersonRepository>().WithService.DefaultInterfaces().LifestyleTransient(), //All services Classes.FromAssembly(Assembly.GetAssembly(typeof(PersonService))).InSameNamespaceAs<PersonService>().WithService.DefaultInterfaces().LifestyleTransient(), //All MVC controllers Classes.FromThisAssembly().BasedOn<IController>().LifestyleTransient() ); } /// <summary> /// Creates NHibernate Session Factory. /// </summary> /// <returns>NHibernate Session Factory</returns> private static ISessionFactory CreateNhSessionFactory() { var connStr = ConfigurationManager.ConnectionStrings["PhoneBook"].ConnectionString; return Fluently.Configure() .Database(MsSqlConfiguration.MsSql2008.ConnectionString(connStr)) .Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.GetAssembly(typeof(PersonMap)))) .BuildSessionFactory(); } void Kernel_ComponentRegistered(string key, Castle.MicroKernel.IHandler handler) { //Intercept all methods of all repositories. if (UnitOfWorkHelper.IsRepositoryClass(handler.ComponentModel.Implementation)) { handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(NhUnitOfWorkInterceptor))); } //Intercept all methods of classes those have at least one method that has UnitOfWork attribute. foreach (var method in handler.ComponentModel.Implementation.GetMethods()) { if (UnitOfWorkHelper.HasUnitOfWorkAttribute(method)) { handler.ComponentModel.Interceptors.Add(new InterceptorReference(typeof(NhUnitOfWorkInterceptor))); return; } } } } 

As you can see, I register all the components using the Register method in Castle Windsor. Note: all classes of the repository are registered in one line. The same needs to be done for services and controllers. I use a factory method to create an ISessionFactory factory that creates ISession objects (a database connection) for use with NHibernate. At the beginning of the Install method, I register the ComponentRegistered event to implement interception logic. Take a look at the Kernel_ComponentRegistered. If the method is a repository method, I will always use interception for it. In addition, if the method is marked with the UnitOfWork attribute, it is also intercepted by the NhUnitOfWorkInterceptor class.

Intercept

Interception is a special technique that allows you to execute some code at the beginning and end of a method call. Interception is usually used for logging, profiling, caching, etc. It allows you to dynamically embed code in the necessary methods, without changing the methods themselves.

In our case, the interception is useful for the implementation of the unit of work. If a particular method is a repository method or is marked as a UnitOfWork attribute (described above), I open a connection to the database and (Session in NHibernate) and start the transaction at the beginning of the method. If the intercepted method did not throw a single exception, the transaction is committed to the end of the method. If the method throws an exception, the entire transaction is rolled back. Take a look at my implementation of the NhUnitOfWorkInterceptor class:

 /// <summary> /// This interceptor is used to manage transactions. /// </summary> public class NhUnitOfWorkInterceptor : IInterceptor { private readonly ISessionFactory _sessionFactory; /// <summary> /// Creates a new NhUnitOfWorkInterceptor object. /// </summary> /// <param name="sessionFactory">Nhibernate session factory.</param> public NhUnitOfWorkInterceptor(ISessionFactory sessionFactory) { _sessionFactory = sessionFactory; } /// <summary> /// Intercepts a method. /// </summary> /// <param name="invocation">Method invocation arguments</param> public void Intercept(IInvocation invocation) { //If there is a running transaction, just run the method if (NhUnitOfWork.Current != null || !RequiresDbConnection(invocation.MethodInvocationTarget)) { invocation.Proceed(); return; } try { NhUnitOfWork.Current = new NhUnitOfWork(_sessionFactory); NhUnitOfWork.Current.BeginTransaction(); try { invocation.Proceed(); NhUnitOfWork.Current.Commit(); } catch { try { NhUnitOfWork.Current.Rollback(); } catch { } throw; } } finally { NhUnitOfWork.Current = null; } } private static bool RequiresDbConnection(MethodInfo methodInfo) { if (UnitOfWorkHelper.HasUnitOfWorkAttribute(methodInfo)) { return true; } if (UnitOfWorkHelper.IsRepositoryMethod(methodInfo)) { return true; } return false; } } 

Intercept is the main method. First, it checks if there is a previously started transaction for the given thread. If yes, it does not start a new transaction, but uses the current one (see NhUnitOfWork.Current). Thus, nested method calls with the UnitOfWork attribute can share the same transaction. A transaction is created or committed only by the first use of the unit of work method. In addition, if the method is not transactional, just a method call and a return occur. The invocation.Proceed () command makes a call to the intercepted method.

If there are no current transactions, you need to start a new transaction and fix it in the absence of errors. Otherwise, you should roll it back. After that, you should set NhUnitOfWork.Current = null, and then, if necessary, start other transactions for this stream. You can also take a look at the UnitOfWorkHelper class.

Thus, the code for opening and closing connections, as well as for starting, fixing and rolling back transactions is determined only at one point in the application.

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


All Articles