Hoping for your assistance. I have simple master-child sets ("player" and "playerChildObject"). Fetching "players" also fetches their linked children.
SysCache caches players, however doesn't cache children.
Here are the objects (both set with Cache.ReadWrite()):
Entities:
public class Player
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<PlayerChildObject> PlayerChildObjects { get; set; }
public Player()
{
PlayerChildObjects = new List<PlayerChildObject>();
}
}
public class PlayerChildObject
{
public virtual int Id { get; protected set; }
public virtual Player Player { get; set; }
}
Mapping:
public class PlayerMap : ClassMap<Player>
{
public PlayerMap()
{
Id(x => x.Id).Column("id");
Map(x => x.Name).Column("name");
HasMany(x => x.PlayerChildObjects).KeyColumn("PlayerId")
.Inverse()
.Cascade.All()
.Not.LazyLoad();
Table("accounts");
Cache.ReadWrite();
}
}
public class PlayerChildObjectMap : ClassMap<PlayerChildObject>
{
public PlayerChildObjectMap()
{
Id(x => x.Id).Column("id");
References<Player>(x => x.Player, "playerId");
Table("playerChildObjects");
Cache.ReadWrite();
}
}
Session factory also with "UseQueryCache" and "UseSecondLevelCache":
Fluently.Configure()
.Database(MySQLConfiguration.Standard.ConnectionString(cs => ...))
.Cache(c => c.UseQueryCache().UseSecondLevelCache()
.ProviderClass(typeof (NHibernate.Caches.SysCache.SysCacheProvider).AssemblyQualifiedName))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<PlayerChildObjectMap>())
.BuildSessionFactory();
And the query itself with SetCacheable(true).SetCacheMode(CacheMode.Normal):
using (var session = SessionFactory.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
players = session.CreateCriteria(typeof(Player))
.SetCacheable(true)
.SetCacheMode(CacheMode.Normal)
.List<Player>();
transaction.Commit();
}
}
However, profiler shows that Players are cached (called only the 1st time executing players fetch code), while the same PlayerChildObjects are retrieved each time by PlayerID.
So, first request gives N+1 DB calls (list of players + child objects by each player), all consecutive requests execute N calls (child objects by each player).
What am I missing? How to make SysCache caching children as well?
SysCache version: 3.1.0.4000
I would say, there are two issues. Firstly, even the one-to-many
(HasMany
) does have cache setting:
HasMany(x => x.PlayerChildObjects).KeyColumn("PlayerId")
...
.Cache.IncludeAll() // or .IncludeNonLazy, .CustomInclude("customInclude")
.ReadOnly() // or .NonStrictReadWrite(), .ReadWrite(), .Transactional(),
// .CustomUsage("customUsage")
.Region("regionName");
Check more abut HasMany mapping here (article by Adam Bar about mapping by code, but at the end is fluent one)
The doc 19.2.1. Cache mappings
Secondly, we should use batch-fetching:
public PlayerMap()
{
BatchSize(25);
HasMany(x => x.PlayerChildObjects).KeyColumn("PlayerId")
...
.BatchSize(25)
...
public PlayerChildObjectMap()
{
BatchSize(25);
...
Read more about it in doc 19.1.5. Using batch fetching, or here: