0

我正在使用 NHibernate 通过代码进行映射。这是课程:

[Serializable]
public class Person : ObjectBase
{
    public virtual IDictionary<AttributeType, Attribute> Attributes { get; set; }
}

这是映射:

public class PersonMapping : BaseObjectMapping<Person>
{
    public PersonMapping()
    {
        Map(x => x.Attributes, c =>
        {
            c.Access(Accessor.Property);
            c.Lazy(CollectionLazy.NoLazy);
            c.Cascade(Cascade.All);
            c.Key(k => k.Column("PersonId"));
        }, k => k.Element(m => m.Column("AttributeTypeId")), r => r.OneToMany());
    }
}

问题是,当我尝试访问字典中的键时,键类型是 AttributeTypeProxy。然后,它似乎试图在会话不再存在的代码区域中延迟加载密钥。因此,抛出错误:Initializing[AttributeType#1]-Could not initialize proxy - no Session。

因此,经过一些研究,我被告知我需要强制它急切加载。我没有看到任何与键映射相关的 .Lazy。另外,我已经验证了 Map 的值是预先加载的(我假设这是由于 CollectionLazy.NoLazy)。如何使 Map 急切加载的密钥?


编辑:

为了使其无法按照 Rippo 的回答正确加载属性,我临时更改了 Person 的映射以生成 Attribute 表。

Map(x => x.Attributes, c =>
{
    c.Key(k => k.Column("PersonId"));
    c.Table("Attribute");
}, 
k => k.Element(m => m.Column("AttributeTypeId")), 
r => r.Component(m => m.Property(p => p.Data)));

现在,当您按照他的示例尝试访问时person.Attributes,它会给出以下错误:Initializing[Person#1]-failed to lazily initialize a collection of role: Person.Attributes, no session or session was closed

Rippo 还建议发布实际检索数据的代码。我使用这些方法:

internal static class RepositoryHelper
{
    public static void PerformDatabaseUpdate(Action<ISession> action)
    {
        using (var session = NHibernateHelper.OpenSession())
        using (var transaction = session.BeginTransaction())
        {
            action(session);
            transaction.Commit();
        }
    }

    public static T PerformDatabaseQuery<T>(Func<ISession, T> action)
    {
        using (var session = NHibernateHelper.OpenSession())
        {
            return action(session);
        }
    }
}

在尝试使用 Map 映射 IDictionary 之前,我在使用这些之前没有遇到任何问题。不过,在此之前,我一直只使用属性和 ICollections(通过 Set 映射)。我还应该提到,这用于客户端-服务器应用程序。客户端是一个 MVVM WPF 应用程序,而服务器是一个服务(目前,只是一个控制台应用程序)。


编辑2:

我找到了一种解决方法,但我绝对不会认为这是一个答案。我也不知道它为什么起作用。我能得出的唯一结论是,通过代码映射的 Map 没有发挥作用。这是我所做的更改:

[Serializable]
public class Person : ObjectBase
{
    public virtual IDictionary<AttributeType, Attribute> Attributes { get; set; }
    public virtual ICollection<AttributeType> AttributeTypes
    {
        get { return Attributes.Keys; }
        set { }
    }
}

是的。我添加了一个 AttributeType 的 ICollection 来保存字典中的键。它只能从 Attributes 返回实际的键;不过,我需要 NHibernate 的 set 方法才能正确使用它。我只是在方法中什么都没放。

然后,我将其添加到映射中:

public class PersonMapping : BaseObjectMapping<Person>
{
    public PersonMapping()
    {
        Map(x => x.Attributes, c =>
        {
            c.Access(Accessor.Property);
            c.Lazy(CollectionLazy.NoLazy);
            c.Cascade(Cascade.All);
            c.Key(k => k.Column("PersonId"));
        }, k => k.Element(m => m.Column("AttributeTypeId")), r => r.OneToMany());

        Set(x => x.AttributeTypes, c =>
        {
            c.Table("UnusedAttributeTypes");
            c.Access(Accessor.Property);
            c.Lazy(CollectionLazy.NoLazy);
            c.Key(k => k.Column("PersonId"));
        }, r => r.ManyToMany());
    }
}

这只是创建一个名为 UnusedAttributeTypes 的表,其中包含 PersonId 和 AttributeTypeId。这是一个虚拟表,因为我无法通过我的对象访问。

现在,当我去调用时person.Attributes.Keys,它们不是代理 AttributeType 对象,而是实际对象并且它们被正确填充。此外,person.Attributes.Values他们仍然像以前一样居住。那里没有变化。

我希望我不需要搜索 NHibernate 源代码来找出解决问题的原因,或者实际问题是什么。

编辑 3:删除 c.Cascade(Cascade.All); 来自 AttributeTypes 映射。

4

1 回答 1

1

IMO您的问题不是懒惰->急切的问题,它更多地与您如何处理会话管理有关。在大多数情况下,延迟加载是最好的方法。

如果您的代码是这样的: -

  using (var session = sessionFactory.OpenSession())
  {
    using (var transaction = session.BeginTransaction())
    {
       ... code to get a person
       transaction.Commit();
    }
  }

然后需要在内部 using 块中检索所有数据,否则如果您尝试在外部获取它,您将获得一个Could not initialize proxy - no Session.

这将引发您看到的错误:-

  Person person
  using (var session = sessionFactory.OpenSession())
  {
    using (var transaction = session.BeginTransaction())
    {
       person = session.Get<Person>(1);
       transaction.Commit();
    }
  }
  var attrs = person.Attributes...

要解决这个问题,最简单的方法是编写一个session-per-request. 我看到的很多代码都使用 http mopdule 并连接Begin/End requests

我更喜欢这种方法(和另一种方法),因为它更适合我,因为您可以将整个事务包装在 ActionResult 周围。

您选择哪一个取决于您,但我建议您使用该session-per-request策略。

注意:我假设您使用的是 asp.net MVC,如果它是 WebForms,您将需要使用我的第一个选项。如果是 WinForms 等,则需要查找session-per-presenter .

于 2013-06-26T06:53:38.807 回答