0

I'm mapping by code using NHibernate. Here is the class:

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

and here is the mapping:

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

The problem is, when I go and attempt to access the keys in the dictionary, the key type is AttributeTypeProxy. Then, it seems to attempt to lazy load the key in an area of code where the session no longer exists. Thus, throwing the error: Initializing[AttributeType#1]-Could not initialize proxy - no Session.

So, after some research, I'm told I need to force it to eager load. I do not see any .Lazy related to the key mappings. Also, I've verified that the value of the Map is eager loaded (I assume this is due to the CollectionLazy.NoLazy). How do I make the key of a Map eager load?


Edit:

In an attempt to produce it not loading Attributes properly as per Rippo's answer, I changed the mapping for Person temporarily to produce the Attribute table.

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

Now, when you try to access, as per his example, person.Attributes, it gives this error: Initializing[Person#1]-failed to lazily initialize a collection of role: Person.Attributes, no session or session was closed

Rippo also suggested posting code that actually retrieves the data. I use these methods:

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

I haven't had any problems using these prior to trying to use a Map for mapping an IDictionary. Though, before this, I had been only using properties and ICollections (via Set mapping). I should also mention that this is used for a client-server application. The client is a MVVM WPF application and the server will be a service (currently, just a console app).


Edit2:

I've found a workaround, but I would definitely not consider it an answer. I also have no idea why it works. The only conclusion I can come up with is that the Map in mapping by code isn't doing its job. Here is what I changed:

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

Yeah. I added an ICollection of AttributeType to also hold the Keys from the dictionary. It can only return the actual keys from Attributes; though, I need the set method for NHibernate to use it properly. I simply put nothing in the method.

Then, I added this to the mapping:

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

This is simply creating a table called UnusedAttributeTypes that contains the PersonId and the AttributeTypeId. It is a dummy table since I can't access via my objects.

Now, when I go and call person.Attributes.Keys, they are not proxy AttributeType object, but the actual objects and they are populated properly. Also, the person.Attributes.Values are still populated as they have been. No change there.

I hope I don't need to search through the NHibernate source code to figure out why this solves the issue, or what the actual issue is.

Edit 3: Removed the c.Cascade(Cascade.All); from the AttributeTypes mapping.

Community
  • 1
  • 1
Michael Yanni
  • 1,476
  • 4
  • 17
  • 29

1 Answers1

1

IMO your problem isn't a lazy -> eager issue the issue it is more to do with how you are handling your session management. In most instances lazy loading is the best route to take.

If your code is something like this:-

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

Then ALL data needs to be retrieved in the inner using block else if you attempt to get it outside you will get a Could not initialize proxy - no Session.

This WILL throw the error you are seeing:-

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

To get around this issue the easiest way is to code up a session-per-request. A lot of code I see uses a http mopdule and wire up Begin/End requests.

I prefer this approach (and another) as it sits better with me as you can wrap an entire transaction around a ActionResult.

Which ever one you choose its up to you but I would recommend using the session-per-request strategy.

Note: I am assuming you are using asp.net MVC, if its WebForms the you will need to go with my first option. If it is WinForms etc then you will need to look-up session-per-presenter .

Rippo
  • 22,117
  • 14
  • 78
  • 117
  • I, in general, understand what you are saying. But, it seems that `person.Attributes` does exist and does not throw the error. As mentioned, the values (of type **Attribute**) are loaded into `Attributes`. I can see the members of **Attribute** being populated. These are the values of the Map. However, the keys (of type **AttributeType**) are not populated. Your explanation does not explain why the values of the dictionary are loaded, and the keys are not. If Attributes wasn't partially populated, I'd look into your approach. It almost seems like an NHibernate bug to me. – Michael Yanni Jun 26 '13 at 13:55
  • I suspect we are going to need to see the code that retrieves the data including the using blocks, it **depends** – Rippo Jun 26 '13 at 14:17
  • I will also add I am not sure if you are mapping your `map` correctly, have you seen this, http://notherdev.blogspot.co.uk/2012/02/mapping-by-code-map.html also my point still stands about using a session-per-request as you will hit these types of issues all over the place. – Rippo Jun 26 '13 at 14:37
  • I've read many of those blog posts as those seem to be the only documentation on Mapping by Code that really exists, other than random SO questions. I've edited my original question to add what you've requested. Let me know if you need the contents of the OpenSession method. – Michael Yanni Jun 26 '13 at 15:00