NHibernateEntityManager

NHibernateEntityManager

本文介绍了由于加载后关闭会话,导致多对一关系异常的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我第一次使用NHibernate(1.2.1版),所以我编写了一个使用它的简单测试应用程序(一个ASP.NET项目).在我的数据库中,我有两个表:人员和类别.每个人都有一个类别,似乎很容易.


| Persons      |      | Categories   |
|--------------|      |--------------|
| Id (PK)      |      | Id (PK)      |
| Firstname    |      | CategoryName |
| Lastname     |      | CreatedTime  |
| CategoryId   |      | UpdatedTime  |
| CreatedTime  |      | Deleted      |
| UpdatedTime  |
| Deleted      |

Id,CreatedTime,UpdatedTime和Deleted属性是我在所有表中使用的约定,因此,我尝试将这一事实引入附加的抽象层.我有一个包含三个重要类的项目DatabaseFramework:

  • Entity:定义这四个属性的抽象类.所有实体对象"(在本例中为人"和类别")都必须继承Entity.
  • IEntityManager:一个通用接口(类型参数为Entity),用于定义诸如Load,Insert,Update等方法.
  • NHibernateEntityManager:此接口的实现,使用NHibernate进行加载,保存等.

现在,Person和Category类很简单,它们只是定义了表格的属性(请记住,其中的四个在基本Entity类中).
由于Persons表通过CategoryId属性与Categories表相关,因此Person类具有一个Category属性,该属性保存相关的类别.但是,在我的网页中,例如,出于数据绑定的目的,我还将需要此类别的名称(CategoryName).因此,我创建了一个附加属性CategoryName,该属性返回当前Category属性的CategoryName属性;如果Category为null,则返回一个空字符串:


Namespace Database
    Public Class Person
        Inherits DatabaseFramework.Entity

    Public Overridable Property Firstname As String
    Public Overridable Property Lastname As String
    Public Overridable Property Category As Category

    Public Overridable ReadOnly Property CategoryName As String
        Get
            Return If(Me.Category Is Nothing, _
                      String.Empty, _
                      Me.Category.CategoryName)
        End Get
    End Property

End Class

结束命名空间

我正在使用此映射文件映射Person类. Yads在另一个线程中建议了多对一关系:

(我无法显示根节点,该论坛将其隐藏,我不知道如何转义类似html的标记...)

最后一个重要的细节是NHibernateEntityManager实现的Load方法. (这是C#,因为它在另一个项目中,对此感到抱歉).
我只需在GetSession方法中打开一个新的ISession(ISessionFactory.OpenSession),然后使用它来填充EntityCollection(Of TEntity),它只是一个继承System.Collections.ObjectModel.Collection(Of T)的集合.


        public virtual EntityCollection< TEntity > Load()
        {
            using (ISession session = this.GetSession())
            {
                var entities = session
                                .CreateCriteria(typeof (TEntity))
                                .Add(Expression.Eq("Deleted", false))
                                .List< TEntity >();
                return new EntityCollection< TEntity >(entities);
            }
        }


(再次,我无法使其正确格式化代码,它隐藏了通用类型参数,可能是因为它将成角度的符号读取为HTML标记..?如果您知道如何让我做到这一点,请告诉我! )

现在,此Load方法的想法是,我得到一个功能完整的Persons集合,其所有属性都设置为正确的值(包括Category属性,因此CategoryName属性应返回正确的名称). br>但是,似乎并非如此.当我尝试将此Load方法的结果数据绑定到ASP.NET中的GridView时,它告诉我:

Property accessor 'CategoryName' on object 'NHibernateWebTest.Database.Person' threw the following exception:'Could not initialize proxy - the owning Session was closed.'

在以下情况下发生在DataBind方法调用上的异常:

        public virtual void LoadGrid()
        {
            if (this.Grid == null) return;

        this.Grid.DataSource = this.Manager.Load();
        this.Grid.DataBind();
    }

当然,会话已关闭,我通过using块将其关闭.这不是正确的方法,我应该保持会话开放吗?还有多长时间?运行DataBind方法后可以关闭它吗?

在每种情况下,我都非常希望Load方法仅返回功能性的项目集合.在我看来,它现在仅在需要时才获得Category(例如,当GridView想要读取CategoryName时,它想要读取Category属性),但是那时会话已关闭.这种推理正确吗?

如何停止这种行为?难道不是吗?否则我该怎么办?

谢谢!

解决方案

在映射中设置延迟加载= false将解决该错误.最好是在加载查询中告诉NHibernate您想尽快获取类别的子级集合.

对于标准查询,应该可以使用.SetFetchMode("Categories",FetchMode.Eager)之类的东西.

这是一个链接,它使您对所谓的"n + 1"问题,延迟加载与它有何关系以及如何使用NHibernate.

HTH,
贝里

类似这样的东西:

           var entities = session
                            .CreateCriteria<TEntity>()
                            .SetFetchMode("Categories", FetchMode.Eager)
                            .Add(Expression.Eq("Deleted", false))
                            .List< TEntity >();

I am using NHibernate (version 1.2.1) for the first time so I wrote a simple test application (an ASP.NET project) that uses it. In my database I have two tables: Persons and Categories. Each person gets one category, seems easy enough.


| Persons      |      | Categories   |
|--------------|      |--------------|
| Id (PK)      |      | Id (PK)      |
| Firstname    |      | CategoryName |
| Lastname     |      | CreatedTime  |
| CategoryId   |      | UpdatedTime  |
| CreatedTime  |      | Deleted      |
| UpdatedTime  |
| Deleted      |

The Id, CreatedTime, UpdatedTime and Deleted attributes are a convention I use in all my tables, so I have tried to bring this fact into an additional abstraction layer. I have a project DatabaseFramework which has three important classes:

  • Entity: an abstract class that defines these four properties. All 'entity objects' (in this case Person and Category) must inherit Entity.
  • IEntityManager: a generic interface (type parameter as Entity) that defines methods like Load, Insert, Update, etc.
  • NHibernateEntityManager: an implementation of this interface using NHibernate to do the loading, saving, etc.

Now, the Person and Category classes are straightforward, they just define the attributes of the tables of course (keeping in mind that four of them are in the base Entity class).
Since the Persons table is related to the Categories table via the CategoryId attribute, the Person class has a Category property that holds the related category. However, in my webpage, I will also need the name of this category (CategoryName), for databinding purposes for example. So I created an additional property CategoryName that returns the CategoryName property of the current Category property, or an empty string if the Category is null:


Namespace Database
    Public Class Person
        Inherits DatabaseFramework.Entity

    Public Overridable Property Firstname As String
    Public Overridable Property Lastname As String
    Public Overridable Property Category As Category

    Public Overridable ReadOnly Property CategoryName As String
        Get
            Return If(Me.Category Is Nothing, _
                      String.Empty, _
                      Me.Category.CategoryName)
        End Get
    End Property

End Class

End Namespace

I am mapping the Person class using this mapping file. The many-to-one relation was suggested by Yads in another thread:

<id name="Id" column="Id" type="int" unsaved-value="0">
  <generator class="identity" />
</id>

<property name="CreatedTime" type="DateTime" not-null="true" />
<property name="UpdatedTime" type="DateTime" not-null="true" />
<property name="Deleted" type="Boolean" not-null="true" />

<property name="Firstname" type="String" />
<property name="Lastname" type="String" />
<many-to-one name="Category" column="CategoryId" class="NHibernateWebTest.Database.Category, NHibernateWebTest" />

(I can't get it to show the root node, this forum hides it, I don't know how to escape the html-like tags...)

The final important detail is the Load method of the NHibernateEntityManager implementation. (This is in C# as it's in a different project, sorry about that).
I simply open a new ISession (ISessionFactory.OpenSession) in the GetSession method and then use that to fill an EntityCollection(Of TEntity) which is just a collection inheriting System.Collections.ObjectModel.Collection(Of T).


        public virtual EntityCollection< TEntity > Load()
        {
            using (ISession session = this.GetSession())
            {
                var entities = session
                                .CreateCriteria(typeof (TEntity))
                                .Add(Expression.Eq("Deleted", false))
                                .List< TEntity >();
                return new EntityCollection< TEntity >(entities);
            }
        }


(Again, I can't get it to format the code correctly, it hides the generic type parameters, probably because it reads the angled symbols as a HTML tag..? If you know how to let me do that, let me know!)

Now, the idea of this Load method is that I get a fully functional collection of Persons, all their properties set to the correct values (including the Category property, and thus, the CategoryName property should return the correct name).
However, it seems that is not the case. When I try to data-bind the result of this Load method to a GridView in ASP.NET, it tells me this:

Property accessor 'CategoryName' on object 'NHibernateWebTest.Database.Person' threw the following exception:'Could not initialize proxy - the owning Session was closed.'

The exception occurs on the DataBind method call here:

        public virtual void LoadGrid()
        {
            if (this.Grid == null) return;

        this.Grid.DataSource = this.Manager.Load();
        this.Grid.DataBind();
    }

Well, of course the session is closed, I closed it via the using block. Isn't that the correct approach, should I keep the session open? And for how long? Can I close it after the DataBind method has been run?

In each case, I'd really like my Load method to just return a functional collection of items. It seems to me that it is now only getting the Category when it is required (eg, when the GridView wants to read the CategoryName, which wants to read the Category property), but at that time the session is closed. Is that reasoning correct?

How do I stop this behavior? Or shouldn't I? And what should I do otherwise?

Thanks!

解决方案

Setting lazy loading = false in your mapping will solve the error. It would be a better practice though to tell NHibernate in your load query that you want to fetch the child collection of categories eagerly.

For a criteia query something like .SetFetchMode("Categories", FetchMode.Eager) should work.

Here is a link that gives some insight into the so called "n + 1" problem, how lazy loading relates to it, and how NHibernate is meant to be used.

HTH,
Berryl

something like this:

           var entities = session
                            .CreateCriteria<TEntity>()
                            .SetFetchMode("Categories", FetchMode.Eager)
                            .Add(Expression.Eq("Deleted", false))
                            .List< TEntity >();

这篇关于由于加载后关闭会话,导致多对一关系异常的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-11 16:13