В этой статье я продемонстрирую реализацию внедрения зависимости, репозитория и единицы работы, используя Castle Windsor в качестве DI-контейнера и NHibernate как инструмент объектно-реляционного отображения (ORM).
> Скачать исходный код – 962 Кб
Внедрение зависимости – это паттерн разработки ПО, который позволяет удалять жестко запрограммированные зависимости, а также менять их во время выполнения и компиляции [1].
Репозиторий – это посредник между доменом и уровнями отображения данных, который использует специальный интерфейс, чтобы получить доступ к объектам домена [2].
Единица работы – паттерн, который используется для определения и управления транзакционными функциями вашего приложения [4].
На тему внедрения сущностей и единицы работы есть много статей, уроков и прочих ресурсов, потому я не буду давать им определение. Эта статья посвящена решению трудностей, о которых мы поговорим далее.
public interface IEntity<TPrimaryKey>
{
TPrimaryKey Id { get; set; }
}
public class Entity<TPrimaryKey> : IEntity<TPrimaryKey>
{
public virtual TPrimaryKey Id { get; set; }
}
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;
}
}
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);
/// <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);
}
/// <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));
}
}
public interface IPersonRepository : IRepository<Person, int>
{
}
public class NhPersonRepository : NhRepositoryBase<Person, int>, IPersonRepository
{
}
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);
}
}
}
/// <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();
}
/// <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();
}
}
}
/// <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
{
}
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.
}
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));
}
}
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);
}
}
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;
}
}
}
}
/// <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;
}
}
К сожалению, не доступен сервер mySQL