StructureMap возвращает удаленный объект сеанса nHibenrate из локальной области потока - PullRequest
6 голосов
/ 03 июля 2010

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

У меня есть это веб-приложение, которое использует SM для IoC.Я использую область HybridHttpOrThreadLocalScoped для хранения своих объектов nHibernate ISession.Это работает нормально в режиме сеанса для каждого запроса для моих веб-запросов.

Но у меня также есть quartz.net, который планирует пару заданий.Для получения объекта ISession задание использует ту же единицу работы.В этом сценарии, когда планировщик запускает задание, сначала все работает нормально, и задание выполняется нормально пару раз, пока не будет повторен идентификатор потока задания.

Представьте себе, что когда задание запланировано, оно начинает выполняться в потоках с идентификаторами 11, 12, 13, а затем снова с идентификатором потока 11.На этом этапе структура карты возвращает объект сеанса, который уже удален, и я получаю «System.ObjectDisposedException: сеанс закрыт!»ошибка.

Итак, из того, что я вижу, сеанс хранится в локальном хранилище потока, и после того, как я располагаю сеанс в конце своей единицы работы, объект сеанса все еще сохраняется в локальном хранилище потока. Кажется, что после завершения потока его локальное хранилище не очищается и каким-то образом, когда создается новый поток с тем же идентификатором, Structuremap ищет сеанс в локальном хранилище старого потока (который должен быть очищендля нового потока я верю) и возвращает объект сеанса, который уже удален.

Вопросы:

  1. Есть ли способ очистить локальное хранилище потока (при завершении)?
  2. Есть ли эквивалент "ReleaseAndDisposeAllHttpScopedObjects" для объектов с областью потока?
  3. Есть ли способ обнулить (или извлечь) удаленный объект, поэтому даже если SM ищет его, он ненайти любой и должен создать новый экземпляр?

Надеюсь, я прояснил свой вопрос.Это заняло пару часов моего времени, и все же я не нашел способ обойти это. Я ценю любую подсказку :>

Обновление: Я добавил свое собственное решение, чтобы UoW, обслуживаемый StructureMap, работал и с http-запросами, и с кварцевыми заданиями.Дайте мне знать, если у вас есть лучшее / более легкое / простое решение.

Ответы [ 2 ]

3 голосов
/ 13 июля 2011

Я пересматривал то, что сделал, чтобы StructureMap работал с UoW для Http и UoW для кварцевого задания, и я решил поделиться своим решением здесь.

Так что идея заключалась в том, что я хотел использовать область применения StructureMap Hybrid дляполучить экземпляр 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-кварцкласс гибридного жизненного цикла:

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, чтобы получить идею.)

Пожалуйста, поделитесь своими идеями, комментариями и предложениями:>

1 голос
/ 06 мая 2011

Почему бы не создать новый сеанс для кварцевых заданий?Единица работы обычно является транзакционной операцией на БД.Я не могу представить, что кварцевые задания транзакционно связаны с веб-запросами / ответамиСоздание новых сессий не дорого.Это возможно?

...