17

One-to-one relations within nhibernate can be lazyloaded either "false" or "proxy". I was wondering if anyone knows a way to do a lazy one-to-one mapping.

I worked out a hack to achieve the same result by using a lazy set mapped to a private field, and having the public property return the first result of that set. It works, but isn't the cleanest code...

Thanks in advance!

Casper
  • 1,242
  • 1
  • 11
  • 12

6 Answers6

20

Lazy loading of one-to-one isn't supported unless the association is mandatory. See here for the reasoning.

It boils down to the fact that in order to decide if the other side of the relationship exists (N)Hibernate has to go to the database. Since you've already taken the database hit, you might as well load the full object.

While there are cases where hitting the DB just to see if the related object exists without actually loading the object makes sense (if the related object is very "heavy"), it isn't currently supported in NHibernate.

Sean Carpenter
  • 7,681
  • 3
  • 37
  • 38
  • 12
    And what if 90% of the time you aren't even going to hit the database to see if it exists because it's not used? Developers know better on their personal usage than NHibernate, something like this should be supported. – BradLaney May 23 '12 at 16:09
  • @BradLaney, on non constrained="true" side, lazy loading through proxyfying can not work as explained in Sean's link. But since [early 2010‌](https://ayende.com/blog/4378/nhibernate-new-feature-no-proxy-associations)​, lazy="no-proxy" is supported on one-to-one and many-to-one. With this option, lazy loading works on non constrained side (tested with NH 4) when querying it (not it in case of direct load: still eager fetching). This causes the root entity to get proxyfied instead of the related entity though, for allowing intercepting the call to related entity property and lazily load it. – Frédéric Mar 22 '16 at 08:21
4

As far as I know, there isn't a non-hacky way to lazy load a one-to-one. I hope I'm wrong, but last time I checked it was the case.

James Gregory
  • 14,173
  • 2
  • 42
  • 60
  • 4
    Any progress in 2015 regards this issue? – Cristian E. May 03 '15 at 10:53
  • `lazy="no-proxy"` is supported since [January 2010](https://ayende.com/blog/4378/nhibernate-new-feature-no-proxy-associations), for another reason, but by the way enables lazy-loading on the non `constrained` side (witnessed with NH 4). (Lazy loading was supported for long on `constrained` side, but many people miss the point. See this [answer](/a/389345/1178314).) – Frédéric Mar 21 '16 at 23:42
4

There is way thought. It's described here in details :

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernateTest" namespace="NHibernateTest">
  <class name="Person" >
    <id name="PersonID" type="Int32">
      <generator class="identity" />
    </id>
    <property name="LastName" type="String" length="50" />
    <property name="FirstName" type="String" length="50" />
    <many-to-one name="Photo" class="PersonPhoto" />
  </class>

  <class name="PersonPhoto">
    <id name="PersonID" type="Int32">
      <generator class="foreign">
        <param name="property">Owner</param>
      </generator>
    </id>
    <property name="Photo" type="BinaryBlob" />
    <one-to-one name="Owner" class="Person" constrained="true" />
  </class>
</hibernate-mapping> 
Artem Tikhomirov
  • 21,497
  • 10
  • 48
  • 68
  • Hi Artem, did you know your question about "Flash Media Server not calling application.onDisconnect handler ?" was deleted ? (http://stackoverflow.com/questions/359727) I can still see it because of my rep level. Do you want me to write a uservoice entry asking for its restoration ? – VonC Feb 17 '09 at 14:05
  • Huh. General(ist) is on the warpath :) C'mon VonC, take it ease. There is still your disclaimer on top of your answer. It was a fair play. Thank you for your honesty. – Artem Tikhomirov Feb 20 '09 at 08:31
  • This doesn't work (even when specifying the column, as per David Yates' answer). In a number of situations NHibernate gets very confused. – cbp Mar 28 '12 at 08:15
  • @ArtemTikhomirov this only works for a mandatory association "the one-to-one relation sets constrained=true since the person details belongs to a person (a person MUST exists)" – Răzvan Flavius Panda Jul 04 '15 at 21:35
2

I tried the example used by Artem Tikhomirov above. I kept getting an error that the Photo column does not exist. After looking at this, I figured out that the mapping was off a little. When I changed the many-to-one mapping to specify the column name like this:

many-to-one name="Photo" column="PersonID" class="PersonPhoto" unique="true"

I got it to work. I hope this helps someone :o)

David Yates
  • 1,935
  • 2
  • 22
  • 38
0

After reading the answers here, I´ve manage to get it to work. I´m just going to add this example because I´m using a One to One relation with Constrained= False and because it´s a Mapping by Code Example

Two Classes:

public class Pedido:BaseModel
{
    public virtual BuscaBase Busca { get; set; }
}

public class BuscaBase : BaseModel
    {       
        public virtual Pedido Pedido { get; set; }
    }

Mappings:

public class PedidoMap : ClassMapping<Pedido>
{
    public PedidoMap()
    {
         Id(x => x.Id, x => x.Generator(Generators.Identity));            

         ManyToOne(x => x.Busca, m => 
         { 
             m.Cascade(Cascade.DeleteOrphans);
             m.NotNullable(true); m.Unique(true);
             m.Class(typeof(BuscaBase));
         });    
    }
}

public class BuscaBaseMap : ClassMapping<BuscaBase>
{
    public BuscaBaseMap()
    {            
        Id(x => x.Id, x => x.Generator(Generators.Sequence, g => g.Params(new { sequence = "buscaefetuada_id_seq" })));

        OneToOne(x => x.Pedido, m =>
        {
            m.Lazy(LazyRelation.NoProxy);
            m.Constrained(false);
            m.Cascade(Cascade.None);
            m.Class(typeof(Pedido));
        });            
    }
}

Note: I was Using for the one-to-one mapping m.PropertyReference(typeof(Pedido).GetProperty("Busca")); but this does't work for the lazy loading. You have to specify the relation using the Class

A quick brief about the Constrained = False used in here, the "Pedido" object might not exist in "BuscaBase" object.

Daniel
  • 2,780
  • 23
  • 21
0

What worked for me is the following (very similar to @Daniel) but I found that it was necessary to specify the LazyRelation.NoProxy on both ends of the mapping.

  public class Person
    {
        public virtual int Id { get; set; }

        public virtual string Name { get; set; }

        public virtual PersonDetails Details { get; set; }

        public class PersonMap : ClassMapping<Person>
        {
            public PersonMap()
            {
                Id(x => x.Id, m =>
                {
                    m.Generator(Generators.Native);
                });

                 
                Property(x => x.Name);

                OneToOne(x => x.Details, m =>
                { 
                    m.Lazy(LazyRelation.NoProxy);
                    m.Cascade(Cascade.Persist); 
                });
            }
        }
    }

    public class PersonDetails
    {
        public virtual int Id { get; set; }

        public virtual string ExtraDetails { get; set; }

        public virtual Person Person { get; set; }

        public class PersonDetailsMap : ClassMapping<PersonDetails>
        {
            public PersonDetailsMap()
            {

                Id(x => x.Id, m =>
                {
                    m.Generator(Generators.Native);
                }); 

                Property(x => x.ExtraDetails);
                
                ManyToOne(x => x.Person, m =>
                {
                    m.Lazy(LazyRelation.NoProxy);
                    m.Unique(true);
                    m.NotNullable(true); 
                });
            }
        }
    }


using var session = NhHelper.OpenSession();

var person1 = new Person();
person1.Name = "A";

var person1Details = new PersonDetails();
person1Details.ExtraDetails = "A details"; 

person1.Details = person1Details;
person1Details.Person = person1;

session.Save(person1);
//because of PersonMapping's Cascade.Persist it is not necessary to manually save person1Details object.
 

using var session = NhHelper.OpenSession();
foreach(var person in session.Query<Person>()) {
  Console.WriteLine(person.Name);  //<-- does not load PersonDetails unless it's property is accessed
}
  • NHibernate 5.3.5
  • Npgsql 5.0.3 (Postgresql Db).
krdx
  • 1,315
  • 15
  • 22