Почему NHibernate генерирует исключение «GenericADOException: не удалось инициализировать коллекцию» во время ISession.Refresh в этом случае? - PullRequest
7 голосов
/ 02 августа 2011

У меня было странное поведение (по крайней мере, для меня) с ISession.Refresh().

У меня есть объект с загруженной в ленивый набор дочерними объектами и свойство только для чтениякоторый попадает в эту коллекцию, все включено в кэш 2-го уровня.Я использую ISession.Refresh() в длинном сеансе для получения последних данных после фиксации транзакции в БД и получаю следующую ошибку:

NHibernate.Exceptions.GenericADOException : could not initialize a collection: [Test.NUnit.DBTest.TestModel.ParentTestEntity.Children#d4251363-cf88-4684-b65a-9f330107afcf][SQL: SELECT children0_.ParentTestEntity_id as ParentTe4_1_, children0_.Id as Id1_, children0_.Id as Id42_0_, children0_.RowVersion as RowVersion42_0_, children0_.Parent_id as Parent3_42_0_ FROM "ChildTestEntity" children0_ WHERE children0_.ParentTestEntity_id=?]
  ----> System.NullReferenceException : Object reference not set to an instance of an object.
    at NHibernate.Loader.Loader.LoadCollection(ISessionImplementor session, Object id, IType type)
    at NHibernate.Loader.Collection.CollectionLoader.Initialize(Object id, ISessionImplementor session)
    at NHibernate.Persister.Collection.AbstractCollectionPersister.Initialize(Object key, ISessionImplementor session)
    at NHibernate.Event.Default.DefaultInitializeCollectionEventListener.OnInitializeCollection(InitializeCollectionEvent event)
    at NHibernate.Impl.SessionImpl.InitializeCollection(IPersistentCollection collection, Boolean writing)
    at NHibernate.Collection.AbstractPersistentCollection.Initialize(Boolean writing)
    at NHibernate.Collection.AbstractPersistentCollection.ReadSize()
    at NHibernate.Collection.PersistentBag.get_Count()
    DBTest\TestModel\EntiteTestCacheCollectionsParent.cs(25,0): at Test.NUnit.DBTest.TestModel.ParentTestEntity.get_Count()
    at (Object , GetterCallback )
    at NHibernate.Bytecode.Lightweight.AccessOptimizer.GetPropertyValues(Object target)
    at NHibernate.Tuple.Entity.PocoEntityTuplizer.GetPropertyValuesWithOptimizer(Object entity)
    at NHibernate.Tuple.Entity.PocoEntityTuplizer.GetPropertyValues(Object entity)
    at NHibernate.Persister.Entity.AbstractEntityPersister.GetPropertyValues(Object obj, EntityMode entityMode)
    at NHibernate.Event.Default.AbstractVisitor.Process(Object obj, IEntityPersister persister)
    at NHibernate.Event.Default.DefaultRefreshEventListener.OnRefresh(RefreshEvent event, IDictionary refreshedAlready)
    at NHibernate.Event.Default.DefaultRefreshEventListener.OnRefresh(RefreshEvent event)
    at NHibernate.Impl.SessionImpl.FireRefresh(RefreshEvent refreshEvent)
    at NHibernate.Impl.SessionImpl.Refresh(Object obj)
    DBTest\NHibernateBehaviorTests.cs(610,0): at Test.NUnit.DBTest.NHibernateBehaviorTests.Test()
    --NullReferenceException
    at NHibernate.Engine.Loading.CollectionLoadContext.AddCollectionToCache(LoadingCollectionEntry lce, ICollectionPersister persister)
    at NHibernate.Engine.Loading.CollectionLoadContext.EndLoadingCollection(LoadingCollectionEntry lce, ICollectionPersister persister)
    at NHibernate.Engine.Loading.CollectionLoadContext.EndLoadingCollections(ICollectionPersister persister, IList`1 matchedCollectionEntries)
    at NHibernate.Engine.Loading.CollectionLoadContext.EndLoadingCollections(ICollectionPersister persister)
    at NHibernate.Loader.Loader.EndCollectionLoad(Object resultSetId, ISessionImplementor session, ICollectionPersister collectionPersister)
    at NHibernate.Loader.Loader.InitializeEntitiesAndCollections(IList hydratedObjects, Object resultSetId, ISessionImplementor session, Boolean readOnly)
    at NHibernate.Loader.Loader.DoQuery(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies)
    at NHibernate.Loader.Loader.DoQueryAndInitializeNonLazyCollections(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies)
    at NHibernate.Loader.Loader.LoadCollection(ISessionImplementor session, Object id, IType type)

Вот модульный тест, который показывает проблему с упрощенной моделью:

    [Test]
    public void Test()
    {
        ISession session1 = NHibernateHelper.SessionFactory.OpenSession();
        ISession session2 = NHibernateHelper.SessionFactory.OpenSession();

        // Create a new entity tree and persist it
        ParentTestEntity parentSession1 = new ParentTestEntity();
        parentSession1.AddChild(new ChildTestEntity());
        session1.Save(parentSession1);
        session1.Flush();

        // Load the saved object into another session
        ParentTestEntity parentSession2 = session2.Get<ParentTestEntity>(parentSession1.Id);
        session2.Refresh(parentSession2); // Throws here
    }

Вот сущности:

public class ParentTestEntity
{
    public virtual Guid Id { get; private set; }
    public virtual long RowVersion { get; private set; }

    public virtual IList<ChildTestEntity> Children { get; protected set; }

    public ParentTestEntity()
    {
        this.Children = new List<ChildTestEntity>();
    }

    public virtual int Count
    {
        get
        {
            return Children.Count;
        }

        set { }
    }

    public virtual void AddChild(ChildTestEntity child)
    {
        if (this.Children == null)
        {
            this.Children = new List<ChildTestEntity>();
        }

        this.Children.Add(child);
        child.Parent = this;
    }
}

public class ChildTestEntity
{
    public virtual Guid Id { get; private set; }
    public virtual long RowVersion { get; private set; }

    public virtual ParentTestEntity Parent { get; set; }
}

И их сопоставления:

public class ParentTestEntityMap : ClassMap<ParentTestEntity>
{
    public ParentTestEntityMap()
    {
        Cache.ReadWrite();

        Id(x => x.Id)
            .GeneratedBy.GuidComb();

        Version(x => x.RowVersion);

        HasMany(x => x.Children)
            .Inverse()
            .Cascade.All()
            .Cache.ReadWrite();

        Map(x => x.Count);
    }
}

public class ChildTestEntityMap : ClassMap<ChildTestEntity>
{
    public ChildTestEntityMap()
    {
        Cache.ReadWrite();

        Id(x => x.Id)
            .GeneratedBy.GuidComb(); 

        Version(x => x.RowVersion);

        References(x => x.Parent)
            .Not.Nullable();
    }
}

Во время моих тестов я обнаружил, что либо:

  • удаление сопоставления свойства Count,
  • удаление сопоставления Cache.ReadWrite(),
  • перечисление коллекции до Refresh,

достаточно для правильной работы Refresh.

Кто-нибудь знает, что я могу сделать, чтобы работа Refresh?

Примечания:

  • Я могу воспроизвестиэто поведение в NHibernate 2.1.2 и 3.1.0,
  • Я знаю, что пустой сеттер в Count ужасен, он здесь только для того, чтобы отразить отображение сущности в реальной модели.

Ответы [ 2 ]

1 голос
/ 08 августа 2011

Использование Load() вместо Get() решает проблему.

Это не общее решение, так как Load() имеет немного другую семантику и будет выдавать, если соответствующая строка не существует.

Это приемлемое ограничение в нашей архитектуре, поскольку мы знаем, что загруженные объекты существуют в БД.

Однако любое решение с Get() все еще приветствуется.

0 голосов
/ 04 августа 2011

Пожалуйста, см. мой ответ на аналогичный вопрос: «Невозможно назначить значение свойства в конструкторе сущности» .Я не буду вдаваться во все детали здесь, потому что вы можете прочитать это там.

Короткий ответ: вы получаете доступ к виртуальному свойству в конструкторе, которое запрещено.Следующее изменение должно решить проблему.Замените эти строки ...

public virtual IList<ChildTestEntity> Children { get; protected set; }

public ParentTestEntity()
{
    this.Children = new List<ChildTestEntity>();
}

... этими строками:

private IList<ChildTestEntity> _children;

public virtual IList<ChildTestEntity> Children
{
    get { return _children; }
    protected set { _children = value; }
}

public ParentTestEntity()
{
    _children = new List<ChildTestEntity>();
}

FxCop и ReSharper - два разных инструмента, которые могут автоматически определять эту проблему.

...