0

NHibernate在ISession.Refresh会话缓存上为同一数据库记录生成两个实例。

为什么?

该问题似乎与使用“KeyReference”(“key-many-to-one”)的“Composite Id”有关。这种情况发生在 NHibernate 的“2.1.2.4000”和“3.2.0.4000”版本中。

如何在不放弃“复合 id”的情况下解决这个问题?

以下测试应该通过,但它没有:

/// <summary>
/// Test for <see cref="ISession.Refresh(object)"/>. After a call to 
/// <c>Refresh()</c>On 'ISession.Refresh' NHibernate GENERATES two 
/// instances for the the same database record on the Session Cache. WHY?.
/// The problem seems to be related to 'Composit Id' using 'KeyReference'.
/// ('key-many-to-one'). The situation occurs in versions '2.1.2.4000' and 
/// '3.2.0.4000' of NHibernate. This test should pass, but does not pass!
/// <para>There is another test (<see cref="SofPOC.Questions.NHRefresh.NHRefreshTest"/>) 
/// that makes the 'Refresh' in several scenarios where this problem does 
/// not occur.
/// </para>
/// </summary>
/// <remarks>
/// Tables:
/// <code>
/// MasterCategory:
/// Id|Description
/// --+-----------
/// 1 |Cat_1      
/// 2 |Cat_2      
/// 
/// Master:
/// IdA|IdB|Description  |MasterCategoryId
/// ---+---+-------------+----------------
/// 1  |1  |MASTER_DESC_1|1               
/// 2  |2  |MASTER_DESC_2|2               
/// 
/// Detail:
/// MasterIdA|MasterIdB|SubId|Description    
/// ---------+---------+-----+---------------
/// 1        |1        |1    |DETAIL_DESC_1_1
/// 1        |1        |2    |DETAIL_DESC_1_2
/// 1        |1        |3    |DETAIL_DESC_1_3
/// 2        |2        |4    |DETAIL_DESC_2_1
/// 2        |2        |5    |DETAIL_DESC_2_2        
/// </code>
/// </remarks>
[TestFixture]
public class NHRefreshCpIdTest
{
    private static readonly ILog LOG = LogManager.GetLogger(typeof(NHRefreshCpIdTest));

    public ISessionFactory SessionFactory { get; set; }

    [TestFixtureSetUp]
    public void TestFixtureSetUp()
    {
        File.Copy("./Questions/NHRefreshCpId/NHRefreshCpIdTest.db", "./Questions/NHRefreshCpId/NHRefreshCpIdTestEdited.db", true);

        Configuration config = new Configuration();
        config.Properties[NHibernate.Cfg.Environment.ConnectionProvider] = "NHibernate.Connection.DriverConnectionProvider";
        config.Properties[NHibernate.Cfg.Environment.ConnectionDriver] = "NHibernate.Driver.SQLite20Driver";
        config.Properties[NHibernate.Cfg.Environment.ConnectionString] = "Data Source=|DataDirectory|./Questions/NHRefreshCpId/NHRefreshCpIdTestEdited.db;Version=3;FailIfMissing=True;";
        config.Properties[NHibernate.Cfg.Environment.Dialect] = "NHibernate.Dialect.SQLiteDialect";
        //config.Properties[NHibernate.Cfg.Environment.By]
        config.Properties[NHibernate.Cfg.Environment.ShowSql] = "true";
        config.Properties[NHibernate.Cfg.Environment.FormatSql] = "true";
        FluentConfiguration fConfigure = Fluently.Configure(config);
#if NH2
        config.Properties[NHibernate.Cfg.Environment.ProxyFactoryFactoryClass] = "NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle";
#endif
        fConfigure.Mappings(m =>
            m.FluentMappings.Add(typeof(MasterCategoryEntMap)));
        fConfigure.Mappings(m =>
            m.FluentMappings.Add(typeof(MasterEntMap)));
        fConfigure.Mappings(m =>
            m.FluentMappings.Add(typeof(DetailEntMap)));
        config = fConfigure.BuildConfiguration();

        this.SessionFactory = config.BuildSessionFactory();
    }

    [Test]
    public void Test()
    {
        ISession ss = null;


        #region teste
        ss = this.SessionFactory.OpenSession();
        try
        {
            MasterEnt master1BeforeRefresh = ss.Get<MasterEnt>(new MasterCpId(1, 1));
            MasterEnt master2BeforeRefresh = ss.Get<MasterEnt>(new MasterCpId(2, 2));
            MasterCategoryEnt masterCat1BeforeRefresh = master1BeforeRefresh.MasterCategory;
            MasterCategoryEnt masterCat2BeforeRefresh = ss.Get<MasterCategoryEnt>(2);

            #region persist data using NHibernate
            ITransaction tx = ss.BeginTransaction();
            ss.Flush();
            tx.Commit();
            #endregion

            #region change data out of NHibernate
            IDbCommand command = ss.Connection.CreateCommand();
            command.CommandText = "UPDATE Master SET MasterCategoryId = 2 WHERE IdA = 1 and IdB = 1";
            IDbTransaction dbTx = ss.Connection.BeginTransaction();
            command.ExecuteNonQuery();
            dbTx.Commit();
            #endregion

            LOG.Debug("JUST BEFORE 'Refresh'");

            ss.Refresh(master1BeforeRefresh);
            MasterEnt master1AfterRefresh = ss.Get<MasterEnt>(new MasterCpId(1, 1));
            DetailEnt detail4AfterRefresh = ss.Get<DetailEnt>(new DetailCpId(master1AfterRefresh, 4));
            MasterCategoryEnt masterCat1AfterRefresh = ss.Get<MasterCategoryEnt>(1);
            MasterCategoryEnt masterCat2AfterRefresh = master1AfterRefresh.MasterCategory;

            //Here is the test
            Assert.AreEqual(1, masterCat1BeforeRefresh.Id);
            Assert.AreEqual(1, masterCat1AfterRefresh.Id);
            Assert.AreEqual(2, masterCat2BeforeRefresh.Id);
            Assert.AreEqual(2, masterCat2AfterRefresh.Id);
            Assert.AreSame(master1BeforeRefresh, master1AfterRefresh);
            Assert.AreSame(masterCat1BeforeRefresh, masterCat1AfterRefresh);
            Assert.AreSame(masterCat2BeforeRefresh, masterCat2AfterRefresh);
        }
        finally
        {
            ss.Close();
        }
        #endregion
    }
}

我正在使用:

  • NHibernate 2.1.2
  • NHibernate 3.2.0
  • System.Data.SQLite 1.0.80.0

完整的源代码在这里:NHibernateRefresh.7z

笔记:

  • 在打开解决方案(“.\Src\SofPOC.2010.sln”)之前,运行“.\Dependencies\setup.bat”来加载依赖项。
  • 有关依赖项的说明,请参阅“.\readme.txt”和“.\dependencies\readme.txt”。
4

1 回答 1

1

原因:

在为“父”调用“DefaultRefreshEventListener.OnRefresh”时,在“父”和“子”之间发生“带连接加载”,首先加载“子”,然后再加载“父”,但此时暂时从会话中删除“父级”的那一刻,“子级”通过代理引用“父级”。尽管“parent1BeforeRefresh”和“parent1AfterRefresh”是不同的实例,但“parent1AfterRefresh”是指向“parent1BeforeRefresh”的代理。'child1BeforeRefresh' 和 'child1AfterRefresh' 中的问题更为严重,因为这些实例完全不相关,并且都在会话缓存中。

溶液 (NH-3253)

如果NH-3253尚未被接受和修复,DefaultRefreshEventListener请自行替换:

2.1.x 版:

/// <summary> 
/// Defines the default refresh event listener used by hibernate for refreshing entities
/// in response to generated refresh events. 
/// </summary>
[Serializable]
public class DefaultRefreshEventListener : IRefreshEventListener
{
    private static readonly ILog log = LogManager.GetLogger(typeof(DefaultRefreshEventListener));

    public virtual void OnRefresh(RefreshEvent @event)
    {
        OnRefresh(@event, IdentityMap.Instantiate(10));
    }

    public virtual void OnRefresh(RefreshEvent @event, IDictionary refreshedAlready)
    {
        IEventSource source = @event.Session;

        if (source.PersistenceContext.ReassociateIfUninitializedProxy(@event.Entity))
            return;

        object obj = source.PersistenceContext.UnproxyAndReassociate(@event.Entity);

        if (refreshedAlready.Contains(obj))
        {
            log.Debug("already refreshed");
            return;
        }

        EntityEntry e = source.PersistenceContext.GetEntry(obj);
        IEntityPersister persister;
        object id;

        if (e == null)
        {
            persister = source.GetEntityPersister(null, obj); //refresh() does not pass an entityName
            id = persister.GetIdentifier(obj, source.EntityMode);
            if (log.IsDebugEnabled)
            {
                log.Debug("refreshing transient " + MessageHelper.InfoString(persister, id, source.Factory));
            }
            EntityKey key = new EntityKey(id, persister, source.EntityMode);
            if (source.PersistenceContext.GetEntry(key) != null)
            {
                throw new PersistentObjectException("attempted to refresh transient instance when persistent instance was already associated with the Session: " + 
                    MessageHelper.InfoString(persister, id, source.Factory));
            }
        }
        else
        {
            if (log.IsDebugEnabled)
            {
                log.Debug("refreshing " + MessageHelper.InfoString(e.Persister, e.Id, source.Factory));
            }
            if (!e.ExistsInDatabase)
            {
                throw new HibernateException("this instance does not yet exist as a row in the database");
            }

            persister = e.Persister;
            id = e.Id;
        }

        // cascade the refresh prior to refreshing this entity
        refreshedAlready[obj] = obj;
        new Cascade(CascadingAction.Refresh, CascadePoint.BeforeRefresh, source).CascadeOn(persister, obj, refreshedAlready);

        if (e != null)
        {
            EntityKey key = new EntityKey(id, persister, source.EntityMode);
            source.PersistenceContext.RemoveEntity(key);
            if (persister.HasCollections)
                new EvictVisitor(source).Process(obj, persister);
        }

        if (persister.HasCache)
        {
            CacheKey ck = new CacheKey(id, persister.IdentifierType, persister.RootEntityName, source.EntityMode, source.Factory);
            persister.Cache.Remove(ck);
        }

        EvictCachedCollections(persister, id, source.Factory);

        // NH Different behavior : NH-1601
        // At this point the entity need the real refresh, all elementes of collections are Refreshed,
        // the collection state was evicted, but the PersistentCollection (in the entity state)
        // is associated with a possible previous session.
        new WrapVisitor(source).Process(obj, persister);

        // NH-3253: Forcing simple load to prevent the redundante instance 
        //on session:
        //Ocurre when:
        // -there is a 'one-to-many' 'Parent-Child' relationship;
        // -AND the 'Parent' 'many-to-one' association is ' fetch="select" ';
        // -AND 'Child' is using a 'composite-id' and 'key-many-to-one' to the
        //'Parent'.
        object result;
        if (this.IsReferencedByCompositeId(persister))
            result = persister.Load(id, obj, @event.LockMode, source);

        string previousFetchProfile = source.FetchProfile;
        source.FetchProfile = "refresh";
        result = persister.Load(id, obj, @event.LockMode, source);
        source.FetchProfile = previousFetchProfile;

        // NH Different behavior : we are ignoring transient entities without throw any kind of exception 
        // because a transient entity is "self refreshed"
        if (!ForeignKeys.IsTransient(persister.EntityName, obj, result == null, @event.Session))
            UnresolvableObjectException.ThrowIfNull(result, id, persister.EntityName);
    }

    private Dictionary<EntityMetamodel, bool> IsReferencedByCompositeIdCache = new Dictionary<EntityMetamodel, bool>();
    /// <summary>
    /// Returns <c>true</c> if the entity in <paramref name="persister"/> 
    /// ('root') has a direct or indirect association with another 
    /// entity that is associated back to 'root' through a 'composit-id' and 
    /// 'key-many-to-one'.
    /// </summary>
    /// <param name="persister"></param>
    /// <returns></returns>
    private bool IsReferencedByCompositeId(IEntityPersister persister)
    {
        try
        {
            bool result = false;
            if (IsReferencedByCompositeIdCache.ContainsKey(persister.EntityMetamodel))
            {
                result = this.IsReferencedByCompositeIdCache[persister.EntityMetamodel];
            }
            else
            {
                EntityMetamodel em =
                    this.GetReferrerByCompositeId(
                        persister.EntityMetamodel,
                        persister.EntityMetamodel,
                        false,
                        new Iesi.Collections.Generic.HashedSet<EntityMetamodel>());
                if (em == null)
                    result = false;
                else
                    result = true;

                lock (this)
                {
                    this.IsReferencedByCompositeIdCache[persister.EntityMetamodel] = result;
                }
            }

            return result;
        }
        catch (Exception ex)
        {
            log.Error("Unespected ERROR!", ex);
            throw;
        }
    }

    /// <summary>
    /// Recursive Helper for <see cref="IsReferencedByCompositeId(IEntityPersister)"/>.
    /// </summary>
    /// <param name="rootEM"></param>
    /// <param name="nestedEM"></param>
    /// <param name="neestedIsCompositeId"></param>
    /// <param name="visitedList"></param>
    /// <returns></returns>
    private EntityMetamodel GetReferrerByCompositeId(
        EntityMetamodel rootEM,
        EntityMetamodel nestedEM,
        bool neestedIsCompositeId,
        ICollection<EntityMetamodel> visitedList)
    {
        EntityMetamodel emResult = null;

        if (visitedList.Contains(nestedEM))
        {
            return emResult;
        }
        else
        {
            visitedList.Add(nestedEM);

            ISessionFactoryImplementor sessionImplementor = rootEM.SessionFactory;

            if (nestedEM.IdentifierProperty.Type is IAbstractComponentType)
            {
                IAbstractComponentType componentType = (IAbstractComponentType)nestedEM.IdentifierProperty.Type;
                for (int i = 0; i < componentType.Subtypes.Length; i++)
                {
                    IType subType = componentType.Subtypes[i];
                    if (!subType.IsAnyType
                        && subType.IsAssociationType
                        && subType is IAssociationType)
                    {
                        IAssociationType associationType = (IAssociationType)subType;
                        string associatedEntityName = null;
                        try
                        {
                            //for 'Collection Types', sometimes 'Element Type' is not an 'Entity Type'
                            associatedEntityName = associationType.GetAssociatedEntityName(sessionImplementor);
                        }
                        catch (MappingException me)
                        {
                            //I think it will never happen because a 
                            //"Composit Id" can not have a property that 
                            //uses 'NHibernate.Type.CollectionType'. 
                            //But just in case ...
                            if (log.IsDebugEnabled)
                                log.Debug("Can not perform 'GetAssociatedEntityName'. " +
                                    "Considering it is not an entity type: '" +
                                    nestedEM.IdentifierProperty.Name + "." +
                                    componentType.PropertyNames[i] + "'"
                                    , me);
                        }
                        if (associatedEntityName != null)
                        {
                            IEntityPersister persisterNextNested = sessionImplementor.GetEntityPersister(associatedEntityName);
                            if (rootEM == persisterNextNested.EntityMetamodel)
                            {
                                emResult = nestedEM;
                                return emResult;
                            }
                            else
                            {
                                emResult = this.GetReferrerByCompositeId(
                                    rootEM,
                                    persisterNextNested.EntityMetamodel,
                                    true,
                                    visitedList);

                                if (emResult != null)
                                    return emResult;
                            }
                        }
                    }
                }
            }
            for (int i = 0; i < nestedEM.Properties.Length; i++)
            {
                StandardProperty property = nestedEM.Properties[i];

                if (!property.Type.IsAnyType
                    && property.Type.IsAssociationType
                    && property.Type is IAssociationType)
                {
                    IAssociationType associationType = (IAssociationType)property.Type;
                    string associatedEntityName = null;
                    try
                    {
                        //for 'Collection Types', sometimes 'Element Type' is not an 'Entity Type'
                        associatedEntityName = associationType.GetAssociatedEntityName(sessionImplementor);
                    }
                    catch (MappingException me)
                    {
                        if (log.IsDebugEnabled)
                            log.Debug("Can not perform 'GetAssociatedEntityName'. " +
                                    "Considering it is not an entity type: '" +
                                    nestedEM.EntityType.Name + "." +
                                    nestedEM.PropertyNames[i] + "'",
                                me);
                    }
                    if (associatedEntityName != null)
                    {
                        IEntityPersister persisterNextNested = sessionImplementor.GetEntityPersister(associatedEntityName);
                        emResult = this.GetReferrerByCompositeId(
                            rootEM,
                            persisterNextNested.EntityMetamodel,
                            false,
                            visitedList);
                        if (emResult != null)
                            return emResult;
                    }
                }
            }
        }

        return null;
    }

    // Evict collections from the factory-level cache
    private void EvictCachedCollections(IEntityPersister persister, object id, ISessionFactoryImplementor factory)
    {
        EvictCachedCollections(persister.PropertyTypes, id, factory);
    }

    private void EvictCachedCollections(IType[] types, object id, ISessionFactoryImplementor factory)
    {
        for (int i = 0; i < types.Length; i++)
        {
            if (types[i].IsCollectionType)
            {
                factory.EvictCollection(((CollectionType)types[i]).Role, id);
            }
            else if (types[i].IsComponentType)
            {
                IAbstractComponentType actype = (IAbstractComponentType)types[i];
                EvictCachedCollections(actype.Subtypes, id, factory);
            }
        }
    }
}

3.3.x 版:

/// <summary> 
/// Defines the default refresh event listener used by hibernate for refreshing entities
/// in response to generated refresh events. 
/// </summary>
[Serializable]
public class DefaultRefreshEventListener : IRefreshEventListener
{
    private static readonly IInternalLogger log = LoggerProvider.LoggerFor(typeof(DefaultRefreshEventListener));

    public virtual void OnRefresh(RefreshEvent @event)
    {
        OnRefresh(@event, IdentityMap.Instantiate(10));
    }

    public virtual void OnRefresh(RefreshEvent @event, IDictionary refreshedAlready)
    {
        IEventSource source = @event.Session;

        bool isTransient = !source.Contains(@event.Entity);
        if (source.PersistenceContext.ReassociateIfUninitializedProxy(@event.Entity))
        {
            if (isTransient)
                source.SetReadOnly(@event.Entity, source.DefaultReadOnly);
            return;
        }

        object obj = source.PersistenceContext.UnproxyAndReassociate(@event.Entity);

        if (refreshedAlready.Contains(obj))
        {
            log.Debug("already refreshed");
            return;
        }

        EntityEntry e = source.PersistenceContext.GetEntry(obj);
        IEntityPersister persister;
        object id;

        if (e == null)
        {
            persister = source.GetEntityPersister(null, obj); //refresh() does not pass an entityName
            id = persister.GetIdentifier(obj, source.EntityMode);
            if (log.IsDebugEnabled)
            {
                log.Debug("refreshing transient " + MessageHelper.InfoString(persister, id, source.Factory));
            }
            EntityKey key = new EntityKey(id, persister, source.EntityMode);
            if (source.PersistenceContext.GetEntry(key) != null)
            {
                throw new PersistentObjectException("attempted to refresh transient instance when persistent instance was already associated with the Session: " + 
                    MessageHelper.InfoString(persister, id, source.Factory));
            }
        }
        else
        {
            if (log.IsDebugEnabled)
            {
                log.Debug("refreshing " + MessageHelper.InfoString(e.Persister, e.Id, source.Factory));
            }
            if (!e.ExistsInDatabase)
            {
                throw new HibernateException("this instance does not yet exist as a row in the database");
            }

            persister = e.Persister;
            id = e.Id;
        }

        // cascade the refresh prior to refreshing this entity
        refreshedAlready[obj] = obj;
        new Cascade(CascadingAction.Refresh, CascadePoint.BeforeRefresh, source).CascadeOn(persister, obj, refreshedAlready);

        if (e != null)
        {
            EntityKey key = new EntityKey(id, persister, source.EntityMode);
            source.PersistenceContext.RemoveEntity(key);
            if (persister.HasCollections)
                new EvictVisitor(source).Process(obj, persister);
        }

        if (persister.HasCache)
        {
            CacheKey ck = new CacheKey(id, persister.IdentifierType, persister.RootEntityName, source.EntityMode, source.Factory);
            persister.Cache.Remove(ck);
        }

        EvictCachedCollections(persister, id, source.Factory);

        // NH Different behavior : NH-1601
        // At this point the entity need the real refresh, all elementes of collections are Refreshed,
        // the collection state was evicted, but the PersistentCollection (in the entity state)
        // is associated with a possible previous session.
        new WrapVisitor(source).Process(obj, persister);

        // NH-3253: Forcing simple load to prevent the redundante instance 
        //on session:
        //Ocurre when:
        // -there is a 'one-to-many' 'Parent-Child' relationship;
        // -AND the 'Parent' 'many-to-one' association is ' fetch="select" ';
        // -AND 'Child' is using a 'composite-id' and 'key-many-to-one' to the
        //'Parent'.
        object result;
        if (this.IsReferencedByCompositeId(persister))
            result = persister.Load(id, obj, @event.LockMode, source);

        string previousFetchProfile = source.FetchProfile;
        source.FetchProfile = "refresh";
        result = persister.Load(id, obj, @event.LockMode, source);

        if (result != null)
            if (!persister.IsMutable)
                source.SetReadOnly(result, true);
            else
                source.SetReadOnly(result, (e == null ? source.DefaultReadOnly : e.IsReadOnly));

        source.FetchProfile = previousFetchProfile;

        // NH Different behavior : we are ignoring transient entities without throw any kind of exception
        // because a transient entity is "self refreshed"
        if (!ForeignKeys.IsTransient(persister.EntityName, obj, result == null, @event.Session))
            UnresolvableObjectException.ThrowIfNull(result, id, persister.EntityName);
    }

    private Dictionary<EntityMetamodel, bool> IsReferencedByCompositeIdCache = new Dictionary<EntityMetamodel, bool>();
    /// <summary>
    /// Returns <c>true</c> if the entity in <paramref name="persister"/> 
    /// ('root') has a direct or indirect association with another 
    /// entity that is associated back to 'root' through a 'composit-id' and 
    /// 'key-many-to-one'.
    /// </summary>
    /// <param name="persister"></param>
    /// <returns></returns>
    private bool IsReferencedByCompositeId(IEntityPersister persister)
    {
        try
        {
            bool result = false;
            if (IsReferencedByCompositeIdCache.ContainsKey(persister.EntityMetamodel))
            {
                result = this.IsReferencedByCompositeIdCache[persister.EntityMetamodel];
            }
            else
            {
                EntityMetamodel em =
                    this.GetReferrerByCompositeId(
                        persister.EntityMetamodel,
                        persister.EntityMetamodel,
                        false,
                        new Iesi.Collections.Generic.HashedSet<EntityMetamodel>());
                if (em == null)
                    result = false;
                else
                    result = true;

                lock (this)
                {
                    this.IsReferencedByCompositeIdCache[persister.EntityMetamodel] = result;
                }
            }

            return result;
        }
        catch (Exception ex)
        {
            log.Error("Unespected ERROR!", ex);
            throw;
        }
    }

    /// <summary>
    /// Recursive Helper for <see cref="IsReferencedByCompositeId(IEntityPersister)"/>.
    /// </summary>
    /// <param name="rootEM"></param>
    /// <param name="nestedEM"></param>
    /// <param name="neestedIsCompositeId"></param>
    /// <param name="visitedList"></param>
    /// <returns></returns>
    private EntityMetamodel GetReferrerByCompositeId(
        EntityMetamodel rootEM,
        EntityMetamodel nestedEM,
        bool neestedIsCompositeId,
        ICollection<EntityMetamodel> visitedList)
    {
        EntityMetamodel emResult = null;

        if (visitedList.Contains(nestedEM))
        {
            return emResult;
        }
        else
        {
            visitedList.Add(nestedEM);

            ISessionFactoryImplementor sessionImplementor = rootEM.SessionFactory;

            if (nestedEM.IdentifierProperty.Type is IAbstractComponentType)
            {
                IAbstractComponentType componentType = (IAbstractComponentType)nestedEM.IdentifierProperty.Type;
                for (int i = 0; i < componentType.Subtypes.Length; i++)
                {
                    IType subType = componentType.Subtypes[i];
                    if (!subType.IsAnyType
                        && subType.IsAssociationType
                        && subType is IAssociationType)
                    {
                        IAssociationType associationType = (IAssociationType)subType;
                        string associatedEntityName = null;
                        try
                        {
                            //for 'Collection Types', sometimes 'Element Type' is not an 'Entity Type'
                            associatedEntityName = associationType.GetAssociatedEntityName(sessionImplementor);
                        }
                        catch (MappingException me)
                        {
                            //I think it will never happen because a 
                            //"Composit Id" can not have a property that 
                            //uses 'NHibernate.Type.CollectionType'. 
                            //But just in case ...
                            if (log.IsDebugEnabled)
                                log.Debug("Can not perform 'GetAssociatedEntityName'. " +
                                    "Considering it is not an entity type: '" +
                                    nestedEM.IdentifierProperty.Name + "." +
                                    componentType.PropertyNames[i] + "'"
                                    , me);
                        }
                        if (associatedEntityName != null)
                        {
                            IEntityPersister persisterNextNested = sessionImplementor.GetEntityPersister(associatedEntityName);
                            if (rootEM == persisterNextNested.EntityMetamodel)
                            {
                                emResult = nestedEM;
                                return emResult;
                            }
                            else
                            {
                                emResult = this.GetReferrerByCompositeId(
                                    rootEM,
                                    persisterNextNested.EntityMetamodel,
                                    true,
                                    visitedList);

                                if (emResult != null)
                                    return emResult;
                            }
                        }
                    }
                }
            }
            for (int i = 0; i < nestedEM.Properties.Length; i++)
            {
                StandardProperty property = nestedEM.Properties[i];

                if (!property.Type.IsAnyType
                    && property.Type.IsAssociationType
                    && property.Type is IAssociationType)
                {
                    IAssociationType associationType = (IAssociationType)property.Type;
                    string associatedEntityName = null;
                    try
                    {
                        //for 'Collection Types', sometimes 'Element Type' is not an 'Entity Type'
                        associatedEntityName = associationType.GetAssociatedEntityName(sessionImplementor);
                    }
                    catch (MappingException me)
                    {
                        if (log.IsDebugEnabled)
                            log.Debug("Can not perform 'GetAssociatedEntityName'. " +
                                    "Considering it is not an entity type: '" +
                                    nestedEM.EntityType.Name + "." +
                                    nestedEM.PropertyNames[i] + "'",
                                me);
                    }
                    if (associatedEntityName != null)
                    {
                        IEntityPersister persisterNextNested = sessionImplementor.GetEntityPersister(associatedEntityName);
                        emResult = this.GetReferrerByCompositeId(
                            rootEM,
                            persisterNextNested.EntityMetamodel,
                            false,
                            visitedList);
                        if (emResult != null)
                            return emResult;
                    }
                }
            }
        }

        return null;
    }

    // Evict collections from the factory-level cache
    private void EvictCachedCollections(IEntityPersister persister, object id, ISessionFactoryImplementor factory)
    {
        EvictCachedCollections(persister.PropertyTypes, id, factory);
    }

    private void EvictCachedCollections(IType[] types, object id, ISessionFactoryImplementor factory)
    {
        for (int i = 0; i < types.Length; i++)
        {
            if (types[i].IsCollectionType)
            {
                factory.EvictCollection(((CollectionType)types[i]).Role, id);
            }
            else if (types[i].IsComponentType)
            {
                IAbstractComponentType actype = (IAbstractComponentType)types[i];
                EvictCachedCollections(actype.Subtypes, id, factory);
            }
        }
    }
}
于 2012-08-31T21:38:32.727 回答