NHibernate Sessions + транзакции с использованием ASP.NET MVC 3 - PullRequest
3 голосов
/ 06 мая 2011

Я сейчас пишу новое приложение, хотя стандартом компании является использование NHibernate (потому что это стандарт для всех проектов), и вместо этого я использую ASP.NET MVC 3 из-за его зрелости. Я реализовал свои транзакции в контроллерах (что, якобы, так и должно быть), поэтому в моем корневом контроллере это выглядит так:

[TransactionPerRequest]
public class FbsController : Controller
{

}

Тогда все мои контроллеры наследуются от этого FbsController. Причина в том, что 90% всех моих действий будут отправляться в базу данных, поэтому затраты на создание транзакции и ее удаление для оставшихся 10% действий (которые будут выполняться редко) не стоят украшать каждое действие [TransactionPerRequest].

Что меня всегда озадачивало, так это в отношении сессий NHibernate. В классах хранилища это похоже на то, что у меня есть, хотя в других проектах это не так:

    public void Add(User user)
    {
        using (ISession session = NHibernateHelper.OpenSession())
        {
            session.Save(user);
        }
    }

    public void Remove(User user)
    {
        using (ISession session = NHibernateHelper.OpenSession())
        {
            session.Delete(user);
        }
    }

    public User GetById(int userId)
    {
        using (ISession session = NHibernateHelper.OpenSession())
        {
            return session.QueryOver<User>()
                .Where(c => c.UserID == userId)
                .SingleOrDefault();
        }
    }

Итак, для большинства функций в моем репозитории я должен открыть сессию. Есть ли способ избежать этого поведения, поэтому мне не нужно открывать сеанс внутри каждого метода репозитория? Это кажется немного нелогичным, так как обычно мне приходится делать это для каждого. Мне было интересно, каково было решение всех остальных для проблемы транзакций и сеансов, которая, как я вижу, замусорена вокруг кода различными способами.

Реально я хочу, чтобы мои методы хранилища выглядели следующим образом:

    public void Add(User user)
    {
        session.Save(user);
    }

    public void Remove(User user)
    {
        session.Delete(user);
    }

    public User GetById(int userId)
    {
        return session.QueryOver<User>()
            .Where(c => c.UserID == userId)
            .SingleOrDefault();
    }

Со всем, что решается неявно.

Ответы [ 3 ]

4 голосов
/ 06 мая 2011

Я делаю что-то вроде:

В моем Global.asax.cs:

public static ISessionFactory SessionFactory { get; set; }

и затем определяю в Application_Start:

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    RegisterRoutes(RouteTable.Routes);

    var nhConfig = new Configuration().Configure();
    SessionFactory = nhConfig.BuildSessionFactory();
}

Этот класс затем создается:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class NHSession : ActionFilterAttribute
{
    public NHSession()
    {
        Order = 100;
    }

    protected ISessionFactory sessionFactory
    {
        get
        {
                return MvcApplication.SessionFactory;
        }
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var session = sessionFactory.OpenSession();
        CurrentSessionContext.Bind(session);
        session.BeginTransaction();
    }

    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        var session = CurrentSessionContext.Unbind(sessionFactory);
        if (session != null)
        {
            if (session.Transaction.IsActive)
            {
                try
                {
                    session.Transaction.Commit();
                }
                catch
                {
                    session.Transaction.Rollback();
                }
            }
            session.Close();
        }
    }
}

Тогда мой общий репозиторий выглядит следующим образом:

public class Repository<T> : IRepository<T>
{
    private readonly ISessionFactory SessionFactory;
    public Repository(ISessionFactory sessionFactory)
    {
        SessionFactory = sessionFactory;
    }
    public ISession Session
    {
        get
        {
            return SessionFactory.GetCurrentSession();
        }
    }
    public T Get(long id)
    {
        return Session.Get<T>(id);
    }
}

Моя конкретная реализация репозитория:

public class CmsContentRepository : Repository<CmsContent>, ICmsContentRepository
{
    public CmsContentRepository(ISessionFactory sessionFactory) : base(sessionFactory) { }
}

И еще одна вещь, которую я затем украшаю своими контроллерами следующим образом:

[NHSession]
public ViewResult Revisions(int id)
{
    var model = Service.CmsContentRepository.Get(id);
    return View("Revisions", model);
}

Это дает мне возможность использовать единицу работы в запросе.В основном, запрос приходит и запускает сеанс, SessionFactory передается в конструктор хранилища (ий).Я использую DI здесь, но это не обязательно.Если обнаружена ошибка, то сеанс откатывается, если нет, он фиксируется в конце запроса.Я бы порекомендовал NHProf, так как он поможет вам разобраться с управлением сеансами (то есть, если он настроен неправильно).

0 голосов
/ 06 мая 2011

Я использовал StructureMap, чтобы автоматически запускать сеанс при первом вызове ISession, а затем кэшировать сеанс с помощью HttpRequest. Это позволяет мне использовать ленивую загрузку и транзакции на протяжении всего запроса с минимальными затратами кода.

Вот код для моего загрузчика, который все настраивает для меня, используя Fluent NHibernate и StructureMap.

public class Bootstrapper
{
    public static ISessionFactory DBSessionFactory { get; set; }
    public static ISession DBSession { get; set; }

    public static void InitializeObjectFactory()
    {
        ObjectFactory.Initialize(x =>
                                     {
                                         x.PullConfigurationFromAppConfig = true;
                                         x.Scan(y =>
                                                    {
                                                        y.Assembly(Assembly.GetAssembly(typeof(AccountController)));
                                                        y.Assembly(Assembly.GetAssembly(typeof(IMyProject)));
                                                        y.WithDefaultConventions();
                                                    }
                                             );

                                         // these are for NHibernate
                                         x.ForRequestedType<ISessionFactory>()
                                             .CacheBy(InstanceScope.Singleton)
                                             .TheDefault.Is.ConstructedBy(GetDBSessionFactory);

                                         // open session at beginning of every http request 
                                         // (the session is disposed at end of http request in global.asax's Application_EndRequest)
                                         x.ForRequestedType<ISession>()
                                             .CacheBy(InstanceScope.HttpContext)
                                             .TheDefault.Is.ConstructedBy(GetDBSession);
                                     });
    }

    public static ISessionFactory CreateSessionFactory()
    {
        return GetFluentConfiguration()
            .BuildSessionFactory();
    }

    public static ISessionFactory GetDBSessionFactory()
    {
        if (DBSessionFactory == null)
        {
            DBSessionFactory = CreateSessionFactory();
        }
        return DBSessionFactory;
    }

    public static ISession GetDBSession()
    {
        if (DBSession == null)
        {
            DBSession = CreateSession();
        }
        return DBSession;
    }

    public static ISession CreateSession()
    {
        return GetDBSessionFactory()
            .OpenSession();
    }

    public static FluentConfiguration GetFluentConfiguration()
    {
        string commandTimeout = ConfigurationManager.AppSettings["MyDBCommandTimeout"];
        return Fluently.Configure()
            .Database(// use your db configuration )
            .Mappings(m =>
                          {
                              m.HbmMappings
                                  .AddFromAssemblyOf<MyEO>();
                              m.FluentMappings
                                  .AddFromAssemblyOf<MyEO>()
                                  .AddFromAssemblyOf<MyEOMap>();
                          })
            .ExposeConfiguration(
                cfg =>
                    {
                        // command_timeout sets the timeout for the queries
                        cfg.SetProperty("command_timeout", commandTimeout);
                    }
            );
    }
}

Вызов Bootstrapper.InitializeObjectFactory (); в методе Application_Start () в вашем global.asax, например:

protected void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);
    Bootstrapper.InitializeObjectFactory();
    ...
}

Закройте сеанс в вашем Application_EndRequest ():

protected void Application_EndRequest()
{
    // ensure that we aren’t leaking ISessions on every web request
    if (Bootstrapper.DBSession != null)
    {
        if (Bootstrapper.DBSession.IsOpen)
        {
             Bootstrapper.DBSession.Close();
        }
        Bootstrapper.DBSession.Dispose();
        Bootstrapper.DBSession = null;
    }

    HttpContextBuildPolicy.DisposeAndClearAll();
}

Теперь вы просто звоните

ObjectFactory.GetInstance<ISession>() 

из любого места (я обертываю его во вспомогательный класс для простоты кода), и StructureMap предоставит вам ваш кешированный сеанс.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...