0

设置

映射

我有一个 MyItem 类,它有一个由 MyDetail 项组成的延迟加载集合:

[Serializable]
class MyItem : IComparable, ICloneable
{
  public virtual int ID { get; set; }
}

[Serializable]
class MyDetail : ICloneable
{
  public virtual int ID { get; set; }
  public virtual MyItem EnclosingItem { get; set; }
}

void MyItemMap(IClassMapper<MyItem> ca)
{
  ca.Lazy(false);
  ca.Cache(cm => { cm.Usage(CacheUsage.NonstrictReadWrite); cm.Region(cacheRegion); });
  ca.Id(x => x.ID, cm => { cm.Generator(Generators.Native); });
  ca.Discriminator(cm => { cm.Column("Type"); cm.Type(NHibernateUtil.String); });
  ca.Bag(x => x.Details, cm =>
  {
    cm.Key(k => k.Column("ItemID"));
    cm.Inverse(true);
    cm.Type<DetailFactory>();
    cm.Cascade(Cascade.All | Cascade.DeleteOrphans);
    cm.Fetch(CollectionFetchMode.Select);
    cm.Lazy(CollectionLazy.Lazy);
    cm.Cache(m => { m.Usage(CacheUsage.NonstrictReadWrite); });
  } cr => cr.OneToMany());
}

void MyDetailMap(IClassMapper<MyDetail> ca)
{
  ca.Lazy(true);
  ca.Cache(cm => { cm.Usage(CacheUsage.NonstrictReadWrite); cm.Region(cacheRegion); });
  ca.Id(x => x.ID, cm => { cm.Generator(Generators.Native); });
  ca.ManyToOne(x => x.EnclosingItem, cm => { cm.Column("ItemID"); cm.NotNullable(true); cm.Fetch(FetchKind.Select); cm.Lazy(LazyRelation.Proxy); });
}

该系列

加载关联的 MyDetail 项的类是 PersistentGenericBag:

class DetailList : PersistentGenericBag<MyDetail>
{
}

采集工厂

这是通过自定义集合工厂发生的:

class DetailFactory : IUserCollectionType
{
  public IPersistentCollection Instantiate(ISessionImplementor session, ICollectionPersister persister)
  {
    return new DetailList(session);
  }

  public IPersistentCollection Wrap(ISessionImplementor session, object collection)
  {
    return new DetailList(session, (IList<MyDetail>)collection);
  }
}

问题

假设我已经加载了一个 MyItem 并且我至少请求了一次详细信息,所以我的二级缓存 (SLC) 包含所有这些内容。还假设我只积极使用 1 个细节,而实际上有 100 个细节连接到 MyItem。这意味着,每当我从 SLC 访问 MyItem 并访问我感兴趣的一个细节时,整个集合都会实现:

// Get item coming from SLC
MyItem myItem = nhSession.Get(id);

// Access detail, materializing all MyDetails from SLC
MyDetail myDetail = myItem.Details["Interesting"];

// Normally, this last line will invoke the default NH proxy
// which then materializes the Details collections, fetching
// all details belonging to myItem from the database, or from
// the SLC for that matter.
//
// To cater for my scenario, I've got access to an interceptor
// to do my own processing (the details to how this is hooked
// up are not very relevant). This interceptor should somehow
// be able to, instead of initializing the collection, return
// the result from the SLC rightaway. This would prevent
// the materialization of possibly large collections.

解决优化问题

现在我想通过绕过集合加载来优化这个场景,并直接查询 SLC 以了解该细节的存在。请注意,我的问题不是从代码中截取实际的详细信息调用,而是干预以何种方式返回的内容。

我正在使用 SysCache,所以理论上我可以读出 ASP.NET 缓存,但由于缓存键是 NH 的实现细节,我不确定这是一个好主意。

有人给我指点吗?

4

1 回答 1

0

我已经完成了这项工作,但最终结果是我的应用程序现在甚至需要更长的时间。由于这对我来说还没有意义,我仍在调查,但无论如何我都会发布代码。

首先,我连接了一个听众。每次保存 MyDetail 时,它都会插入一个额外的映射。这意味着,除了在 SLC 中有 MyDetail 的 ID 之外,我还可以通过 EnclosureItem.ID 和 MyDetail.Name 来查找它们。

class PostLoadListener : IPostLoadEventListener
{
  public void OnPostLoad(PostLoadEvent @event)
  {
    // Only act when MyDetail is loaded
    var detail = @event.Entity as MyDetail;
    if (detail == null) return;

    ISessionImplementor session = @event.Session;
    var persister = @event.Persister;

    // Still tak into account caching settings (copied over these checks from NH)
    if (persister.HasCache == false || ((session.CacheMode & CacheMode.Put) != CacheMode.Put)) return;

    // Create the mapping by combining the collection ID and the detail
    // name, which together are being mapped to the actual detail ID
    var id = detail.EnclosingItem.ID + "#" + detail.Name;
    var cacheKey = new CacheKey(id, NHibernateUtil.String, PersistentContentDetailsList.IdMapRole, session.EntityMode, session.Factory);

    // Cache the mapping [detail.EnclosingItem.ID#detail.Name] => [detail.ID]
    persister.Cache.Put(cacheKey, detail.ID, session.Timestamp, null, null, true);
  }
}

然后,我确保我的 DetailList 在适用时进行上述查找:

class DetailList : PersistentGenericBag<MyDetail>
{
  public MyDetail this[string name]
  {
    get { MyDetail detail; TryGetValue(name, out detail); return detail; }
    set { /* */  }
  }

  private IList<ContentDetail> List { get { return this; }
  }

  /*
  * If the collection was already initialized, it doesn't make sense
  * to reach for the SLC, so skip it in that case. If it wasn't,
  * then try to fetch from the SLC. If the detail wasn't found there,
  * initialize the collection by triggering the enumerator and
  * subsequently find the detail by name.
  */
  public bool TryGetValue(string name, out MyDetail value)
  {
    return
      (value = WasInitialized
                 ? List.FirstOrDefault(i => i.Name == name)
                 : (GetCachedDetail(name) ?? List.FirstOrDefault(i => i.Name == name))
      ) != null;
  }

  /*
  * GetCachedDetail uses the collection identifier, combines it with
  * the detail name, and tries to find that mapping in the SLC.
  * If it exists, the ID is returned. So basically, you get to know
  * which MyDetail to fetch from the SLC when you know its EnclosingItem
  * and its name.
  */
  private ContentDetail GetCachedDetail(string name)
  {
    var mapId = Key + "#" + name;
    var persister = Session.Factory.GetEntityPersister(typeof(ContentDetail).FullName);
    var key = new CacheKey(mapId, NHibernateUtil.String, IdMapRole, Session.EntityMode, Session.Factory);
    var cacheEntry = persister.Cache.Get(key, Session.Timestamp);

    // This last step might be optimized by only executing the relevant 
    // loading statements of the Session.Load invocation chain
    return cacheEntry != null ? ((ISession)Session).Load<MyDetail>((int)cacheEntry) : null;
  }
}
于 2013-04-05T08:43:41.583 回答