StructureMap връща изхвърлен обект на сесия nHibenrate от локален обхват на нишка

[ИЛИ] Как да дефинираме жизнения цикъл на StructureMap за UoW, който да се използва от http заявки и кварцови задания

Имам това уеб приложение, което използва SM за IoC. Използвам HybridHttpOrThreadLocalScoped обхват, за да съхранявам моите nHibernate ISession обекти. Това работи добре в режим на сесия на заявка за моите уеб заявки.

Но също така имам quartz.net, който планира няколко работни места. Заданието използва същата единица работа, за да получи обекта ISession. В този сценарий, когато планировчикът стартира заданието, всичко работи добре отначало и заданието работи добре няколко пъти, ДОКАТО идентификаторът на нишката на заданието не се повтори.

Представете си, че когато заданието е планирано, то започва да се изпълнява в нишки с идентификатори 11, 12, 13 и след това отново с идентификатор на нишка 11. В този момент structuremap връща обект на сесия, който вече е изхвърлен и получавам "System.ObjectDisposedException: Сесията е затворена!" грешка.

Така че от това, което виждам, сесията се съхранява в локално хранилище на нишка и след като изтрия сесията в края на моята работна единица, обектът на сесията все още се съхранява в локалното хранилище на нишката. Изглежда, че след прекратяване на нишката нейното локално хранилище не се изчиства и по някакъв начин, когато се създаде нова нишка със същия идентификатор, structuremap търси сесията в старото локално хранилище на нишката (което се предполага, че е изчистен за новата нишка според мен) и връща обекта на сесията, който вече е изтрит.

Въпроси:

  1. Има ли начин да изчистите локалното хранилище на нишката (при прекратяване)?
  2. Има ли еквивалент на „ReleaseAndDisposeAllHttpScopedObjects“ за обекти с обхват на нишка?
  3. Има ли начин да анулира (или изхвърли) изхвърления обект, така че дори SM да го търси, тогава няма да намери такъв и трябва да създаде нов екземпляр?

Надявам се, че изясних въпроса си. Това отне няколко часа от времето ми и все още не съм намерил начин да го заобиколя. Оценявам всеки намек :>

Актуализация: Добавих мое собствено решение, за да накарам UoW, обслужван от StructureMap, да работи както с http заявки, така и с кварцови задачи. Уведомете ме, ако имате по-добро/по-лесно/просто решение.


person kaptan    schedule 03.07.2010    source източник
comment
Управлявате ли вашите кварцови IJobs със StructureMap?   -  person Mauricio Scheffer    schedule 04.07.2010
comment
@Mauricio: Използвам StructureMap в приложението си. Все пак не съм сигурен какво имате предвид под управление на кварцови задачи от StructeMap: ›   -  person kaptan    schedule 13.07.2011
comment
вашите екземпляри Quartz IJob управляват ли се от StructureMap? С други думи: регистрирате ли работните си места в контейнера?   -  person Mauricio Scheffer    schedule 13.07.2011
comment
@Mauricio: Не, не регистрирам работните си места в контейнера StructureMap. Използвам StructureMap, за да получа UoW за всяка уволнена работа.   -  person kaptan    schedule 13.07.2011
comment
Бих оставил контейнера да управлява задачите... това вероятно ще улесни нещата.   -  person Mauricio Scheffer    schedule 13.07.2011


Отговори (2)


Преразглеждах какво направих, за да накарам StructureMap да работи с UoW за Http и UoW за кварцова работа и реших да споделя решението си тук.

Така че идеята беше, че исках да използвам хибридния обхват на StructureMap, за да получа екземпляр на UoW, когато има http контекст, и също така да получа различен екземпляр на UoW за нишка, когато няма http контекст (например, когато се задейства кварцова работа). Като този:

For<IUnitOfWork>().HybridHttpOrThreadLocalScoped().Use<UnitOfWork>();

UoW за http работи добре. Проблемът беше UoW на нишка.

Ето какво става. Когато задание на Quratz се задейства, то изтегля нишка от пула с нишки и започва да изпълнява задачата, използвайки тази нишка. Когато работата започне, изисквам UoW. StructureMap търси в локалното хранилище за тази нишка, за да върне UoW, но тъй като не може да намери такова, създава такъв и го записва в локалното хранилище на нишката. Получавам UoW, след това изпълнявам Begin, Commit, Dispose и всичко е наред.

Проблемът възниква, когато нишка е изтеглена от пул от нишки, който е бил използван преди за стартиране на задание (и е използвал UoW). Тук, когато поискате UoW, StructureMap търси в кеша (локално хранилище на нишка) и намира UoW и ви го връща. Но проблемът е, че UoW е изхвърлен!

Така че не можем наистина да използваме UoW на нишка за кварцови задания, защото самите нишки не са изхвърлени и те държат старите кеширани изхвърлени UoW. По принцип жизненият цикъл на нишка не съвпада с жизнения цикъл на кварцова работа. Ето защо създадох свой собствен жизнен цикъл за кварцова работа.

Първо създадох свой собствен http-quartz хибриден клас на жизнения цикъл:

public class HybridHttpQuartzLifecycle : HttpLifecycleBase<HttpContextLifecycle, QuartzLifecycle>
{
    public override string Scope { get { return "HybridHttpQuartzLifecycle"; } }
}

След това създадох моя клас QuartzLifecyle:

public class QuartzLifecycle : ILifecycle
{

    public void EjectAll()
    {
        FindCache().DisposeAndClear();
    }

    public IObjectCache FindCache()
    {
        return QuartzContext.Cache;
    }

    public string Scope { get { return "QuartzLifecycle"; } }
}

След това трябва да създам някакъв контекстен клас като HttpContext за Quartz, който да съдържа свързаната с контекста информация. Така че създадох клас QuartzContext. Когато се стартира кварцово задание, JobExecutionContext за това задание трябва да се регистрира в QuartzContext. Тогава действителният кеш (MainObjectCache) за екземпляри на StructureMap ще бъде създаден под този конкретен JobExecutionContext. Така че по този начин, след като изпълнението на заданието приключи, кешът също ще изчезне и няма да имаме проблем с изхвърлянето на UoW в кеша.

Освен това, тъй като _jobExecutionContext е ThreadStatic, когато поискаме кеша от QuartzContext, той ще върне кеша от JobExecutionContext, който е запазен за същата нишка. Така че, когато няколко задания се изпълняват едновременно, техните JobExecutionContexts се записват отделно и ние ще имаме отделни кешове за всяко изпълнявано задание.

public class QuartzContext
{

    private static readonly string _cacheKey = "STRUCTUREMAP-INSTANCES";

    [ThreadStatic]
    private static JobExecutionContext _jobExecutionContext;

    protected static void Register(JobExecutionContext jobExecutionContext)
    {
        _jobExecutionContext = jobExecutionContext;
        _jobExecutionContext.Put(_cacheKey, new MainObjectCache());
    }

    public static IObjectCache Cache 
    { 
        get 
        {
            return (IObjectCache)_jobExecutionContext.Get(_cacheKey);
        } 
    }
}  

Имам абстрактен клас, наречен BaseJobSingleSession, от който произлизат други задачи. Този клас разширява класа QuartzContext. Можете да видите, че регистрирам JobExecutionContext, когато заданието бъде уволнено.

abstract class BaseJobSingleSession : QuartzContext, IStatefulJob
{
    public override void Execute(JobExecutionContext context)
    {
        Register(context);
        IUnitOfWork unitOfWork = ObjectFactory.GetInstance<IUnitOfWork>();

        try
        {
            unitOfWork.Begin();

            // do stuff ....

            unitOfWork.Commit();
        }
        catch (Exception exception)
        {
            unitOfWork.RollBack();

        }
        finally
        {
            unitOfWork.Dispose();
        }
    }
}

Най-накрая дефинирах жизнения цикъл за UoW:

For<IUnitOfWork>().LifecycleIs(new HybridHttpQuartzLifecycle()).Use<UnitOfWork>();

(За жизнения цикъл и контекстните класове погледнах в изходния код на StructureMap, за да разбера идеята.)

Моля, споделете своите идеи, коментари и предложения: >

person kaptan    schedule 12.07.2011

Защо не създадете нова сесия за кварцовите задачи? Една работна единица обикновено е транзакционна операция в db. Не мога да си представя, че кварцовите задачи са транзакционно свързани с уеб заявката/отговорите. Създаването на нови сесии не е скъпо. Възможно ли е това?

person rcravens    schedule 06.05.2011
comment
да Искам да създам нова сесия за всяко кварцово задание. Но тъй като използвам идеята за UoW, искам тя да бъде последователна в приложението ми. Така че не искам да създавам сесии директно за кварцови задачи. Искам да създам екземпляр на UoW. Но в същото време искам да използвам StructureMap, за да получа екземпляр на UoW. Ето защо в крайна сметка дефинирах хибриден жизнен цикъл в StructeMap за моя UoW, така че да връща подходящия UoW за всяка http заявка или всяко изпълнение на кварцова задача. - person kaptan; 13.07.2011
comment
@kaptan: Съгласен съм с @rcravens. Това, което направих, е да получа екземпляр на ISessionFactory (ObjectFactory.GetInstance‹ISessionFactory›()) и да отворя нова сесия, когато работата ми започне. - person LeftyX; 13.07.2011