由早期标准限制的NHibernate Lazy Loading

时间:2014-05-09 19:16:57

标签: nhibernate lazy-loading criteria

我一直在NHibernate中看到一些关于延迟加载和在我的应用程序中设置的几个过滤器的奇怪行为。似乎如果我对延迟加载的关联进行过滤,那么该关联只有适用于NHibernate会话剩余部分的过滤器的成员。我知道这听起来令人困惑,所以我用一个相当简单的例子再现了它。

我制作了一个使用Spring MVC 1.3.2和NHibernate 3.3.3.4001的“PetFun”Web应用程序。这个虚拟应用程序只有一个HomeController,以及宠物和玩具的数据库处理。 Pet对象与Toy有多对多的关系。这两个对象的hbm.xml映射:

<class name="Pet" table="PETS">
  <id name="Id" column="ID">
    <generator class="identity"/>
  </id>
  <property name="name" column="NAME" not-null="true" access="field"/>
  <set name="toys" table="PETS_TOYS" lazy="true" access="field">
    <key column ="PET_ID" />
    <many-to-many class="Toy, PetFun.Domain" column="TOY_ID" />
  </set>
</class>

<class name="Toy" table="TOYS">
  <id name="Id" column="ID">
    <generator class="identity"/>
  </id>
  <property name="name" column="NAME" not-null="true" access="field"/>
</class>

我有一个Pets存储库(自动装入HomeController),它有两个功能:GetByName(字符串名称)和GetByToy(玩具玩具)。他们的实施:

public IEnumerable<Pet> GetByName(string name)
  {
  return databaseSessionManager.GetCurrentSession().CreateCriteria<Pet>()
    .Add(Restrictions.Eq("name", name))
    .List<Pet>();
  }

public IEnumerable<Pet> GetByToy(Toy toy)
  {
  return databaseSessionManager.GetCurrentSession().CreateCriteria<Pet>()
    .CreateAlias("toys", "toys", NHibernate.SqlCommand.JoinType.LeftOuterJoin)
    .Add(Restrictions.Eq("toys.Id", toy.Id))
    .List<Pet>();
  }

我还有一个带有GetName(字符串名称)功能的Toys存储库,它与Pets中的功能相同。最后,我有以下数据库模式和数据集:

IF EXISTS (SELECT 1 FROM sysobjects WHERE xtype='u' AND name='PETS_TOYS') DROP TABLE PETS_TOYS;
IF EXISTS (SELECT 1 FROM sysobjects WHERE xtype='u' AND name='TOYS') DROP TABLE TOYS;
IF EXISTS (SELECT 1 FROM sysobjects WHERE xtype='u' AND name='PETS') DROP TABLE PETS;

CREATE TABLE PETS (
  ID int IDENTITY(1,1) NOT NULL, 
  NAME NVARCHAR(50) NOT NULL 
  CONSTRAINT PK_PETS PRIMARY KEY CLUSTERED 
  ( ID ASC ) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY] ) ON [PRIMARY];

CREATE TABLE TOYS (
  ID int IDENTITY(1,1) NOT NULL, 
  NAME NVARCHAR(50) NOT NULL 
  CONSTRAINT PK_TOYS PRIMARY KEY CLUSTERED 
  ( ID ASC ) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY] ) ON [PRIMARY]; 

CREATE TABLE PETS_TOYS (
  PET_ID INT NOT NULL, 
  TOY_ID INT NOT NULL, 
  CONSTRAINT FK_PETSTOYS_PETID FOREIGN KEY (PET_ID) REFERENCES PETS(ID) ON DELETE CASCADE, 
  CONSTRAINT FK_PETSTOYS_TOYID FOREIGN KEY (TOY_ID) REFERENCES TOYS(ID) ON DELETE CASCADE);


INSERT INTO PETS (NAME) VALUES('Fido');
INSERT INTO PETS (NAME) VALUES('Einstein');
INSERT INTO PETS (NAME) VALUES('Cujo');

INSERT INTO TOYS (NAME) VALUES('Ball');
INSERT INTO TOYS (NAME) VALUES('Rope');
INSERT INTO TOYS (NAME) VALUES('Frisbee');
INSERT INTO TOYS (NAME) VALUES('Blood');

INSERT INTO PETS_TOYS (PET_ID, TOY_ID) VALUES(1, 1); -- Fido has Ball
INSERT INTO PETS_TOYS (PET_ID, TOY_ID) VALUES(2, 2); -- Einstein has Rope
INSERT INTO PETS_TOYS (PET_ID, TOY_ID) VALUES(3, 1); -- Cujo has Ball
INSERT INTO PETS_TOYS (PET_ID, TOY_ID) VALUES(3, 4); -- Cujo has Blood

关于数据本身的主要注意事项是Cujo有两个玩具,球和血。

如果我只调用Pet cujo = pets.GetByName(“Cujo”)之类的东西。首先(),我得到我期望的...一个Pet对象与两个玩具相关联(“Ball”和“Blood” )。但是,以下结果会产生不同的结果:

Toy ball = toys.GetByName("Ball").First();
IEnumerable<Pet> petsWithBall = pets.GetByToy(ball); // pet.toy.id = ball.id -- causes problem below
Pet cujo = pets.GetByName("Cujo").First();
IEnumerable<Toy> cujoToys = cujo.GetToys(); // cujo's lazy collection only has ball

现在 cujoToys 变量只包含一个玩具:“Ball”。似乎执行 Pets#GetByToy (上面第2行)中的查询限制了第二个查询加载的Cujo对象。延迟加载的集合仅由与第一个查询的限制匹配的实体填充。

我注意到,当我的别名涉及左外连接时,似乎只是这种情况。在这个特定的例子中,我不需要这个连接,但是我的“真实”应用程序确实需要在发生问题时进行连接。

真的令人讨厌的事情是,如果延迟加载的集合被标记为脏,因为我实际上正在修改它,NHibernate将损坏/有限形式的数据写入数据库。因此,如果我在这个请求周期中给Cujo一个飞盘玩具,他就会失去与血液的联系......这将是一部无聊的电影。

作为一个注释,如果我在下一个请求的开始加载Cujo,他有他的两个玩具......所以我猜这与缓存有关(我的NHibernate会话的范围是请求周期。)

以前有人见过这样的事吗?我很乐意在需要时澄清任何细节。来自Web.config的我的NHibernate配置,以防它有用:

<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
  <reflection-optimizer use="false" />
  <session-factory>
    <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
    <property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>
    <property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
    <property name="show_sql">true</property>
    <property name="current_session_context_class">managed_web</property>
    <mapping assembly="PetFun.ORM"/>
  </session-factory>
</hibernate-configuration>

1 个答案:

答案 0 :(得分:1)

嗯,如何修复此方案的方法是更改​​:IEnumerable<Pet> GetByToy(Toy toy)。这是新的实施:

public IEnumerable<Pet> GetByToy(Toy toy)
{
    // I. we have to firstly create a subquery
    var petsWithToySubquery = DetachedCriteria.For<Pet>("pet")
        // join toys
        .CreateAlias("toys", "toys", NHibernate.SqlCommand.JoinType.LeftOuterJoin)
        // add restriction
        .Add(Restrictions.Eq("toys.Id", toy.Id))
        // and the key to success: select the Pet ID
        .SetProjection(Projections.Property("pet.Id"));

    // II. the correctly initiated list 
    // (ready to properly and lazily load all Toys)
    var list = databaseSessionManager.GetCurrentSession()
        // THE query - to get filtered list of Pets
        .CreateCriteria<Pet>()
        // here we do filtering 
        .Add(Subqueries.PropertyIn("Id", petsWithToySubquery));
        // the list, ready to support lazy loading
        .List<Pet>();

    // THE list of Pets, having searched Toy
    return list;
}

注意:总的来说,我会说,多对多会给你带来更多问题/挑战而不是好处。

我的建议是,改变配对表并用自己的代理键扩展它。然后显式地将此表映射为标准实体。在此处查看更多信息: