Dependency injection and unit of work implementation with Castle Windsor and NHibernate
- Transfer
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 (ORM) tool.
→ 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 at runtime and compilation [1] .
A repository is an intermediary between a domain and data display layers, which uses a special interface to gain access to domain objects [2] .
Unit of work - a pattern that is used to define and manage the transactional functions of your application [4].
On the topic of introducing entities and units of work, there are many articles, lessons, and other resources, so I will not give them a definition. This article is dedicated to solving the difficulties that we will discuss later.
The development of a data-driven application must follow 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 every call to the repository method. But this option will be inefficient 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 the 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 if you do not develop a site, but a Windows service that launches many threads that use the database for some time? 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, where in this case should you start, commit or roll back a transaction? It is not possible to do this in the repository methods - a transaction can include many different calls to the repository methods. 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. The same difficulties arise here: you start or commit unnecessary transactions. In addition, you will need to roll back some of them 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 roll back transactions.
Therefore, it is best to start a transaction when you really need it, record it if all your operations are successful, and roll it back only if any of your operations failed. It is with this principle that I will be guided further.
My application is a phone book created using ASP.NET MVC (as a web framework), Sql Server (as a DBMS), NHibernate (as ORM), and Castle Windsor (as a dependency injection container).
Entities
In my implementation, an entity is converted to a table entry in the database. An entity in object-oriented design is a persistent object with a unique identifier . In this case, all entities are derived from the Entity class below:
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's what the People class definition looks like:
As you can see, the primary key for Person is defined as int.
Transforming Entities
Object-relational mapping tools, such as the Entity and NHibernate frameworks, require the definition of transforming entities to database tables. There are many ways to implement this. For example, I used the NHibernate Fluent API. You need to define a transform class for all entities, as shown below with the People entity example:
Repositories are used to create the database level to separate the logic of data access 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 repository classes:
Thus, all repository classes 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:
The session property is used to get the session object (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. Further I will try to dwell in more detail 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, declare two types, as shown below.
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 remove the phones of a certain person:
Unit of work
The unit of work is used to define and manage the transactional functions of the application. First of all, you need to define the IUnitOfWork interface:
The implementation of IUnitOfWork for NHibernate is shown below:
The static property Current is key to the entire class. It receives and configures the _current field marked as ThreadStatic . That way I can use the same unit of work object in the same thread. This means that multiple objects can share the same connection or transaction. Finally, I define the attribute used to mark the method, which should be transactional:
If a particular method is to be transactional, it must be marked with the UnitOfWork attribute. Then I will intercept these methods using dependency injection as shown below.
Service Level
In subject-oriented design, domain services are used to implement domain logic and can use repositories to perform database tasks. For example, this is what the definition of PersonService looks like:
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. Further we will see how this is implemented.
Dependency Injection (DI)
DI containers, such as Castle Windsor, are used to manage the dependencies and life cycles of an application object. This allows you to create loosely coupled components and modules in an application. In an ASP.NET application, a DI container is usually initialized in the global.asax file. This usually happens at startup.
The WindsowContainer object, which is key when implementing dependencies, is created when the application starts and is deleted at the end. To implement dependencies, you must also change the default settings of the MVC controller factory in the InitializeWindsor method. Whenever a MVC pattern on ASP.NET requires a Controller (in every web request), it creates it using dependency injection. More information about Castle Windsor can be found here . Here's what the controller factory looks like:
It is quite simple and understandable even at first glance. You should embed our own object dependencies using the PhoneBookDependencyInstaller class. Castle Windsor automatically finds this class thanks to the implementation of IWindsorInstaller. Remember the line _windsorContainer.Install (FromAssembly.This ()); in the global.asax file.
As you can see, I register all components using the Register method in Castle Windsor. Please note: all repository classes are registered on a single line. The same needs to be done for services and controllers. I use the factory method to create an ISessionFactory factory that creates ISession objects (database connection) for use with NHibernate. At the beginning of the Install method, I log the ComponentRegistered event to implement the interception logic. Take a look at Kernel_ComponentRegistered. If the method is a repository method, I will always use interception for it. In addition, if a 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 at the 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, interception is useful for the implementation of a 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 does not throw a single exception, the transaction is committed at the end of the method. If the method throws an exception, the whole transaction is rolled back. Take a look at my implementation of the NhUnitOfWorkInterceptor class:
Intercept is the main method. First, it checks if a previously running transaction exists for a given thread. If so, 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 the first time you use the unit of work method. In addition, if the method is not transactional, it simply calls the method and returns. 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 commit it in the absence of errors. Otherwise, roll it back. After that, set NhUnitOfWork.Current = null, so that later, 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, committing and rolling back transactions, is defined only at one point in the application.
→ 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 at runtime and compilation [1] .
A repository is an intermediary between a domain and data display layers, which uses a special interface to gain access to domain objects [2] .
Unit of work - a pattern that is used to define and manage the transactional functions of your application [4].
On the topic of introducing entities and units of work, there are many articles, lessons, and other resources, so 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 must follow 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 every call to the repository method. But this option will be inefficient 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 the connection using Application_BeginRequest and close it using Application_EndRequest. But this approach has disadvantages:
- The database is opened and closed for each request, even if not all of them use the database. It turns out that the connection is taken from the pool even when it is not used.
- The database opens at the beginning of the query and closes at the end. But sometimes the request is too long, and the database operation takes an insignificant part of its time, which again leads to inefficient use of the connection pool.
The difficulties described above may seem insignificant to you, but for me they are a big problem. But what if you do not develop a site, but a Windows service that launches many threads that use the database for some time? 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, where in this case should you start, commit or roll back a transaction? It is not possible to do this in the repository methods - a transaction can include many different calls to the repository methods. Therefore, all these operations can be performed by the domain layer. But this approach also has disadvantages:
- The domain layer includes a database-specific code, which violates the principle of a single duty and the use of a multi-level representation.
- This approach duplicates the transaction logic in each method of the domain layer.
As I mentioned above, you can manage transactions using Application_BeginRequest and Application_EndRequest. The same difficulties arise here: you start or commit unnecessary transactions. In addition, you will need to roll back some of them 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 roll back transactions.
Therefore, it is best to start a transaction when you really need it, record it if all your operations are successful, and roll it back only if any of your operations failed. It is with 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 ORM), and Castle Windsor (as a dependency injection container).
Entities
In my implementation, an entity is converted to a table entry in the database. An entity in object-oriented 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 Id { get; set; }
}
public class Entity : IEntity
{
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's what the People class definition looks like:
public class Person : Entity
{
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 defined as int.
Transforming Entities
Object-relational mapping tools, such as the Entity and NHibernate frameworks, require the definition of transforming entities to database tables. There are many ways to implement this. For example, I used the NHibernate Fluent API. You need to define a transform class for all entities, as shown below with the People entity example:
public class PersonMap : ClassMap
{
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 the database level to separate the logic of data access 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 repository classes:
///
/// This interface must be implemented by all repositories to ensure UnitOfWork to work.
///
public interface IRepository
{
}
///
/// This interface is implemented by all repositories to ensure implementation of fixed methods.
///
/// Main Entity type this repository works on
/// Primary key type of the entity
public interface IRepository : IRepository where TEntity : Entity
{
///
/// Used to get a IQueryable that is used to retrive entities from entire table.
///
/// IQueryable to be used to select entities from database
IQueryable GetAll();
///
/// Gets an entity.
///
/// Primary key of the entity to get
/// Entity
TEntity Get(TPrimaryKey key);
///
/// Inserts a new entity.
///
/// Entity
void Insert(TEntity entity);
///
/// Updates an existing entity.
///
/// Entity
void Update(TEntity entity);
///
/// Deletes an entity.
///
/// Id of the entity
void Delete(TPrimaryKey id);
}
Thus, all repository classes 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:
///
/// Base class for all repositories those uses NHibernate.
///
/// Entity type
/// Primary key type of the entity
public abstract class NhRepositoryBase : IRepository where TEntity : Entity
{
///
/// Gets the NHibernate session object to perform database operations.
///
protected ISession Session { get { return NhUnitOfWork.Current.Session; } }
///
/// Used to get a IQueryable that is used to retrive object from entire table.
///
/// IQueryable to be used to select entities from database
public IQueryable GetAll()
{
return Session.Query();
}
///
/// Gets an entity.
///
/// Primary key of the entity to get
/// Entity
public TEntity Get(TPrimaryKey key)
{
return Session.Get(key);
}
///
/// Inserts a new entity.
///
/// Entity
public void Insert(TEntity entity)
{
Session.Save(entity);
}
///
/// Updates an existing entity.
///
/// Entity
public void Update(TEntity entity)
{
Session.Update(entity);
}
///
/// Deletes an entity.
///
/// Id of the entity
public void Delete(TPrimaryKey id)
{
Session.Delete(Session.Load(id));
}
}
The session property is used to get the session object (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. Further I will try to dwell in more detail 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, declare two types, as shown below.
public interface IPersonRepository : IRepository
{
}
public class NhPersonRepository : NhRepositoryBase, 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 remove the phones of a certain person:
public interface IPhoneRepository : IRepository
{
///
/// Deletes all phone numbers for given person id.
///
/// Id of the person
void DeletePhonesOfPerson(int personId);
}
public class NhPhoneRepository : NhRepositoryBase, 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 unit of work is used to define and manage the transactional functions of the application. First of all, you need to define the IUnitOfWork interface:
///
/// Represents a transactional job.
///
public interface IUnitOfWork
{
///
/// Opens database connection and begins transaction.
///
void BeginTransaction();
///
/// Commits transaction and closes database connection.
///
void Commit();
///
/// Rollbacks transaction and closes database connection.
///
void Rollback();
}
The implementation of IUnitOfWork for NHibernate is shown below:
///
/// Implements Unit of work for NHibernate.
///
public class NhUnitOfWork : IUnitOfWork
{
///
/// Gets current instance of the NhUnitOfWork.
/// It gets the right instance that is related to current thread.
///
public static NhUnitOfWork Current
{
get { return _current; }
set { _current = value; }
}
[ThreadStatic]
private static NhUnitOfWork _current;
///
/// Gets Nhibernate session object to perform queries.
///
public ISession Session { get; private set; }
///
/// Reference to the session factory.
///
private readonly ISessionFactory _sessionFactory;
///
/// Reference to the currently running transcation.
///
private ITransaction _transaction;
///
/// Creates a new instance of NhUnitOfWork.
///
///
public NhUnitOfWork(ISessionFactory sessionFactory)
{
_sessionFactory = sessionFactory;
}
///
/// Opens database connection and begins transaction.
///
public void BeginTransaction()
{
Session = _sessionFactory.OpenSession();
_transaction = Session.BeginTransaction();
}
///
/// Commits transaction and closes database connection.
///
public void Commit()
{
try
{
_transaction.Commit();
}
finally
{
Session.Close();
}
}
///
/// Rollbacks transaction and closes database connection.
///
public void Rollback()
{
try
{
_transaction.Rollback();
}
finally
{
Session.Close();
}
}
}
The static property Current is key to the entire class. It receives and configures the _current field marked as ThreadStatic . That way I can use the same unit of work object in the same thread. This means that multiple objects can share the same connection or transaction. Finally, I define the attribute used to mark the method, which should be transactional:
///
/// 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.
///
[AttributeUsage(AttributeTargets.Method)]
public class UnitOfWorkAttribute : Attribute
{
}
If a particular method is to be transactional, it must be marked with the UnitOfWork attribute. Then I will intercept these methods using dependency injection as shown below.
Service Level
In subject-oriented design, domain services are used to implement domain logic and can use repositories to perform database tasks. For example, this is what the definition of PersonService looks like:
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. Further we will see how this is implemented.
Dependency Injection (DI)
DI containers, such as Castle Windsor, are used to manage the dependencies and life cycles of an application object. This allows you to create loosely coupled components and modules in an application. In an ASP.NET application, a 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 key when implementing dependencies, is created when the application starts and is deleted at the end. To implement dependencies, you must also change the default settings of the MVC controller factory in the InitializeWindsor method. Whenever a MVC pattern on ASP.NET requires a Controller (in every web request), it creates it using dependency injection. More information about Castle Windsor can be found here . Here's what the controller factory looks like:
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 embed our own object dependencies using the PhoneBookDependencyInstaller class. Castle Windsor automatically finds this class thanks to the implementation of IWindsorInstaller. Remember the line _windsorContainer.Install (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().UsingFactoryMethod(CreateNhSessionFactory).LifeStyle.Singleton,
//Unitofwork interceptor
Component.For().LifeStyle.Transient,
//All repoistories
Classes.FromAssembly(Assembly.GetAssembly(typeof(NhPersonRepository))).InSameNamespaceAs().WithService.DefaultInterfaces().LifestyleTransient(),
//All services
Classes.FromAssembly(Assembly.GetAssembly(typeof(PersonService))).InSameNamespaceAs().WithService.DefaultInterfaces().LifestyleTransient(),
//All MVC controllers
Classes.FromThisAssembly().BasedOn().LifestyleTransient()
);
}
///
/// Creates NHibernate Session Factory.
///
/// NHibernate Session Factory
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 components using the Register method in Castle Windsor. Please note: all repository classes are registered on a single line. The same needs to be done for services and controllers. I use the factory method to create an ISessionFactory factory that creates ISession objects (database connection) for use with NHibernate. At the beginning of the Install method, I log the ComponentRegistered event to implement the interception logic. Take a look at Kernel_ComponentRegistered. If the method is a repository method, I will always use interception for it. In addition, if a 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 at the 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, interception is useful for the implementation of a 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 does not throw a single exception, the transaction is committed at the end of the method. If the method throws an exception, the whole transaction is rolled back. Take a look at my implementation of the NhUnitOfWorkInterceptor class:
///
/// This interceptor is used to manage transactions.
///
public class NhUnitOfWorkInterceptor : IInterceptor
{
private readonly ISessionFactory _sessionFactory;
///
/// Creates a new NhUnitOfWorkInterceptor object.
///
/// Nhibernate session factory.
public NhUnitOfWorkInterceptor(ISessionFactory sessionFactory)
{
_sessionFactory = sessionFactory;
}
///
/// Intercepts a method.
///
/// Method invocation arguments
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 a previously running transaction exists for a given thread. If so, 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 the first time you use the unit of work method. In addition, if the method is not transactional, it simply calls the method and returns. 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 commit it in the absence of errors. Otherwise, roll it back. After that, set NhUnitOfWork.Current = null, so that later, 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, committing and rolling back transactions, is defined only at one point in the application.