Проблема с веб-службой nHibernate SessionFactory

У меня есть веб-служба C # .Net. Я вызываю dll (C # .Net), которая использует nHibernate для подключения к моей базе данных. Когда я вызываю dll, она выполняет запрос к базе данных и загружает родительский объект «Задача». Однако, когда dll пытается получить доступ к дочерним объектам «Task.SubTasks», она выдает следующую ошибку:

NHibernate.HibernateException failed to lazily initialize a collection of role:  SubTasks no session or session was closed

Я новичок в nHibernate, поэтому не уверен, какой фрагмент кода мне не хватает.

Нужно ли мне запускать сеанс Factory в моем веб-сервисе перед вызовом dll? Если да, то как мне это сделать?

РЕДАКТИРОВАТЬ: добавлен код веб-службы и код метода CreateContainer (). Этот код вызывается непосредственно перед вызовом dll

    [WebMethod]
    public byte[] GetTaskSubtask (string subtaskId)
    {
        var container = CreateContainer(windsorPath);

        IoC.Initialize(container);
        //DLL CALL
        byte[] theDoc = CommonExport.GetSubtaskDocument(subtaskId);

        return theDoc;

    }

/// <summary>
/// Register the IoC container.
/// </summary>
/// <param name="aWindsorConfig">The path to the windsor configuration 
/// file.</param>
/// <returns>An initialized container.</returns>
protected override IWindsorContainer CreateContainer(
   string aWindsorConfig)
{
    //This method is a workaround.  This method should not be overridden.  
    //This method is overridden because the CreateContainer(string) method 
    //in UnitOfWorkApplication instantiates a RhinoContainer instance that 
    //has a dependency on Binsor.  At the time of writing this the Mammoth 
    //application did not have the libraries needed to resolve the Binsor 
    //dependency.

    IWindsorContainer container = new RhinoContainer();

    container.Register(
       Component.For<IUnitOfWorkFactory>().ImplementedBy
          <NHibernateUnitOfWorkFactory>());
    return container;
}

РЕДАКТИРОВАТЬ: добавление кода DLL и кода репозитория ...

Код DLL

public static byte[] GetSubtaskDocument(string subtaskId)
{
    BOESubtask task = taskRepo.FindBOESubtaskById(Guid.Parse(subtaskId));

    foreach(subtask st in task.Subtasks) <--this is the line that throws the error
    {
    //do some work
    }


}

Репозиторий для задачи

/// <summary>
/// Queries the database for the Subtasks whose ID matches the 
/// passed in ID.
/// </summary>
/// <param name="aTaskId">The ID to find matching Subtasks 
/// for.</param>
/// <returns>The Subtasks whose ID matches the passed in 
/// ID (or null).</returns>
public Task FindTaskById(Guid aTaskId)
{ 
    var task = new Task();
    using (UnitOfWork.Start())
    {
        task =  FindOne(DetachedCriteria.For<Task>()
                    .Add(Restrictions.Eq("Id", aTaskId)));
        UnitOfWork.Current.Flush();
    }
    return task;
}

Репозиторий для подзадач

/// <summary>
/// Queries the database for the Subtasks whose ID matches the 
/// passed in ID.
/// </summary>
/// <param name="aBOESubtaskId">The ID to find matching Subtasks 
/// for.</param>
/// <returns>The Subtasks whose ID matches the passed in 
/// ID (or null).</returns>
public Subtask FindBOESubtaskById(Guid aSubtaskId)
{ 
    var subtask = new Subtask();
    using (UnitOfWork.Start())
    {
        subtask =  FindOne(DetachedCriteria.For<Subtask>()
                    .Add(Restrictions.Eq("Id", aSubtaskId)));
        UnitOfWork.Current.Flush();
    }
    return subtask;
}

person MikeTWebb    schedule 27.04.2012    source источник


Ответы (2)


Вы, по-видимому, сопоставили коллекцию в одном из ваших классов данных NHibernate с включенной ленивой загрузкой (или лучше: не отключенной, поскольку это поведение по умолчанию). NHibernate загружает объект и создает прокси для сопоставленных коллекций. Как только к ним обращаются, NHibernate пытается загрузить элементы для этой коллекции. Но если вы закроете сеанс NHibernate до того, как это произойдет, возникнет полученная вами ошибка. Вероятно, вы предоставляете свой объект данных через веб-службу клиенту веб-службы. Во время процесса сериализации XmlSerializer пытается сериализовать коллекцию, которая предлагает NHibernate заполнить ее. При закрытии сеанса возникает ошибка.

Два способа предотвратить это:

  • закрыть сеанс после отправки ответа

or

  • отключите отложенную загрузку ваших коллекций, чтобы они загружались мгновенно

Дополнение после вышеуказанных правок:

в вашем репозитории вы запускаете UnitsOfWork с помощью оператора using. Они удаляются, как только код будет завершен. Я не знаю реализации UnitOfWork, но предполагаю, что она контролирует время жизни сеанса NHibernate. Удалив UnitOfWork, вы, вероятно, закроете сеанс NHibernate. Поскольку ваше сопоставление инициализирует коллекции с отложенной загрузкой, эти коллекции еще не заполнены, и возникает ошибка. NHibernate нужен точный экземпляр сеанса, в котором загружена сущность, для заполнения лениво инициализированных коллекций.

Вы столкнетесь с подобными проблемами, если используете ленивую загрузку и имеете репозиторий, закрывающий сеанс до завершения ответа. Один из вариантов - инициализировать UnitOfWork в начале запроса и закрыть его после завершения ответа (например, в Application_BeginRequest, Application_EndRequest в Global.asax.cs). Это, конечно, будет означать тесную интеграцию вашего репозитория с веб-сервисом.

В любом случае создание сеанса для одного запроса в сочетании с отложенной загрузкой - плохая идея и, скорее всего, создаст аналогичные проблемы в будущем. Если вы не можете изменить реализацию репозитория, возможно, вам придется отключить отложенную загрузку.

person Dirk Trilsbeek    schedule 27.04.2012
comment
Спасибо за ответ. Но я думаю, что моя проблема на более высоком уровне. У меня нет кода сеанса nHibernate. Я открываю контейнер с помощью Виндзора, но не сеанса. Код, вызывающий ошибку, находится в самой dll. Веб-служба инициализирует контейнер, а затем вызывает dll. DLL захватывает объекты из базы данных и возвращает их как byte [] в веб-службу, но она прерывается до того, как попадает туда. - person MikeTWebb; 27.04.2012
comment
но в какой-то момент кто-то где-то открывает и закрывает сеанс NHibernate. И поскольку в сообщении об ошибке указано, что NHibernate жалуется на закрытый сеанс, он должен быть кем-то открыт и закрыт. Какие части Виндзора вы используете? ActiveRecord, средство WCF, интеграция с NHibernate? - person Dirk Trilsbeek; 27.04.2012
comment
согласен ... это где-то происходит автоматически. И это интеграция с nHibernate. В hibernate.cfg есть раздел ‹session-factory›. Я добавил код CreateContainer выше - person MikeTWebb; 27.04.2012
comment
Было бы интересно посмотреть, что происходит в CommonExport.GetSubtaskDocument (). Я не знаком с NHibernateUnitOfWorkFactory (Rhino.Tools?), Возможно, у него есть настраиваемое управление сеансом. Один совет относительно Castle Windsor: вы воссоздаете IWindsorContainer для каждого запроса. Вы можете создать его при Application_Start в Global.asax.cs, так как создание может стать довольно дорогостоящим. Вы также убедитесь, что Windsor действительно может отслеживать созданные им объекты. В настоящее время каждый запрос создает новый контейнер, каждый может отслеживать только те объекты, которые он создал сам. - person Dirk Trilsbeek; 27.04.2012
comment
Спасибо за предложение Application_Start! Также мы используем Rhino. Я уберу часть кода CommonExport.GetSubtaskDocument () и добавлю его в вопрос. - person MikeTWebb; 27.04.2012
comment
Я обновил свой ответ в соответствии с вашими дополнениями в первоначальном вопросе. - person Dirk Trilsbeek; 27.04.2012
comment
Спасибо, Гарланд. Вы были на правильном пути. Я удалил Unit of Work из репозитория, запустил UnitOfOwrk в веб-службе и заключил вызов ddl в оператор using. Я отправлю ответ. Еще раз спасибо! - person MikeTWebb; 28.04.2012

Используя отзывы Гарланда, я решил проблему. Я удалил код UnitOfWork (s) из репозитория в DLL и заключил вызов веб-службы в DLL в UnitOfWork См. Модификации кода ниже:

Веб-сервис

[WebMethod]
public byte[] GetSubtaskDocument (string subtaskId)
{
    var container = CreateContainer(windsorString);

    IoC.Initialize(container);

    byte[] theDoc;
    using (UnitOfWork.Start())
    {
        //DLL call
        theDoc = CommonExport.GetSubtaskDocument(subtaskId);
        UnitOfWork.Current.Flush();
    }
    return theDoc;
}

Вызов репозитория в DLL

public Subtask FindSubtaskById(Guid aSubtaskId)
{ 
    return FindOne(DetachedCriteria.For<Subtask>()
                .Add(Restrictions.Eq("Id", aSubtaskId)));
}
person MikeTWebb    schedule 27.04.2012