Sometimes we only need to know the number of items in a collection. It may be for displaying purposes or some behavioral logic. Either way, it seems a bit resource intensive that we should have to load all of the entities from the database, just to count them. With extra lazy collections we can improve this situation a bit.
Refer to the Getting Ready section of this chapter instructions at the beginning of this chapter.
ExtraLazy
to the project.Accessory
to the folder, having the code:using NH4CookbookHelpers.Queries.Model; namespace QueryRecipes.ExtraLazy { public class Accessory : Entity { public virtual string Name { get; set; } } }
class
named Car
to the folder:using System; using System.Collections.Generic; namespace QueryRecipes.ExtraLazy { public class Car { public Car() { Accessories=new HashSet<Accessory>(); } public virtual Guid Id { get; protected set; } public virtual string Make { get; set; } public virtual string Model { get; set; } public virtual ISet<Accessory> Accessories { get; set; } } }
Car.hbm.xml
to the folder:<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="QueryRecipes"
namespace="QueryRecipes.ExtraLazy">
<class name="Car">
<id name="Id">
<generator class="guid.comb" />
</id>
<property name="Make" />
<property name="Model" />
<set name="Accessories" table="CarAccessories" lazy="extra" cascade="all">
<key column="CarId" foreign-key=""/>
<composite-element class="Accessory">
<property name="Name"/>
</composite-element>
</set>
</class>
</hibernate-mapping>
class
named Recipe
to the folder, having the code:using System; using NH4CookbookHelpers.Queries; using NHibernate; using NHibernate.Cfg; namespace QueryRecipes.ExtraLazy { public class Recipe : QueryRecipe { private Guid _carId;private int _firstAccessoryId; protected override void Configure( Configuration nhConfig) { nhConfig.AddResource( "QueryRecipes.ExtraLazy.Car.hbm.xml", GetType().Assembly); } } }
protected override void AddData( ISessionFactory sessionFactory) { using (var session = sessionFactory.OpenSession()) { using (var tx = session.BeginTransaction()) { var car = new Car { Make = "SAAB", Model = "9-5" }; for (var i = 0; i < 100; i++) { var accessory = new Accessory { Name = "Accessory" + i }; car.Accessories.Add(accessory); } session.Save(car); _carId = car.Id; _firstAccessoryId = car.Accessories.First().Id; tx.Commit(); } } }
protected override void Run(ISession session) { //Get the car var car = session.Get<Car>(_carId); //And one of the accessories var accessory = session.Get<Accessory>(_firstAccessoryId); Console.WriteLine("Accessory count: {0}", car.Accessories.Count); Console.WriteLine("Car has accessory {0}: {1}", accessory.Name, car.Accessories.Contains(accessory)); }
ExtraLazy
recipe.In this mapping, the special addition is that we have specified the accessories collections laziness to be extra. This means that the accessory data isn't loaded from the database unless it's really, really needed. Just asking for the number of accessories on the car with car.Accessories.Count
is not such an occasion. All that's really needed is to execute a COUNT
query, and that's exactly what happens. The query log shows:
SELECT count(AccessoryId) FROM CarAccessories WHERE CarId=1
The next query is triggered by the Contains
method, which can also be easily translated to a SQL query:
SELECT 1 FROM CarAccessories WHERE CarId=1 AND AccessoryId=1
The extra lazy collections added functionality is limited to Count
and Contains
. Any other call to the collection, such as Accessories.Any()
will trigger a full load of the related data.
So, shouldn't we then always use lazy="extra"
, since it seems to be fool proof? No, the risk is that we need both the Count
and all the items. If Count
is executed before enumerating the data, we will have executed the COUNT query unnecessarily. Extra laziness is a convenient feature in certain scenarios, but it's usually better to code more explicit functions. If you only need to count, create a query that does just that.
3.143.17.27