0
votes

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.

1
Have you tried Map(...).Not.Lazy();?Andre Calil
That's Fluent NHibernate. I'm using Mapping by Code, and not Fluent.Michael Yanni
Indeed, I'm sorry. I've been using Fluent for so much time that it feels like there's no other way of mapping =)Andre Calil

1 Answers

1
votes

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 .