Question

I have a class Unit with a Dictionary of Parts. The mapping looks like this

<class name="Unit" table="Units">
<id name="Id">
  <generator class="native"/>
</id>
<property name="CreationDate" />

<property name="MacAddress" />
<property name="OEMId" />
<property name="SerialNumber" />
<property name="QualityControlState" />

<map name="Parts" table="UnitParts" lazy="false">
  <key column="UnitId"/>
  <index column="TypeOfPart" type="integer"/>
  <composite-element class="UnitPart">
    <property name="PartInfo"/>
    <property name="Remarks"/>
  </composite-element>
</map>

When I Session.SaveOrUpdate(Unit) all goes well: the two tables are filled with the right data. And the unit is retreivable (with all its Parts) using Session.Get(typeof(Unit) as well.

The problem: Obtaining multiple units lazy loads the Parts Dictionary.

The problem rephrased in pseudo code: LoadMethodOfPartsDictionary = (Stepping with Debugger) ? Eagerly : Lazy;

The following code baffles me. When I step through it using the debugger the Parts Dictionary of a Unit is eagerly loaded. But when I run to return Units (not stepping through Units = crit.Future<Unit>().ToList<Unit>();) its seems NHibernate wants to lazy load as the Parts Dictionary suddenly is NHibernate.Collection.Generic.PersistentGenericMap.

/// <summary>
/// Retreives a list of units by a set of criteria.
/// </summary>
/// <returns>A list of units that fall within/match the criteria</returns>
public static List<Unit> GetUnits(List<KeyValuePair<Unit.SortableProperties, ListSortDirection>> SortColumnOrder, out uint NumberOfRecordsWithoutLimit, uint Start = 0, int End = -1, FilterUnits Filter = default(FilterUnits))
{
    List<Unit> Units = default(List<Unit>);

    NumberOfRecordsWithoutLimit = 0;
    using (ISession Session = ORM.SessionFactory.OpenSession())
    {
        using (ITransaction Transaction = Session.BeginTransaction())
        {
            ICriteria crit = Session.CreateCriteria<Unit>();

            //Limit result set, used for paging
            if (End > 0)
            {
                crit.SetFirstResult((int)Start);
                crit.SetMaxResults(End);
            }

            //TODO: Implement filter code here

            //Add the sort order
            foreach (KeyValuePair<Unit.SortableProperties, ListSortDirection> kvp in SortColumnOrder)
            {
                String PropertyName = "";
                switch (kvp.Key)
                {
                    case Unit.SortableProperties.PRODUCTIONDATE:
                        PropertyName = "CreationDate";
                        break;
                    default:
                        throw new NotImplementedException(kvp.Key.ToString() + " isn't implemented for sorting yet.");
                }
                crit.AddOrder(new Order(PropertyName, (kvp.Value == ListSortDirection.Ascending)));
            }

            if (End > 0)
            {
                //Count the total units available in database.
                Units = crit.Future<Unit>().ToList<Unit>(); //This seems to lazy load the Units
                IFutureValue<int> RowCount = Session.CreateCriteria<Unit>()
                                        .SetProjection(Projections.Count(Projections.Id()))
                                        .FutureValue<int>();
                NumberOfRecordsWithoutLimit = (uint)RowCount.Value;
            }
            else
            {
                Units = (List<Unit>)crit.List<Unit>();
                NumberOfRecordsWithoutLimit = (uint)Units.Count;
            }

            Transaction.Commit();
            Session.Close();
            return Units;
        }
    }
}

Any hints are appreciated.

P.S. I used the [Debugging] tag as this seems to be key in this scenario.

Was it helpful?

Solution

There are two different concepts how to recieve the data from the DB. In case of one-to-many (lists, dictionaries).

1) We can call NHibernate to get data by ID or reference (similar cases)

session.Get<Unit>(1);

In this case NHibernates injects all the properties not marked as lazy. So in your case, this will cause to eagerly load of the Parts as well.

the similar is the reference property: public virtual Unit Unit { get; set; } of ane OtherObject

var otherObject = ... // get from session
var unit = otherObject.Unit; // the full Unit with all `Lazy="false"` properties is loaded

2) But we can also use the Criteria (As in your case). However, this is different approach. In this case, we are explicitly saying that we want only work with Unit

Units = crit.Future<Unit>().ToList<Unit>();

This has many advantages:

  • we can do efficient paging only on the Unit table
  • only the Unit is loaded (maybe the Parts won't be used)
  • if Parts are used, they will be lazily loaded

But if really needed (regardles of side effects like inocrrect paging) we can also load the Parts in one select:

Units = crit
  .SetFetchMode("Parts", FetchMode.Eager)
  .Future<Unit>()
  .ToList<Unit>();

Now is all populated at once.

3) The approach I would vote for (I am using 100%) is to let collection to be lazy, and assign the batch-size 19.1.5. Using batch fetching

EXTENDED

To show you how to change the mapping:

<map name="Parts" table="UnitParts" lazy="true" batch-size="25">

From that moment, your Util is lightweight. No Parts loaded, until really needed. If the session is opened during the whole request, once the Parts are touched, they will be loaded in one SELECT (if the page size of Utils is not larger then 25)

This approach is very efficient, becuase it uses the power of ORM - everything is lazy

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top