Low Flying Pelican
Low Flying Pelican

Reputation: 6054

avoid duplicate parent objects in Nhibernate when mapping with property-ref

I have 2 entities:

Entity A

    public class EntityA
        {
            protected IList<EntityB> _bList = new List<EntityB>();

            virtual public int Id { get; set; }
            virtual public int ExtId { get; set; }


            public virtual void AddB(EntityB b)
            {
                if (!_bList.Contains(b)) _bList.Add(b);
                b.A = this;
                b.ExtId  = this.ExtId;
            }

            public virtual void RemoveB(EntityB b)
            {
                _bList.Remove(b);
            }

            public virtual IList<EntityB> BList
            {
                get { return _bList.ToList().AsReadOnly(); }
            }
        }

Entity A Mapping

    <?xml version="1.0" encoding="utf-8" ?>
    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" auto-import="true">
      <class name="hibernate.domain.mappings.EntityA, hibernate.domain" lazy="true">
        <id name="Id">
          <generator class="native" />
        </id>
        <property type="int" name="ExtId" column="[ExtId]" />
        <bag
          name="BList"
          table="EntityB"
          cascade="all"
          lazy="true"
          inverse="true"
          access="field.camelcase-underscore"
          optimistic-lock="false"
          >
          <key column ="ExtId" property-ref="ExtId" />
          <one-to-many class="hibernate.domain.mappings.EntityB, hibernate.domain" />
        </bag>
    </hibernate-mapping>

Entity B

     public class EntityB
        {
            protected EntityA _a;

            virtual public int Id { get; set; }
            virtual public int ExtId { get; set; }
            virtual public EntityA A
            {
                get { return _a; }
                set { _a = value; }
            }
        }

Entity B Mapping

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" auto-import="true">
  <class name="hibernate.domain.mappings.EntityB, hibernate.domain" lazy="true">
    <id name="Id">
      <generator class="native" />
    </id>
    <property type="int" name="ExtId" column="[EXTID]" />
    <many-to-one
            name = "A"
      property-ref ="ExtId"
            not-null="true"
            class = "hibernate.domain.mappings.EntityA, hibernate.domain"
      access="field.camelcase-underscore"
            cascade = "save-update"
            fetch="select"
            insert = "false"
      lazy = "false"
            update = "false"
      column="ExtId"
      />
  </class>
</hibernate-mapping>

The problem is when I load an object of EntityA and try to get the count of BList, it would execute one SQL per EntityB in the list to fetch it's EntityA reference, But the EntityA for each EntityB would be the original entity I have loaded first. This has become a big performance bottleneck when there are large number of entityB inside entityA. The database is legacy and it's used with some of the legacy applications so changing of the database structure is not an option. And using of second level cache also is not an option as it would make system fail when it's running with legacy code which uses raw SQL. Can anyone suggest a solution to this problem without changing the database structure?

Upvotes: 1

Views: 1023

Answers (2)

Stefan Steinegger
Stefan Steinegger

Reputation: 64628

The problem is that the cache only works based on the primary key. If it is linked by some other property, it doesn't know if it is already loaded or not and loads it again.

There is a hint in this post by ayende that the natural-id may be considered by the second level cache. I know you don't want it. And it is not even sure that it works in this case (ayende is using an explicit filter to natural id).

You may try to map the EXTID column as the primary key in NH and the Id as a generated property ... There is of course the risk that you get other problems instead.

When nothing really works, you need to perform queries to get the exact data you need. For instance:

select a, size(a.BList)
from EntityA a

Which isn't funny of course, you loose the natural way of using the entities as POCOs.

Upvotes: 1

Firo
Firo

Reputation: 30803

you can change fetch="select" to fetch="join" in <many-to-one name = "A"> then it would issue a join when fetching Bs and doesnt have to Select N+1

Upvotes: 0

Related Questions