From 0def815564f14b7d0a408d721a88c229a965cf4a Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Fri, 8 Mar 2019 15:25:09 +0200 Subject: [PATCH 1/8] Fix transient detection for proxy Fixes #2043 --- .../GH2043/EntityWithClassLookup.cs | 13 ++ .../GH2043/EntityWithClassProxyDefinition.cs | 11 ++ .../GH2043/EntityWithInterfaceLookup.cs | 13 ++ .../EntityWithInterfaceProxyDefinition.cs | 11 ++ .../NHSpecificTest/GH2043/Fixture.cs | 181 ++++++++++++++++++ .../NHSpecificTest/GH2043/IEntityProxy.cs | 11 ++ src/NHibernate/Engine/ForeignKeys.cs | 14 +- .../Tuple/Entity/AbstractEntityTuplizer.cs | 5 + 8 files changed, 256 insertions(+), 3 deletions(-) create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithClassLookup.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithClassProxyDefinition.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithInterfaceLookup.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithInterfaceProxyDefinition.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2043/Fixture.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2043/IEntityProxy.cs diff --git a/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithClassLookup.cs b/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithClassLookup.cs new file mode 100644 index 00000000000..5ef02cee062 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithClassLookup.cs @@ -0,0 +1,13 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.IlogsProxyTest +{ + public class EntityWithClassLookup + { + public virtual Guid Id { get; set; } + + public virtual string Name { get; set; } + + public virtual EntityWithClassProxyDefinition EntityLookup { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithClassProxyDefinition.cs b/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithClassProxyDefinition.cs new file mode 100644 index 00000000000..8db1b55d9a8 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithClassProxyDefinition.cs @@ -0,0 +1,11 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.IlogsProxyTest +{ + public class EntityWithClassProxyDefinition + { + public virtual Guid Id { get; set; } + + public virtual string Name { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithInterfaceLookup.cs b/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithInterfaceLookup.cs new file mode 100644 index 00000000000..a5d5841594c --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithInterfaceLookup.cs @@ -0,0 +1,13 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.IlogsProxyTest +{ + public class EntityWithInterfaceLookup + { + public virtual Guid Id { get; set; } + + public virtual string Name { get; set; } + + public virtual IEntityProxy EntityLookup { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithInterfaceProxyDefinition.cs b/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithInterfaceProxyDefinition.cs new file mode 100644 index 00000000000..7da48b9b7c1 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithInterfaceProxyDefinition.cs @@ -0,0 +1,11 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.IlogsProxyTest +{ + public class EntityWithInterfaceProxyDefinition: IEntityProxy + { + public virtual Guid Id { get; set; } + + public virtual string Name { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2043/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2043/Fixture.cs new file mode 100644 index 00000000000..80bc1518a75 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2043/Fixture.cs @@ -0,0 +1,181 @@ +using System; +using System.Linq; +using NHibernate.Cfg; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.IlogsProxyTest +{ + [TestFixture] + public class Fixture : TestCaseMappingByCode + { + private Guid _entityWithClassProxy1Id; + private Guid _entityWithClassProxy2Id; + private Guid _entityWithInterfaceProxy1Id; + private Guid _entityWithInterfaceProxy2Id; + private Guid _entityWithClassLookupId; + private Guid _entityWithInterfaceLookupId; + + protected override void Configure(Configuration configuration) + { + base.Configure(configuration); + + Cfg.Environment.UseReflectionOptimizer = false; + } + + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Proxy(typeof(EntityWithClassProxyDefinition)); + + rc.Id(x => x.Id); + rc.Property(x => x.Name); + }); + + mapper.Class(rc => + { + rc.Proxy(typeof(IEntityProxy)); + + rc.Id(x => x.Id); + rc.Property(x => x.Name); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.EntityLookup, x => x.Class(typeof(EntityWithClassProxyDefinition))); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.EntityLookup, x => x.Class(typeof(EntityWithInterfaceProxyDefinition))); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + + protected override void OnSetUp() + { + using(var session = OpenSession()) + { + var entityCP1 = new EntityWithClassProxyDefinition + { + Id = Guid.NewGuid(), + Name = "Name 1" + }; + _entityWithClassProxy1Id = entityCP1.Id; + + var entityCP2 = new EntityWithClassProxyDefinition + { + Id = Guid.NewGuid(), + Name = "Name 2" + }; + _entityWithClassProxy2Id = entityCP2.Id; + + var entityIP1 = new EntityWithInterfaceProxyDefinition + { + Id = Guid.NewGuid(), + Name = "Name 1" + }; + _entityWithInterfaceProxy1Id = entityIP1.Id; + + var entityIP2 = new EntityWithInterfaceProxyDefinition + { + Id = Guid.NewGuid(), + Name = "Name 2" + }; + _entityWithInterfaceProxy2Id = entityIP2.Id; + + session.Save(entityCP1); + session.Save(entityCP2); + session.Save(entityIP1); + session.Save(entityIP2); + + var entityCL = new EntityWithClassLookup + { + Id = Guid.NewGuid(), + Name = "Name 1", + EntityLookup = (EntityWithClassProxyDefinition)session.Load(typeof(EntityWithClassProxyDefinition), entityCP1.Id) + }; + _entityWithClassLookupId = entityCL.Id; + + var entityIL = new EntityWithInterfaceLookup + { + Id = Guid.NewGuid(), + Name = "Name 1", + EntityLookup = (IEntityProxy)session.Load(typeof(EntityWithInterfaceProxyDefinition), entityIP1.Id) + }; + _entityWithInterfaceLookupId = entityIL.Id; + + session.Save(entityCL); + session.Save(entityIL); + + session.Flush(); + } + } + + + protected override void OnTearDown() + { + using (var session = OpenSession()) + { + using (var transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + + session.Flush(); + transaction.Commit(); + } + } + } + + + [Test] + public void UpdateEntityWithClassLookup() + { + using (var session = OpenSession()) + { + using(var transaction = session.BeginTransaction()) + { + var entityToUpdate = session.Query() + .First(e => e.Id == _entityWithClassLookupId); + + entityToUpdate.EntityLookup = (EntityWithClassProxyDefinition)session.Load(typeof(EntityWithClassProxyDefinition), _entityWithClassProxy2Id); + + session.Update(entityToUpdate); + session.Flush(); + transaction.Rollback(); + } + } + } + + + [Test] + public void UpdateEntityWithInterfaceLookup() + { + using(var session = OpenSession()) + { + using(var transaction = session.BeginTransaction()) + { + var entityToUpdate = session.Query() + .First(e => e.Id == _entityWithInterfaceLookupId); + + entityToUpdate.EntityLookup = (IEntityProxy)session.Load(typeof(EntityWithInterfaceProxyDefinition), _entityWithInterfaceProxy2Id); + entityToUpdate.EntityLookup.Id = Guid.Empty; + + session.Update(entityToUpdate); + session.Flush(); + transaction.Rollback(); + } + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2043/IEntityProxy.cs b/src/NHibernate.Test/NHSpecificTest/GH2043/IEntityProxy.cs new file mode 100644 index 00000000000..e136a2a79c0 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2043/IEntityProxy.cs @@ -0,0 +1,11 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.IlogsProxyTest +{ + public interface IEntityProxy + { + Guid Id { get; set; } + + string Name { get; set; } + } +} diff --git a/src/NHibernate/Engine/ForeignKeys.cs b/src/NHibernate/Engine/ForeignKeys.cs index 3c6df63a815..674a0ace89d 100644 --- a/src/NHibernate/Engine/ForeignKeys.cs +++ b/src/NHibernate/Engine/ForeignKeys.cs @@ -179,9 +179,17 @@ public static bool IsNotTransientSlow(string entityName, object entity, ISession } // let the interceptor inspect the instance to decide - // let the persister inspect the instance to decide - return session.Interceptor.IsTransient(entity) ?? - session.GetEntityPersister(entityName, entity).IsTransient(entity, session); + + if (session.Interceptor.IsTransient(entity) == true) + return true; + + if (entity is INHibernateProxy proxy && proxy.HibernateLazyInitializer.IsUninitialized) + { + return false; + } + + // let the persister inspect the instance to decide + return session.GetEntityPersister(entityName, entity).IsTransient(entity, session); } /// diff --git a/src/NHibernate/Tuple/Entity/AbstractEntityTuplizer.cs b/src/NHibernate/Tuple/Entity/AbstractEntityTuplizer.cs index f4f6c4c0b60..0796e603326 100644 --- a/src/NHibernate/Tuple/Entity/AbstractEntityTuplizer.cs +++ b/src/NHibernate/Tuple/Entity/AbstractEntityTuplizer.cs @@ -114,6 +114,11 @@ public object Instantiate(object id) public object GetIdentifier(object entity) { + if (entity is INHibernateProxy proxy) + { + return proxy.HibernateLazyInitializer.Identifier; + } + object id; if (entityMetamodel.IdentifierProperty.IsEmbedded) { From 2f694e80d7294fafc4a9f8f41a3ccec2e0f9d5ac Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Fri, 8 Mar 2019 15:48:42 +0200 Subject: [PATCH 2/8] Async regen --- .../Async/NHSpecificTest/GH2043/Fixture.cs | 185 ++++++++++++++++++ .../NHSpecificTest/GH2043/Fixture.cs | 10 +- src/NHibernate/Async/Engine/ForeignKeys.cs | 38 +++- 3 files changed, 214 insertions(+), 19 deletions(-) create mode 100644 src/NHibernate.Test/Async/NHSpecificTest/GH2043/Fixture.cs diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2043/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2043/Fixture.cs new file mode 100644 index 00000000000..b747f689a5a --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2043/Fixture.cs @@ -0,0 +1,185 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Linq; +using NHibernate.Cfg; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; +using NHibernate.Linq; + +namespace NHibernate.Test.NHSpecificTest.IlogsProxyTest +{ + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync : TestCaseMappingByCode + { + private Guid _entityWithClassProxy1Id; + private Guid _entityWithClassProxy2Id; + private Guid _entityWithInterfaceProxy1Id; + private Guid _entityWithInterfaceProxy2Id; + private Guid _entityWithClassLookupId; + private Guid _entityWithInterfaceLookupId; + + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Proxy(typeof(EntityWithClassProxyDefinition)); + + rc.Id(x => x.Id); + rc.Property(x => x.Name); + }); + + mapper.Class(rc => + { + rc.Proxy(typeof(IEntityProxy)); + + rc.Id(x => x.Id); + rc.Property(x => x.Name); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.EntityLookup, x => x.Class(typeof(EntityWithClassProxyDefinition))); + }); + + mapper.Class(rc => + { + rc.Id(x => x.Id); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.EntityLookup, x => x.Class(typeof(EntityWithInterfaceProxyDefinition))); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + + protected override void OnSetUp() + { + using(var session = OpenSession()) + { + var entityCP1 = new EntityWithClassProxyDefinition + { + Id = Guid.NewGuid(), + Name = "Name 1" + }; + _entityWithClassProxy1Id = entityCP1.Id; + + var entityCP2 = new EntityWithClassProxyDefinition + { + Id = Guid.NewGuid(), + Name = "Name 2" + }; + _entityWithClassProxy2Id = entityCP2.Id; + + var entityIP1 = new EntityWithInterfaceProxyDefinition + { + Id = Guid.NewGuid(), + Name = "Name 1" + }; + _entityWithInterfaceProxy1Id = entityIP1.Id; + + var entityIP2 = new EntityWithInterfaceProxyDefinition + { + Id = Guid.NewGuid(), + Name = "Name 2" + }; + _entityWithInterfaceProxy2Id = entityIP2.Id; + + session.Save(entityCP1); + session.Save(entityCP2); + session.Save(entityIP1); + session.Save(entityIP2); + + var entityCL = new EntityWithClassLookup + { + Id = Guid.NewGuid(), + Name = "Name 1", + EntityLookup = (EntityWithClassProxyDefinition)session.Load(typeof(EntityWithClassProxyDefinition), entityCP1.Id) + }; + _entityWithClassLookupId = entityCL.Id; + + var entityIL = new EntityWithInterfaceLookup + { + Id = Guid.NewGuid(), + Name = "Name 1", + EntityLookup = (IEntityProxy)session.Load(typeof(EntityWithInterfaceProxyDefinition), entityIP1.Id) + }; + _entityWithInterfaceLookupId = entityIL.Id; + + session.Save(entityCL); + session.Save(entityIL); + + session.Flush(); + } + } + + + protected override void OnTearDown() + { + using (var session = OpenSession()) + { + using (var transaction = session.BeginTransaction()) + { + session.Delete("from System.Object"); + + session.Flush(); + transaction.Commit(); + } + } + } + + + [Test] + public async Task UpdateEntityWithClassLookupAsync() + { + using (var session = OpenSession()) + { + using(var transaction = session.BeginTransaction()) + { + var entityToUpdate = await (session.Query() + .FirstAsync(e => e.Id == _entityWithClassLookupId)); + + entityToUpdate.EntityLookup = (EntityWithClassProxyDefinition)await (session.LoadAsync(typeof(EntityWithClassProxyDefinition), _entityWithClassProxy2Id)); + + await (session.UpdateAsync(entityToUpdate)); + await (session.FlushAsync()); + await (transaction.RollbackAsync()); + } + } + } + + + [Test] + public async Task UpdateEntityWithInterfaceLookupAsync() + { + using(var session = OpenSession()) + { + using(var transaction = session.BeginTransaction()) + { + var entityToUpdate = await (session.Query() + .FirstAsync(e => e.Id == _entityWithInterfaceLookupId)); + + entityToUpdate.EntityLookup = (IEntityProxy)await (session.LoadAsync(typeof(EntityWithInterfaceProxyDefinition), _entityWithInterfaceProxy2Id)); + entityToUpdate.EntityLookup.Id = Guid.Empty; + + await (session.UpdateAsync(entityToUpdate)); + await (session.FlushAsync()); + await (transaction.RollbackAsync()); + } + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2043/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2043/Fixture.cs index 80bc1518a75..d5769e34d56 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH2043/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH2043/Fixture.cs @@ -16,15 +16,7 @@ public class Fixture : TestCaseMappingByCode private Guid _entityWithInterfaceProxy2Id; private Guid _entityWithClassLookupId; private Guid _entityWithInterfaceLookupId; - - protected override void Configure(Configuration configuration) - { - base.Configure(configuration); - - Cfg.Environment.UseReflectionOptimizer = false; - } - - + protected override HbmMapping GetMappings() { var mapper = new ModelMapper(); diff --git a/src/NHibernate/Async/Engine/ForeignKeys.cs b/src/NHibernate/Async/Engine/ForeignKeys.cs index 11597f5fcd1..31e95936ca5 100644 --- a/src/NHibernate/Async/Engine/ForeignKeys.cs +++ b/src/NHibernate/Async/Engine/ForeignKeys.cs @@ -171,20 +171,38 @@ public static async Task IsNotTransientSlowAsync(string entityName, object /// /// Don't hit the database to make the determination, instead return null; /// - public static async Task IsTransientFastAsync(string entityName, object entity, ISessionImplementor session, CancellationToken cancellationToken) + public static Task IsTransientFastAsync(string entityName, object entity, ISessionImplementor session, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); - if (Equals(Intercept.LazyPropertyInitializer.UnfetchedProperty, entity)) + if (cancellationToken.IsCancellationRequested) { - // an unfetched association can only point to - // an entity that already exists in the db - return false; + return Task.FromCanceled(cancellationToken); } + try + { + if (Equals(Intercept.LazyPropertyInitializer.UnfetchedProperty, entity)) + { + // an unfetched association can only point to + // an entity that already exists in the db + return Task.FromResult(false); + } + + // let the interceptor inspect the instance to decide + + if (session.Interceptor.IsTransient(entity) == true) + return Task.FromResult(true); - // let the interceptor inspect the instance to decide - // let the persister inspect the instance to decide - return session.Interceptor.IsTransient(entity) ?? - await (session.GetEntityPersister(entityName, entity).IsTransientAsync(entity, session, cancellationToken)).ConfigureAwait(false); + if (entity is INHibernateProxy proxy && proxy.HibernateLazyInitializer.IsUninitialized) + { + return Task.FromResult(false); + } + + // let the persister inspect the instance to decide + return session.GetEntityPersister(entityName, entity).IsTransientAsync(entity, session, cancellationToken); + } + catch (System.Exception ex) + { + return Task.FromException(ex); + } } /// From 238cb378c9428dcefe40ea143374a7a59988531e Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Fri, 8 Mar 2019 21:13:22 +0200 Subject: [PATCH 3/8] Fixed test --- src/NHibernate.Test/Async/NHSpecificTest/GH2043/Fixture.cs | 1 - src/NHibernate.Test/NHSpecificTest/GH2043/Fixture.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2043/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2043/Fixture.cs index b747f689a5a..396bdef9fe0 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH2043/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2043/Fixture.cs @@ -173,7 +173,6 @@ public async Task UpdateEntityWithInterfaceLookupAsync() .FirstAsync(e => e.Id == _entityWithInterfaceLookupId)); entityToUpdate.EntityLookup = (IEntityProxy)await (session.LoadAsync(typeof(EntityWithInterfaceProxyDefinition), _entityWithInterfaceProxy2Id)); - entityToUpdate.EntityLookup.Id = Guid.Empty; await (session.UpdateAsync(entityToUpdate)); await (session.FlushAsync()); diff --git a/src/NHibernate.Test/NHSpecificTest/GH2043/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2043/Fixture.cs index d5769e34d56..0bb92ff577a 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH2043/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH2043/Fixture.cs @@ -161,7 +161,6 @@ public void UpdateEntityWithInterfaceLookup() .First(e => e.Id == _entityWithInterfaceLookupId); entityToUpdate.EntityLookup = (IEntityProxy)session.Load(typeof(EntityWithInterfaceProxyDefinition), _entityWithInterfaceProxy2Id); - entityToUpdate.EntityLookup.Id = Guid.Empty; session.Update(entityToUpdate); session.Flush(); From baf1031561ffb14de83bfbe5979d6e60a98ea619 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Fri, 8 Mar 2019 22:14:53 +0200 Subject: [PATCH 4/8] small clean up --- src/NHibernate.Test/Async/NHSpecificTest/GH2043/Fixture.cs | 2 +- .../NHSpecificTest/GH2043/EntityWithClassLookup.cs | 2 +- .../NHSpecificTest/GH2043/EntityWithClassProxyDefinition.cs | 2 +- .../NHSpecificTest/GH2043/EntityWithInterfaceLookup.cs | 2 +- .../NHSpecificTest/GH2043/EntityWithInterfaceProxyDefinition.cs | 2 +- src/NHibernate.Test/NHSpecificTest/GH2043/Fixture.cs | 2 +- src/NHibernate.Test/NHSpecificTest/GH2043/IEntityProxy.cs | 2 +- src/NHibernate/Async/Engine/ForeignKeys.cs | 1 - src/NHibernate/Engine/ForeignKeys.cs | 1 - 9 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2043/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2043/Fixture.cs index 396bdef9fe0..fb0a6e856c9 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH2043/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2043/Fixture.cs @@ -16,7 +16,7 @@ using NUnit.Framework; using NHibernate.Linq; -namespace NHibernate.Test.NHSpecificTest.IlogsProxyTest +namespace NHibernate.Test.NHSpecificTest.GH2043 { using System.Threading.Tasks; [TestFixture] diff --git a/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithClassLookup.cs b/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithClassLookup.cs index 5ef02cee062..637c5ff5bda 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithClassLookup.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithClassLookup.cs @@ -1,6 +1,6 @@ using System; -namespace NHibernate.Test.NHSpecificTest.IlogsProxyTest +namespace NHibernate.Test.NHSpecificTest.GH2043 { public class EntityWithClassLookup { diff --git a/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithClassProxyDefinition.cs b/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithClassProxyDefinition.cs index 8db1b55d9a8..4829de3012c 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithClassProxyDefinition.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithClassProxyDefinition.cs @@ -1,6 +1,6 @@ using System; -namespace NHibernate.Test.NHSpecificTest.IlogsProxyTest +namespace NHibernate.Test.NHSpecificTest.GH2043 { public class EntityWithClassProxyDefinition { diff --git a/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithInterfaceLookup.cs b/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithInterfaceLookup.cs index a5d5841594c..6bcee043a49 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithInterfaceLookup.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithInterfaceLookup.cs @@ -1,6 +1,6 @@ using System; -namespace NHibernate.Test.NHSpecificTest.IlogsProxyTest +namespace NHibernate.Test.NHSpecificTest.GH2043 { public class EntityWithInterfaceLookup { diff --git a/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithInterfaceProxyDefinition.cs b/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithInterfaceProxyDefinition.cs index 7da48b9b7c1..198ddadfa4a 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithInterfaceProxyDefinition.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithInterfaceProxyDefinition.cs @@ -1,6 +1,6 @@ using System; -namespace NHibernate.Test.NHSpecificTest.IlogsProxyTest +namespace NHibernate.Test.NHSpecificTest.GH2043 { public class EntityWithInterfaceProxyDefinition: IEntityProxy { diff --git a/src/NHibernate.Test/NHSpecificTest/GH2043/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2043/Fixture.cs index 0bb92ff577a..18d167111b7 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH2043/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH2043/Fixture.cs @@ -5,7 +5,7 @@ using NHibernate.Mapping.ByCode; using NUnit.Framework; -namespace NHibernate.Test.NHSpecificTest.IlogsProxyTest +namespace NHibernate.Test.NHSpecificTest.GH2043 { [TestFixture] public class Fixture : TestCaseMappingByCode diff --git a/src/NHibernate.Test/NHSpecificTest/GH2043/IEntityProxy.cs b/src/NHibernate.Test/NHSpecificTest/GH2043/IEntityProxy.cs index e136a2a79c0..76b48d30eb1 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH2043/IEntityProxy.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH2043/IEntityProxy.cs @@ -1,6 +1,6 @@ using System; -namespace NHibernate.Test.NHSpecificTest.IlogsProxyTest +namespace NHibernate.Test.NHSpecificTest.GH2043 { public interface IEntityProxy { diff --git a/src/NHibernate/Async/Engine/ForeignKeys.cs b/src/NHibernate/Async/Engine/ForeignKeys.cs index 31e95936ca5..70feda6fd05 100644 --- a/src/NHibernate/Async/Engine/ForeignKeys.cs +++ b/src/NHibernate/Async/Engine/ForeignKeys.cs @@ -187,7 +187,6 @@ public static async Task IsNotTransientSlowAsync(string entityName, object } // let the interceptor inspect the instance to decide - if (session.Interceptor.IsTransient(entity) == true) return Task.FromResult(true); diff --git a/src/NHibernate/Engine/ForeignKeys.cs b/src/NHibernate/Engine/ForeignKeys.cs index 674a0ace89d..dea20350b6f 100644 --- a/src/NHibernate/Engine/ForeignKeys.cs +++ b/src/NHibernate/Engine/ForeignKeys.cs @@ -179,7 +179,6 @@ public static bool IsNotTransientSlow(string entityName, object entity, ISession } // let the interceptor inspect the instance to decide - if (session.Interceptor.IsTransient(entity) == true) return true; From 986fa50f1bad993a46731f43d7ab7e8f8e5771b1 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sat, 9 Mar 2019 07:47:19 +0200 Subject: [PATCH 5/8] Fix long table name and formatting --- .../Async/NHSpecificTest/GH2043/Fixture.cs | 53 ++++++++---------- .../NHSpecificTest/GH2043/Fixture.cs | 55 ++++++++----------- 2 files changed, 47 insertions(+), 61 deletions(-) diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2043/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2043/Fixture.cs index fb0a6e856c9..bd73753b0a1 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH2043/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2043/Fixture.cs @@ -10,7 +10,6 @@ using System; using System.Linq; -using NHibernate.Cfg; using NHibernate.Cfg.MappingSchema; using NHibernate.Mapping.ByCode; using NUnit.Framework; @@ -22,9 +21,7 @@ namespace NHibernate.Test.NHSpecificTest.GH2043 [TestFixture] public class FixtureAsync : TestCaseMappingByCode { - private Guid _entityWithClassProxy1Id; private Guid _entityWithClassProxy2Id; - private Guid _entityWithInterfaceProxy1Id; private Guid _entityWithInterfaceProxy2Id; private Guid _entityWithClassLookupId; private Guid _entityWithInterfaceLookupId; @@ -34,6 +31,7 @@ protected override HbmMapping GetMappings() var mapper = new ModelMapper(); mapper.Class(rc => { + rc.Table("ProxyDefinition"); rc.Proxy(typeof(EntityWithClassProxyDefinition)); rc.Id(x => x.Id); @@ -42,6 +40,7 @@ protected override HbmMapping GetMappings() mapper.Class(rc => { + rc.Table("IProxyDefinition"); rc.Proxy(typeof(IEntityProxy)); rc.Id(x => x.Id); @@ -69,13 +68,13 @@ protected override HbmMapping GetMappings() protected override void OnSetUp() { using(var session = OpenSession()) + using (var transaction = session.BeginTransaction()) { var entityCP1 = new EntityWithClassProxyDefinition { Id = Guid.NewGuid(), Name = "Name 1" }; - _entityWithClassProxy1Id = entityCP1.Id; var entityCP2 = new EntityWithClassProxyDefinition { @@ -89,7 +88,6 @@ protected override void OnSetUp() Id = Guid.NewGuid(), Name = "Name 1" }; - _entityWithInterfaceProxy1Id = entityIP1.Id; var entityIP2 = new EntityWithInterfaceProxyDefinition { @@ -123,6 +121,7 @@ protected override void OnSetUp() session.Save(entityIL); session.Flush(); + transaction.Commit(); } } @@ -130,14 +129,12 @@ protected override void OnSetUp() protected override void OnTearDown() { using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) { - using (var transaction = session.BeginTransaction()) - { - session.Delete("from System.Object"); + session.Delete("from System.Object"); - session.Flush(); - transaction.Commit(); - } + session.Flush(); + transaction.Commit(); } } @@ -146,18 +143,16 @@ protected override void OnTearDown() public async Task UpdateEntityWithClassLookupAsync() { using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) { - using(var transaction = session.BeginTransaction()) - { - var entityToUpdate = await (session.Query() - .FirstAsync(e => e.Id == _entityWithClassLookupId)); + var entityToUpdate = await (session.Query() + .FirstAsync(e => e.Id == _entityWithClassLookupId)); - entityToUpdate.EntityLookup = (EntityWithClassProxyDefinition)await (session.LoadAsync(typeof(EntityWithClassProxyDefinition), _entityWithClassProxy2Id)); + entityToUpdate.EntityLookup = (EntityWithClassProxyDefinition) await (session.LoadAsync(typeof(EntityWithClassProxyDefinition), _entityWithClassProxy2Id)); - await (session.UpdateAsync(entityToUpdate)); - await (session.FlushAsync()); - await (transaction.RollbackAsync()); - } + await (session.UpdateAsync(entityToUpdate)); + await (session.FlushAsync()); + await (transaction.CommitAsync()); } } @@ -165,19 +160,17 @@ public async Task UpdateEntityWithClassLookupAsync() [Test] public async Task UpdateEntityWithInterfaceLookupAsync() { - using(var session = OpenSession()) + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) { - using(var transaction = session.BeginTransaction()) - { - var entityToUpdate = await (session.Query() - .FirstAsync(e => e.Id == _entityWithInterfaceLookupId)); + var entityToUpdate = await (session.Query() + .FirstAsync(e => e.Id == _entityWithInterfaceLookupId)); - entityToUpdate.EntityLookup = (IEntityProxy)await (session.LoadAsync(typeof(EntityWithInterfaceProxyDefinition), _entityWithInterfaceProxy2Id)); + entityToUpdate.EntityLookup = (IEntityProxy) await (session.LoadAsync(typeof(EntityWithInterfaceProxyDefinition), _entityWithInterfaceProxy2Id)); - await (session.UpdateAsync(entityToUpdate)); - await (session.FlushAsync()); - await (transaction.RollbackAsync()); - } + await (session.UpdateAsync(entityToUpdate)); + await (session.FlushAsync()); + await (transaction.CommitAsync()); } } } diff --git a/src/NHibernate.Test/NHSpecificTest/GH2043/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2043/Fixture.cs index 18d167111b7..0f2ac723812 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH2043/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH2043/Fixture.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using NHibernate.Cfg; using NHibernate.Cfg.MappingSchema; using NHibernate.Mapping.ByCode; using NUnit.Framework; @@ -10,18 +9,17 @@ namespace NHibernate.Test.NHSpecificTest.GH2043 [TestFixture] public class Fixture : TestCaseMappingByCode { - private Guid _entityWithClassProxy1Id; private Guid _entityWithClassProxy2Id; - private Guid _entityWithInterfaceProxy1Id; private Guid _entityWithInterfaceProxy2Id; private Guid _entityWithClassLookupId; private Guid _entityWithInterfaceLookupId; - + protected override HbmMapping GetMappings() { var mapper = new ModelMapper(); mapper.Class(rc => { + rc.Table("ProxyDefinition"); rc.Proxy(typeof(EntityWithClassProxyDefinition)); rc.Id(x => x.Id); @@ -30,6 +28,7 @@ protected override HbmMapping GetMappings() mapper.Class(rc => { + rc.Table("IProxyDefinition"); rc.Proxy(typeof(IEntityProxy)); rc.Id(x => x.Id); @@ -57,13 +56,13 @@ protected override HbmMapping GetMappings() protected override void OnSetUp() { using(var session = OpenSession()) + using (var transaction = session.BeginTransaction()) { var entityCP1 = new EntityWithClassProxyDefinition { Id = Guid.NewGuid(), Name = "Name 1" }; - _entityWithClassProxy1Id = entityCP1.Id; var entityCP2 = new EntityWithClassProxyDefinition { @@ -77,7 +76,6 @@ protected override void OnSetUp() Id = Guid.NewGuid(), Name = "Name 1" }; - _entityWithInterfaceProxy1Id = entityIP1.Id; var entityIP2 = new EntityWithInterfaceProxyDefinition { @@ -111,6 +109,7 @@ protected override void OnSetUp() session.Save(entityIL); session.Flush(); + transaction.Commit(); } } @@ -118,14 +117,12 @@ protected override void OnSetUp() protected override void OnTearDown() { using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) { - using (var transaction = session.BeginTransaction()) - { - session.Delete("from System.Object"); + session.Delete("from System.Object"); - session.Flush(); - transaction.Commit(); - } + session.Flush(); + transaction.Commit(); } } @@ -134,18 +131,16 @@ protected override void OnTearDown() public void UpdateEntityWithClassLookup() { using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) { - using(var transaction = session.BeginTransaction()) - { - var entityToUpdate = session.Query() - .First(e => e.Id == _entityWithClassLookupId); + var entityToUpdate = session.Query() + .First(e => e.Id == _entityWithClassLookupId); - entityToUpdate.EntityLookup = (EntityWithClassProxyDefinition)session.Load(typeof(EntityWithClassProxyDefinition), _entityWithClassProxy2Id); + entityToUpdate.EntityLookup = (EntityWithClassProxyDefinition) session.Load(typeof(EntityWithClassProxyDefinition), _entityWithClassProxy2Id); - session.Update(entityToUpdate); - session.Flush(); - transaction.Rollback(); - } + session.Update(entityToUpdate); + session.Flush(); + transaction.Commit(); } } @@ -153,19 +148,17 @@ public void UpdateEntityWithClassLookup() [Test] public void UpdateEntityWithInterfaceLookup() { - using(var session = OpenSession()) + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) { - using(var transaction = session.BeginTransaction()) - { - var entityToUpdate = session.Query() - .First(e => e.Id == _entityWithInterfaceLookupId); + var entityToUpdate = session.Query() + .First(e => e.Id == _entityWithInterfaceLookupId); - entityToUpdate.EntityLookup = (IEntityProxy)session.Load(typeof(EntityWithInterfaceProxyDefinition), _entityWithInterfaceProxy2Id); + entityToUpdate.EntityLookup = (IEntityProxy) session.Load(typeof(EntityWithInterfaceProxyDefinition), _entityWithInterfaceProxy2Id); - session.Update(entityToUpdate); - session.Flush(); - transaction.Rollback(); - } + session.Update(entityToUpdate); + session.Flush(); + transaction.Commit(); } } } From 7969cf354af36691fe426f129fd39dc6ba5e0894 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sat, 16 Mar 2019 22:41:27 +0200 Subject: [PATCH 6/8] Move proxy check before interceptor --- src/NHibernate/Async/Engine/ForeignKeys.cs | 38 ++++++++-------------- src/NHibernate/Engine/ForeignKeys.cs | 10 +++--- 2 files changed, 17 insertions(+), 31 deletions(-) diff --git a/src/NHibernate/Async/Engine/ForeignKeys.cs b/src/NHibernate/Async/Engine/ForeignKeys.cs index 70feda6fd05..907f0e38162 100644 --- a/src/NHibernate/Async/Engine/ForeignKeys.cs +++ b/src/NHibernate/Async/Engine/ForeignKeys.cs @@ -171,37 +171,25 @@ public static async Task IsNotTransientSlowAsync(string entityName, object /// /// Don't hit the database to make the determination, instead return null; /// - public static Task IsTransientFastAsync(string entityName, object entity, ISessionImplementor session, CancellationToken cancellationToken) + public static async Task IsTransientFastAsync(string entityName, object entity, ISessionImplementor session, CancellationToken cancellationToken) { - if (cancellationToken.IsCancellationRequested) + cancellationToken.ThrowIfCancellationRequested(); + if (Equals(Intercept.LazyPropertyInitializer.UnfetchedProperty, entity)) { - return Task.FromCanceled(cancellationToken); + // an unfetched association can only point to + // an entity that already exists in the db + return false; } - try - { - if (Equals(Intercept.LazyPropertyInitializer.UnfetchedProperty, entity)) - { - // an unfetched association can only point to - // an entity that already exists in the db - return Task.FromResult(false); - } - - // let the interceptor inspect the instance to decide - if (session.Interceptor.IsTransient(entity) == true) - return Task.FromResult(true); - if (entity is INHibernateProxy proxy && proxy.HibernateLazyInitializer.IsUninitialized) - { - return Task.FromResult(false); - } - - // let the persister inspect the instance to decide - return session.GetEntityPersister(entityName, entity).IsTransientAsync(entity, session, cancellationToken); - } - catch (System.Exception ex) + if (entity is INHibernateProxy proxy && proxy.HibernateLazyInitializer.IsUninitialized) { - return Task.FromException(ex); + return false; } + + // let the interceptor inspect the instance to decide + // let the persister inspect the instance to decide + return session.Interceptor.IsTransient(entity) ?? + await (session.GetEntityPersister(entityName, entity).IsTransientAsync(entity, session, cancellationToken)).ConfigureAwait(false); } /// diff --git a/src/NHibernate/Engine/ForeignKeys.cs b/src/NHibernate/Engine/ForeignKeys.cs index dea20350b6f..ed555566733 100644 --- a/src/NHibernate/Engine/ForeignKeys.cs +++ b/src/NHibernate/Engine/ForeignKeys.cs @@ -178,17 +178,15 @@ public static bool IsNotTransientSlow(string entityName, object entity, ISession return false; } - // let the interceptor inspect the instance to decide - if (session.Interceptor.IsTransient(entity) == true) - return true; - if (entity is INHibernateProxy proxy && proxy.HibernateLazyInitializer.IsUninitialized) { return false; } - // let the persister inspect the instance to decide - return session.GetEntityPersister(entityName, entity).IsTransient(entity, session); + // let the interceptor inspect the instance to decide + // let the persister inspect the instance to decide + return session.Interceptor.IsTransient(entity) ?? + session.GetEntityPersister(entityName, entity).IsTransient(entity, session); } /// From 660dcd34e82c6a86bdfa6ee41da60dd51694b638 Mon Sep 17 00:00:00 2001 From: Roman Artiukhin Date: Sat, 16 Mar 2019 22:42:15 +0200 Subject: [PATCH 7/8] Tests for transient proxy --- .../Async/NHSpecificTest/GH2043/Fixture.cs | 135 +++++++++++++++++- .../NHSpecificTest/GH2043/EntityAssigned.cs | 9 ++ .../GH2043/EntityWithAssignedBag.cs | 11 ++ .../NHSpecificTest/GH2043/Fixture.cs | 134 ++++++++++++++++- 4 files changed, 281 insertions(+), 8 deletions(-) create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2043/EntityAssigned.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithAssignedBag.cs diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2043/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2043/Fixture.cs index bd73753b0a1..6b94d0fc903 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH2043/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2043/Fixture.cs @@ -11,13 +11,16 @@ using System; using System.Linq; using NHibernate.Cfg.MappingSchema; +using NHibernate.Engine; using NHibernate.Mapping.ByCode; +using NHibernate.Proxy; using NUnit.Framework; using NHibernate.Linq; namespace NHibernate.Test.NHSpecificTest.GH2043 { using System.Threading.Tasks; + using System.Threading; [TestFixture] public class FixtureAsync : TestCaseMappingByCode { @@ -61,10 +64,31 @@ protected override HbmMapping GetMappings() rc.ManyToOne(x => x.EntityLookup, x => x.Class(typeof(EntityWithInterfaceProxyDefinition))); }); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.Assigned)); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.Parent); + }); + + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.Assigned)); + rc.Property(x => x.Name); + rc.Bag( + x => x.Children, + m => + { + m.Inverse(true); + m.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + }, + cm => { cm.OneToMany(); }); + }); + return mapper.CompileMappingForAllExplicitlyAddedEntities(); } - protected override void OnSetUp() { using(var session = OpenSession()) @@ -125,7 +149,6 @@ protected override void OnSetUp() } } - protected override void OnTearDown() { using (var session = OpenSession()) @@ -138,7 +161,6 @@ protected override void OnTearDown() } } - [Test] public async Task UpdateEntityWithClassLookupAsync() { @@ -156,7 +178,6 @@ public async Task UpdateEntityWithClassLookupAsync() } } - [Test] public async Task UpdateEntityWithInterfaceLookupAsync() { @@ -173,5 +194,111 @@ public async Task UpdateEntityWithInterfaceLookupAsync() await (transaction.CommitAsync()); } } + + [Test] + public async Task TransientProxySaveAsync() + { + var id = 10; + + using (var session = OpenSession()) + using(var t = session.BeginTransaction()) + { + var e = new EntityAssigned() {Id = id, Name = "a"}; + + await (session.SaveAsync(e)); + await (session.FlushAsync()); + await (t.CommitAsync()); + } + + using (var session = OpenSession()) + using(var t = session.BeginTransaction()) + { + var e = await (GetTransientProxyAsync(session, id)); + await (session.SaveAsync(e)); + await (session.FlushAsync()); + + await (t.CommitAsync()); + } + + using (var session = OpenSession()) + { + var entity = await (session.GetAsync(id)); + Assert.That(entity, Is.Not.Null, "Transient proxy wasn't saved"); + } + } + + [Test] + public async Task TransientProxyBagCascadeSaveAsync() + { + var id = 10; + + using (var session = OpenSession()) + using(var t = session.BeginTransaction()) + { + var e = new EntityAssigned() {Id = id, Name = "a"}; + await (session.SaveAsync(e)); + await (session.FlushAsync()); + await (t.CommitAsync()); + } + + using (var session = OpenSession()) + using(var t = session.BeginTransaction()) + { + var child = await (GetTransientProxyAsync(session, id)); + var parent = new EntityWithAssignedBag() + { + Id = 1, Name = "p", Children = + { + child + } + }; + child.Parent = parent; + + await (session.SaveAsync(parent)); + await (session.FlushAsync()); + + await (t.CommitAsync()); + } + + using (var session = OpenSession()) + { + var entity = await (session.GetAsync(id)); + Assert.That(entity, Is.Not.Null, "Transient proxy wasn't saved"); + } + } + + [Test] + public async Task TransientProxyDetectionFromUserCodeAsync() + { + var id = 10; + + using (var session = OpenSession()) + using (var t = session.BeginTransaction()) + { + var e = new EntityAssigned() {Id = id, Name = "a"}; + await (session.SaveAsync(e)); + await (session.FlushAsync()); + await (t.CommitAsync()); + } + + using (var session = OpenSession()) + using (var t = session.BeginTransaction()) + { + var child = await (GetTransientProxyAsync(session, id)); + Assert.That(await (ForeignKeys.IsTransientSlowAsync(typeof(EntityAssigned).FullName, child, session.GetSessionImplementation(), CancellationToken.None)), Is.True); + await (t.CommitAsync()); + } + } + + private static async Task GetTransientProxyAsync(ISession session, int id, CancellationToken cancellationToken = default(CancellationToken)) + { + EntityAssigned e; + e = await (session.LoadAsync(id, cancellationToken)); + e.Name = "b"; + await (session.DeleteAsync(e, cancellationToken)); + await (session.FlushAsync(cancellationToken)); + Assert.That(e.IsProxy(), Is.True, "Failed test set up"); + return e; + } } } diff --git a/src/NHibernate.Test/NHSpecificTest/GH2043/EntityAssigned.cs b/src/NHibernate.Test/NHSpecificTest/GH2043/EntityAssigned.cs new file mode 100644 index 00000000000..9708e11c7e1 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2043/EntityAssigned.cs @@ -0,0 +1,9 @@ +namespace NHibernate.Test.NHSpecificTest.GH2043 +{ + public class EntityAssigned + { + public virtual int Id { get; set; } + public virtual string Name { get; set; } + public virtual EntityWithAssignedBag Parent { get; set; } + } +} \ No newline at end of file diff --git a/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithAssignedBag.cs b/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithAssignedBag.cs new file mode 100644 index 00000000000..a579d3e966e --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithAssignedBag.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace NHibernate.Test.NHSpecificTest.GH2043 +{ + public class EntityWithAssignedBag + { + public virtual int Id { get; set; } + public virtual string Name { get; set; } + public virtual IList Children { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/src/NHibernate.Test/NHSpecificTest/GH2043/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2043/Fixture.cs index 0f2ac723812..a289226a6fc 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH2043/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH2043/Fixture.cs @@ -1,7 +1,9 @@ using System; using System.Linq; using NHibernate.Cfg.MappingSchema; +using NHibernate.Engine; using NHibernate.Mapping.ByCode; +using NHibernate.Proxy; using NUnit.Framework; namespace NHibernate.Test.NHSpecificTest.GH2043 @@ -49,10 +51,31 @@ protected override HbmMapping GetMappings() rc.ManyToOne(x => x.EntityLookup, x => x.Class(typeof(EntityWithInterfaceProxyDefinition))); }); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.Assigned)); + rc.Property(x => x.Name); + rc.ManyToOne(x => x.Parent); + }); + + mapper.Class( + rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.Assigned)); + rc.Property(x => x.Name); + rc.Bag( + x => x.Children, + m => + { + m.Inverse(true); + m.Cascade(Mapping.ByCode.Cascade.All | Mapping.ByCode.Cascade.DeleteOrphans); + }, + cm => { cm.OneToMany(); }); + }); + return mapper.CompileMappingForAllExplicitlyAddedEntities(); } - protected override void OnSetUp() { using(var session = OpenSession()) @@ -113,7 +136,6 @@ protected override void OnSetUp() } } - protected override void OnTearDown() { using (var session = OpenSession()) @@ -126,7 +148,6 @@ protected override void OnTearDown() } } - [Test] public void UpdateEntityWithClassLookup() { @@ -144,7 +165,6 @@ public void UpdateEntityWithClassLookup() } } - [Test] public void UpdateEntityWithInterfaceLookup() { @@ -161,5 +181,111 @@ public void UpdateEntityWithInterfaceLookup() transaction.Commit(); } } + + [Test] + public void TransientProxySave() + { + var id = 10; + + using (var session = OpenSession()) + using(var t = session.BeginTransaction()) + { + var e = new EntityAssigned() {Id = id, Name = "a"}; + + session.Save(e); + session.Flush(); + t.Commit(); + } + + using (var session = OpenSession()) + using(var t = session.BeginTransaction()) + { + var e = GetTransientProxy(session, id); + session.Save(e); + session.Flush(); + + t.Commit(); + } + + using (var session = OpenSession()) + { + var entity = session.Get(id); + Assert.That(entity, Is.Not.Null, "Transient proxy wasn't saved"); + } + } + + [Test] + public void TransientProxyBagCascadeSave() + { + var id = 10; + + using (var session = OpenSession()) + using(var t = session.BeginTransaction()) + { + var e = new EntityAssigned() {Id = id, Name = "a"}; + session.Save(e); + session.Flush(); + t.Commit(); + } + + using (var session = OpenSession()) + using(var t = session.BeginTransaction()) + { + var child = GetTransientProxy(session, id); + var parent = new EntityWithAssignedBag() + { + Id = 1, Name = "p", Children = + { + child + } + }; + child.Parent = parent; + + session.Save(parent); + session.Flush(); + + t.Commit(); + } + + using (var session = OpenSession()) + { + var entity = session.Get(id); + Assert.That(entity, Is.Not.Null, "Transient proxy wasn't saved"); + } + } + + [Test] + public void TransientProxyDetectionFromUserCode() + { + var id = 10; + + using (var session = OpenSession()) + using (var t = session.BeginTransaction()) + { + var e = new EntityAssigned() {Id = id, Name = "a"}; + session.Save(e); + session.Flush(); + t.Commit(); + } + + using (var session = OpenSession()) + using (var t = session.BeginTransaction()) + { + var child = GetTransientProxy(session, id); + Assert.That(ForeignKeys.IsTransientSlow(typeof(EntityAssigned).FullName, child, session.GetSessionImplementation()), Is.True); + t.Commit(); + } + } + + private static EntityAssigned GetTransientProxy(ISession session, int id) + { + EntityAssigned e; + e = session.Load(id); + e.Name = "b"; + session.Delete(e); + session.Flush(); + Assert.That(e.IsProxy(), Is.True, "Failed test set up"); + return e; + } } } From a1d631d229886724254bfc7e2dea7fa306b095ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= <12201973+fredericdelaporte@users.noreply.github.com> Date: Sun, 17 Mar 2019 11:23:28 +0100 Subject: [PATCH 8/8] Avoid dealing with proxies inside the persister --- src/NHibernate/Async/Engine/ForeignKeys.cs | 20 ++++++++++++++++--- src/NHibernate/Engine/ForeignKeys.cs | 20 ++++++++++++++++--- .../Tuple/Entity/AbstractEntityTuplizer.cs | 5 ----- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/NHibernate/Async/Engine/ForeignKeys.cs b/src/NHibernate/Async/Engine/ForeignKeys.cs index 907f0e38162..441dbcb87d4 100644 --- a/src/NHibernate/Async/Engine/ForeignKeys.cs +++ b/src/NHibernate/Async/Engine/ForeignKeys.cs @@ -181,15 +181,29 @@ public static async Task IsNotTransientSlowAsync(string entityName, object return false; } - if (entity is INHibernateProxy proxy && proxy.HibernateLazyInitializer.IsUninitialized) + var proxy = entity as INHibernateProxy; + if (proxy?.HibernateLazyInitializer.IsUninitialized == true) { return false; } // let the interceptor inspect the instance to decide + var interceptorResult = session.Interceptor.IsTransient(entity); + if (interceptorResult.HasValue) + return interceptorResult; + // let the persister inspect the instance to decide - return session.Interceptor.IsTransient(entity) ?? - await (session.GetEntityPersister(entityName, entity).IsTransientAsync(entity, session, cancellationToken)).ConfigureAwait(false); + if (proxy != null) + { + // The persister only deals with unproxied entities. + entity = await (proxy.HibernateLazyInitializer.GetImplementationAsync(cancellationToken)).ConfigureAwait(false); + } + + return await (session + .GetEntityPersister( + entityName, + entity) + .IsTransientAsync(entity, session, cancellationToken)).ConfigureAwait(false); } /// diff --git a/src/NHibernate/Engine/ForeignKeys.cs b/src/NHibernate/Engine/ForeignKeys.cs index ed555566733..c48dbefaca9 100644 --- a/src/NHibernate/Engine/ForeignKeys.cs +++ b/src/NHibernate/Engine/ForeignKeys.cs @@ -178,15 +178,29 @@ public static bool IsNotTransientSlow(string entityName, object entity, ISession return false; } - if (entity is INHibernateProxy proxy && proxy.HibernateLazyInitializer.IsUninitialized) + var proxy = entity as INHibernateProxy; + if (proxy?.HibernateLazyInitializer.IsUninitialized == true) { return false; } // let the interceptor inspect the instance to decide + var interceptorResult = session.Interceptor.IsTransient(entity); + if (interceptorResult.HasValue) + return interceptorResult; + // let the persister inspect the instance to decide - return session.Interceptor.IsTransient(entity) ?? - session.GetEntityPersister(entityName, entity).IsTransient(entity, session); + if (proxy != null) + { + // The persister only deals with unproxied entities. + entity = proxy.HibernateLazyInitializer.GetImplementation(); + } + + return session + .GetEntityPersister( + entityName, + entity) + .IsTransient(entity, session); } /// diff --git a/src/NHibernate/Tuple/Entity/AbstractEntityTuplizer.cs b/src/NHibernate/Tuple/Entity/AbstractEntityTuplizer.cs index 0796e603326..f4f6c4c0b60 100644 --- a/src/NHibernate/Tuple/Entity/AbstractEntityTuplizer.cs +++ b/src/NHibernate/Tuple/Entity/AbstractEntityTuplizer.cs @@ -114,11 +114,6 @@ public object Instantiate(object id) public object GetIdentifier(object entity) { - if (entity is INHibernateProxy proxy) - { - return proxy.HibernateLazyInitializer.Identifier; - } - object id; if (entityMetamodel.IdentifierProperty.IsEmbedded) {