Проблема отображений NHibernate при обращении к классу (проблема отложенной загрузки?) - PullRequest
4 голосов
/ 06 января 2010

Я использую NHibernate + Fluent для обработки своей базы данных, и у меня проблема с запросом данных, которые ссылаются на другие данные. Мой простой вопрос: нужно ли мне определять некоторые «BelongsTo» и т. Д. В сопоставлениях или достаточно определить ссылки на одной стороне (см. Образец сопоставления ниже)? Если так - как? Если нет, пожалуйста, продолжайте читать. Посмотрите на этот упрощенный пример - начиная с двух классов моделей:

public class Foo
{
    private IList<Bar> _bars = new List<Bar>();

    public int Id { get; set; }
    public string Name { get; set; }
    public IList<Bar> Bars
    {
        get { return _bars; }
        set { _bars = value; }
    }
}

public class Bar
{
    public int Id { get; set; }
    public string Name { get; set; }
}

Я создал отображения для этих классов. Это действительно, где я задаюсь вопросом, правильно ли я понял. Нужно ли определять привязку к Foo from Bar («BelongsTo» и т. Д.) Или достаточно одного способа? Или мне нужно также определить отношение от Foo к Bar в классе модели и т. Д.? Вот сопоставления:

public class FooMapping : ClassMap<Foo>
{
    public FooMapping()
    {
        Not.LazyLoad();
        Id(c => c.Id).GeneratedBy.HiLo("1");
        Map(c => c.Name).Not.Nullable().Length(100);
        HasMany(x => x.Bars).Cascade.All();
    }
}

public class BarMapping : ClassMap<Bar>
{
    public BarMapping()
    {
        Not.LazyLoad();
        Id(c => c.Id).GeneratedBy.HiLo("1");
        Map(c => c.Name).Not.Nullable().Length(100);
    }
}

И у меня есть функция для запросов к Foo, например:

public IList<Foo> SearchForFoos(string name)
{
    using (var session = _sessionFactory.OpenSession())
    {
        using (var tx= session.BeginTransaction())
        {
            var result = session.CreateQuery("from Foo where Name=:name").SetString("name", name).List<Foo>();
            tx.Commit();
            return result;
        }
    }        
}

Теперь это то, где это терпит неудачу. Возвращение из этой функции изначально выглядит нормально, результат найден и все. Но есть проблема - в списке баров есть следующее исключение, показанное в отладчике:

base {NHibernate.HibernateException} = {"Initializing [MyNamespace.Foo # 14] - не удалось лениво инициализировать коллекцию ролей: MyNamespace.Foo.Bars, ни один сеанс или сеанс не был закрыт»}

Что пошло не так? Я не использую отложенную загрузку, так как может быть что-то не так в отложенной загрузке? Разве Бар не должен быть загружен вместе с Foo? Что меня интересует, так это то, что в запросе на генерацию он не запрашивает Бар:

выберите foo0_.Id как Id4_, foo0_.Name как Name4_ из "Foo" foo0_, где foo0_.Name=@p0; @ p0 = 'one'

Что еще более странно для меня, это то, что если я отлаживаю код - проходя через каждую строку - тогда я не получаю ошибку. Моя теория заключается в том, что у него почему-то есть время проверять наличие бара во время того же сеанса, потому что все движется медленнее, но я не знаю ... Нужно ли мне сказать, чтобы он тоже брал бара - явно? Я пробовал разные решения сейчас, но мне кажется, что я упускаю что-то базовое здесь.

1 Ответ

8 голосов
/ 06 января 2010

Это типичная проблема. Используя NHibernate или Fluent-NHibernate, каждый класс, который вы используете, который сопоставляет ваши данные, украшен (поэтому они должны быть виртуальными) с большим количеством материала. Это происходит все во время выполнения.

Ваш код четко показывает открытие и закрытие сеанса в операторе использования. Находясь в режиме отладки, отладчик очень хорош (или нет), чтобы держать сеанс открытым после окончания оператора using (код очистки вызывается после того, как вы перестанете проходить). Находясь в режиме выполнения (без перехода), ваш сеанс корректно закрыт.

Сессия жизненно важна в NH. Когда вы передаете информацию (результирующий набор), сессия должна оставаться открытой. Обычный шаблон программирования с NH - это открыть сеанс в начале запроса и закрыть его в конце (с asp.net) или оставить его открытым в течение более длительного периода.

Чтобы исправить ваш код, либо переместите сеанс открытия / закрытия в одиночный файл, либо в оболочку, которая может позаботиться об этом. Или переместите сеанс открытия / закрытия в вызывающий код (но через некоторое время это становится грязным). Чтобы исправить это в целом, существует несколько шаблонов. Вы можете найти эту статью о лучших практиках NHibernate , которая охватывает все это.

РЕДАКТИРОВАТЬ: Взятый в другую крайность: архитектура S # arp ( download ) заботится об этих лучших практиках и многих других проблемах NH для вас, полностью скрывая сложности NH конечный пользователь / программист. У него немного крутая кривая обучения (включая MVC и т. Д.), Но как только вы освоите его ... вы уже не сможете обойтись. Не уверен, что его легко смешать с FluentNH.

Использование FluentNH и простой оболочки Dao

Смотрите комментарии, почему я добавил эту дополнительную "главу". Вот пример очень простой, но многократно используемой и расширяемой оболочки Dao для ваших классов DAL. Я предполагаю, что вы настроили свою конфигурацию FluentNH и ваши типичные POCO и отношения.

Следующая обертка - это то, что я использую для простых проектов. Он использует некоторые из шаблонов, описанных выше, но, очевидно, не все для простоты. Этот метод также можно использовать с другими ORM, если вам интересно. Идея состоит в том, чтобы создать синглтон для сеанса, но при этом сохранить возможность закрытия сеанса (для экономии ресурсов) и не беспокоиться о необходимости повторного открытия. Я оставил код для закрытия сессии, но это будет всего пара строк. Идея заключается в следующем:

// the thread-safe singleton
public sealed class SessionManager
{
    ISession session;
    SessionManager()
    {
        ISessionFactory factory = Setup.CreateSessionFactory();
        session = factory.OpenSession();
    }

    internal ISession GetSession()
    {
        return session;
    }

    public static SessionManager Instance
    {
        get
        {
            return Nested.instance;
        }
    }

    class Nested
    {
        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static Nested()
        {
        }

        internal static readonly SessionManager instance = new SessionManager();
    }
}


// the generic Dao that works with your POCO's
public class Dao<T>
    where T : class
{
    ISession m_session = null;

    private ISession Session
    {
        get
        {
            // lazy init, only create when needed
            return m_session ?? (m_session = SessionManager.Instance.GetSession());
        }
    }

    public Dao() { }

    // retrieve by Id
    public T Get(int Id)
    {
        return Session.Get<T>(Id);
    }

    // get all of your POCO type T
    public IList<T> GetAll(int[] Ids)
    {
        return Session.CreateCriteria<T>().
            Add(Expression.In("Id", Ids)).
            List<T>();
    }

    // save your POCO changes
    public T Save(T entity)
    {
        using (var tran = Session.BeginTransaction())
        {
            Session.SaveOrUpdate(entity);
            tran.Commit();
            Session.Refresh(entity);
            return entity;
        }
    }

    public void Delete(T entity)
    {
        using (var tran = Session.BeginTransaction())
        {
            Session.Delete(entity);
            tran.Commit();
        }
    }

    // if you have caching enabled, but want to ignore it
    public IList<T> ListUncached()
    {
        return Session.CreateCriteria<T>()
            .SetCacheMode(CacheMode.Ignore)
            .SetCacheable(false)
            .List<T>();
    }

    // etc, like:
    public T Renew(T entity);
    public T GetByName(T entity, string name);
    public T GetByCriteria(T entity, ICriteria criteria);

Тогда в вашем коде вызова это выглядит примерно так:

Dao<Foo> daoFoo = new Dao<Foo>();
Foo newFoo = new Foo();
newFoo.Name = "Johnson";
daoFoo.Save(newFoo);         // if no session, it creates it here (lazy init)

// or:
Dao<Bar> barDao = new Dao<Bar>();
List<Bar> allBars = barDao.GetAll();

Довольно просто, не правда ли? Продвижение к этой идее заключается в создании определенных Dao для каждого POCO, которые наследуются от вышеуказанного общего класса Dao, и используют класс доступа для их получения. Это облегчает добавление задач, специфичных для каждого POCO, и это, в основном, и было целью NH Best Practices (в двух словах, потому что я исключил интерфейсы, отношения наследования и статические и динамические таблицы).

...