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

我正在使用NHibernate + Fluent来处理我的数据库,我在查询引用其他数据的数据时遇到了问题。 我的简单问题是:我是否需要在映射中定义一些“BelongsTo”等,或者只在一侧定义引用就足够了(参见下面的映射示例)? 如果是这样 – 怎么样? 如果没有请继续阅读..看看这个简化的例子 – 从两个模型类开始:

public class Foo { private IList _bars = new List(); public int Id { get; set; } public string Name { get; set; } public IList Bars { get { return _bars; } set { _bars = value; } } } public class Bar { public int Id { get; set; } public string Name { get; set; } } 

我已经为这些类创建了映射。 这真的是我想知道我是否做对了。 我是否需要从Bar(“BelongsTo”等)定义回Foo的绑定,还是一种方法? 或者我是否需要在模型类中定义从Foo到Bar的关系等等? 以下是映射:

 public class FooMapping : ClassMap { 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 { public BarMapping() { Not.LazyLoad(); Id(c => c.Id).GeneratedBy.HiLo("1"); Map(c => c.Name).Not.Nullable().Length(100); } } 

我有一个查询Foo的函数,如下所示:

 public IList 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(); tx.Commit(); return result; } } } 

现在,这就是它失败的地方。 从这个函数返回最初看起来很好,结果找到了所有。 但是有一个问题 – Bar的列表在调试器中显示以下exception:

base {NHibernate.HibernateException} = {“正在初始化[MyNamespace.Foo#14] – 无法初始化角色集合:MyNamespace.Foo.Bars,没有关闭会话或会话”}

什么地方出了错? 我没有使用延迟加载,所以如何在延迟加载时出现问题? Bar不应该与Foo一起装载吗? 对我来说有趣的是,在生成查询中它不会要求Bar:

选择foo0_.Id为Id4_,foo0_.Name为Name4_,来自“Foo”foo0_,其中foo0_.Name = @ p0; @ p0 =’one’

对我来说更奇怪的是,如果我正在调试代码 – 逐步执行每一行 – 那么我就不会得到错误。 我的理论是,它在某个时间段内有时间检查Bar’s,因为事情变得越来越慢,但我不知道……我是否需要告诉它来取得Bar-too-explicit? 我现在尝试了各种解决方案,但感觉我在这里缺少一些基本的东西。

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

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

会议对NH来说至关重要。 传递信息(结果集)时,会话仍必须打开。 NH的正常编程模式是在请求开始时打开会话并在结束时关闭它(使用asp.net)或保持打开更长时间。

要修复代码,可以将打开/关闭会话移动到单例或可以处理它的包装器。 或者将打开/关闭会话移动到调用代码(但在一段时间内会变得混乱)。 为了解决这个问题,存在几种模式。 您可以查看涵盖所有内容的NHibernate Best Practices文章 。

编辑:采取另一种极端: S#arp架构 ( 下载 )为您处理这些最佳实践和许多其他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 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(Id); } // get all of your POCO type T public IList GetAll(int[] Ids) { return Session.CreateCriteria(). Add(Expression.In("Id", Ids)). List(); } // 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 ListUncached() { return Session.CreateCriteria() .SetCacheMode(CacheMode.Ignore) .SetCacheable(false) .List(); } // etc, like: public T Renew(T entity); public T GetByName(T entity, string name); public T GetByCriteria(T entity, ICriteria criteria); 

然后,在您的调用代码中,它看起来像这样:

 Dao daoFoo = new Dao(); Foo newFoo = new Foo(); newFoo.Name = "Johnson"; daoFoo.Save(newFoo); // if no session, it creates it here (lazy init) // or: Dao barDao = new Dao(); List allBars = barDao.GetAll(); 

很简单,不是吗? 这个想法的进步是为每个POCO创建特定的Dao,它inheritance自上面的一般Dao类并使用访问器类来获取它们。 这使得更容易添加特定于每个POCO的任务,这基本上就是NH最佳实践的意义(简而言之,因为我遗漏了接口,inheritance关系和静态与动态表)。