2011-08-02 18 views
7

He estado experimentando un comportamiento extraño (al menos para mí) con ISession.Refresh().¿Por qué NHibernate lanza una excepción "GenericADOException: no se pudo inicializar una colección" durante ISession.Refresh en este caso?

Tengo una entidad con una colección de niños con carga lenta y una propiedad de solo lectura que llega a esta colección, todo incluido dentro de la memoria caché de segundo nivel. utilizo ISession.Refresh() en una larga sesión para obtener los últimos datos después de cometer una transacción de base de datos, y obtener el siguiente error:

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) 

Aquí es una prueba de unidad que muestran el problema con un modelo simplificado:

[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 
    } 

Estas son las entidades que intervienen:

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; } 
} 

y sus asignaciones:

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(); 
    } 
} 

Durante mis pruebas encontré que ya sea:

  • eliminar el mapeo de la propiedad Count,
  • eliminación de los Cache.ReadWrite() asignaciones,
  • enumerar la colección antes de Refresh,

son suficientes para que el Refresh funcione correctamente.

¿Alguien tiene alguna idea de lo que podría hacer para que Refresh funcione?

Notas:

  • puedo reproducir este comportamiento en tanto NHibernate 2.1.2 y 3.1.0,
  • que conozco la incubadora vacía en Count es feo, que está aquí sólo para reflejar la asignación de la entidad en el modelo real.

Respuesta

1

El uso de Load() en reemplazo de Get() cirucmvents el problema.

Esto no es una solución general, ya que Load() tiene una semántica ligeramente diferente, y arrojará si la fila correspondiente no existe.

Sin embargo, esta es una limitación aceptable en nuestra arquitectura, ya que sabemos que las entidades cargadas existen en la base de datos.

Cualquier solución con Get() sigue siendo bienvenida.

0

Consulte my answer for a similar question, "Cannot assign property value in entity's constructor". No entraré en detalles aquí porque puedes leerlo allí.

La respuesta corta es que está accediendo a una propiedad virtual en su constructor, que es un no-no. El siguiente cambio debería solucionar el problema. Reemplazar estas líneas ...

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

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

... con estas líneas:

private IList<ChildTestEntity> _children; 

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

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

FxCop y ReSharper son dos herramientas diferentes que pueden identificar automáticamente este problema.

+0

Es cierto, esa es una mala supervisión del diseño en el modelo (sabía sobre la regla, pero no pensé en ello en ese momento, gracias por recordarme). Sin embargo, esto no está relacionado con el problema; la prueba falla de la misma manera con sus cambios. Parece estar más relacionado con la obtención de Count como lo muestra el seguimiento de la pila. –

Cuestiones relacionadas