引用类时出现 NHibernate 映射问题(延迟加载问题?)

发布于 2024-08-16 13:10:17 字数 2173 浏览 2 评论 0原文

我正在使用 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; }
}

我已经为这些类创建了映射。这确实是我想知道我是否做对的地方。我是否需要定义从 Bar 到 Foo 的绑定(“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;
        }
    }        
}

现在,这就是它失败的地方。该函数的返回最初看起来一切正常,结果已找到。但有一个问题 - Bar 的列表在调试器中显示以下异常:

base {NHibernate.HibernateException} = {“初始化[MyNamespace.Foo#14] - 未能延迟初始化角色集合:MyNamespace.Foo.Bars,没有会话或会话被关闭”}

出了什么问题?我没有使用延迟加载,那么延迟加载怎么会出问题呢? Bar 不应该与 Foo 一起加载吗?我感兴趣的是,在生成查询中它不要求 Bar:

从“Foo”foo0_中选择foo0_.Id作为Id4_,foo0_.Name作为Name4_,其中foo0_.Name=@p0;@p0 ='one'

对我来说更奇怪的是,如果我正在调试代码 - 单步执行每个线 - 那么我就没有收到错误。我的理论是,它在同一个会话期间以某种方式有时间检查 Bar 的,因为事情进展得更慢,但我不知道..我是否需要告诉它也显式地获取 Bar 的?我现在已经尝试了各种解决方案,但感觉我在这里缺少一些基本的东西。

I'm using NHibernate + Fluent to handle my database, and I've got a problem querying for data which references other data. My simple question is: Do I need to define some "BelongsTo" etc in the mappings, or is it sufficient to define references on one side (see mapping sample below)? If so - how? If not please keep reading.. Have a look at this simplified example - starting with two model classes:

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; }
}

I have created mappings for these classes. This is really where I'm wondering whether I got it right. Do I need to define a binding back to Foo from Bar ("BelongsTo" etc), or is one way sufficient? Or do I need to define the relation from Foo to Bar in the model class too, etc? Here are the mappings:

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);
    }
}

And I have a function for querying for Foo's, like follows:

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;
        }
    }        
}

Now, this is where it fails. The return from this function initially looks all fine, with the result found and all. But there is a problem - the list of Bar's has the following exception shown in debugger:

base {NHibernate.HibernateException} = {"Initializing[MyNamespace.Foo#14]-failed to lazily initialize a collection of role: MyNamespace.Foo.Bars, no session or session was closed"}

What went wrong? I'm not using lazy loading, so how could there be something wrong in the lazy loading? Shouldn't the Bar's be loaded together with the Foo's? What's interesting to me is that in the generate query it doesn't ask for Bar's:

select foo0_.Id as Id4_, foo0_.Name as Name4_ from "Foo" foo0_ where foo0_.Name=@p0;@p0 = 'one'

What's even more odd to me is that if I'm debugging the code - stepping through each line - then I don't get the error. My theory is that it somehow gets time to check for Bar's during the same session cause things are moving slower, but I dunno.. Do I need to tell it to fetch the Bar's too - explicitly? I've tried various solutions now, but it feels like I'm missing something basic here.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

Bonjour°[大白 2024-08-23 13:10:17

这是一个典型的问题。使用 NHibernate 或 Fluent-NHibernate,您使用的映射到数据的每个类都会用很多东西进行装饰(这就是为什么它们需要是虚拟的)。这一切都发生在运行时。

您的代码在 using 语句中清楚地显示了会话的打开和关闭。在调试时,调试器非常好(或不)在 using 语句结束后保持会话打开(在停止单步执行后调用清理代码)。当处于运行模式(不是单步执行)时,您的会话将正确关闭。

这次会议在新罕布什尔州至关重要。当您传递信息(结果集)时,会话必须仍处于打开状态。 NH 的正常编程模式是在请求开始时打开一个会话,并在请求结束时关闭它(使用 asp.net)或使其保持打开较长时间。

要修复您的代码,请将打开/关闭会话移动到单例或可以处理该问题的包装器。或者将打开/关闭会话移至调用代码(但一段时间后这会变得混乱)。为了一般地解决这个问题,存在几种模式。您可以查找这篇 NHibernate 最佳实践文章,其中涵盖了所有内容。

编辑:采取另一个极端: S#arp 架构下载< /a>) 会为您处理这些最佳实践和许多其他 NH 问题,完全掩盖最终用户/程序员的 NH 复杂性。它有一点陡峭的学习曲线(包括 MVC 等),但一旦你掌握了它......你就不能再没有它了。但不确定它是否很容易与 FluentNH 混合。

使用 FluentNH 和一个简单的 Dao 包装器

请参阅评论了解我为什么添加这个额外的“章节”。下面是一个非常简单但可重用且可扩展的 DAL 类 Dao 包装器示例。我假设您已经设置了 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();

非常简单,不是吗?这个想法的进步是为每个 POCO 创建特定的 Dao,这些 Dao 继承自上述通用 Dao 类,并使用访问器类来获取它们。这使得添加特定于每个 POCO 的任务变得更加容易,这基本上就是 NH 最佳实践的内容(简而言之,因为我省略了接口、继承关系以及静态与动态表)。

This is a typical problem. Using NHibernate or Fluent-NHibernate, every class you use that maps to your data is decorated (which is why they need to be virtual) with a lot of stuff. This happens all at runtime.

Your code clearly shows an opening and closing of a session in a using statement. When in debugging, the debugger is so nice (or not) to keep the session open after the end of the using statement (the clean-up code is called after you stop stepping through). When in running mode (not stepping through), your session is correctly closed.

The session is vital in NH. When you are passing on information (the result set) the session must still be open. A normal programming pattern with NH is to open a session at the beginning of the request and close it at the end (with asp.net) or keep it open for a longer period.

To fix your code, either move the open/close session to a singleton or to a wrapper which can take care of that. Or move the open/close session to the calling code (but in a while this gets messy). To fix this generally, several patterns exist. You can look up this NHibernate Best Practices article which covers it all.

EDIT: Taken to another extreme: the S#arp architecture (download) takes care of these best practices and many other NH issues for you, totally obscuring the NH intricacies for the end-user/programmer. It has a bit of a steep learning curve (includes MVC etc) but once you get the hang of it... you cannot do without anymore. Not sure if it is easily mixed with FluentNH though.

Using FluentNH and a simple Dao wrapper

See comments for why I added this extra "chapter". Here's an example of a very simple, but reusable and expandable, Dao wrapper for your DAL classes. I assume you have setup your FluentNH configuration and your typical POCO's and relations.

The following wrapper is what I use for simple projects. It uses some of the patterns described above, but obviously not all to keep it simple. This method is also usable with other ORM's in case you'd wonder. The idea is to create singleton for the session, but still keep the ability to close the session (to save resources) and not worry about having to reopen. I left the code out for closing the session, but that'll be only a couple of lines. The idea is as follows:

// 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);

Then, in your calling code, it looks something like this:

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();

Pretty simple, isn't it? The advancement to this idea is to create specific Dao's for each POCO which inherit from the above general Dao class and use an accessor class to get them. That makes it easier to add tasks that are specific for each POCO and that's basically what NH Best Practices was about (in a nutshell, because I left out interfaces, inheritance relations and static vs dynamic tables).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文