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..6b94d0fc903 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2043/Fixture.cs @@ -0,0 +1,304 @@ +//------------------------------------------------------------------------------ +// +// 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.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 + { + private Guid _entityWithClassProxy2Id; + 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); + rc.Property(x => x.Name); + }); + + mapper.Class(rc => + { + rc.Table("IProxyDefinition"); + 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))); + }); + + 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()) + using (var transaction = session.BeginTransaction()) + { + var entityCP1 = new EntityWithClassProxyDefinition + { + Id = Guid.NewGuid(), + Name = "Name 1" + }; + + var entityCP2 = new EntityWithClassProxyDefinition + { + Id = Guid.NewGuid(), + Name = "Name 2" + }; + _entityWithClassProxy2Id = entityCP2.Id; + + var entityIP1 = new EntityWithInterfaceProxyDefinition + { + Id = Guid.NewGuid(), + Name = "Name 1" + }; + + 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(); + transaction.Commit(); + } + } + + 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.CommitAsync()); + } + } + + [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)); + + await (session.UpdateAsync(entityToUpdate)); + await (session.FlushAsync()); + 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/EntityWithClassLookup.cs b/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithClassLookup.cs new file mode 100644 index 00000000000..637c5ff5bda --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithClassLookup.cs @@ -0,0 +1,13 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH2043 +{ + 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..4829de3012c --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithClassProxyDefinition.cs @@ -0,0 +1,11 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH2043 +{ + 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..6bcee043a49 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithInterfaceLookup.cs @@ -0,0 +1,13 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH2043 +{ + 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..198ddadfa4a --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2043/EntityWithInterfaceProxyDefinition.cs @@ -0,0 +1,11 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH2043 +{ + 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..a289226a6fc --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2043/Fixture.cs @@ -0,0 +1,291 @@ +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 +{ + [TestFixture] + public class Fixture : TestCaseMappingByCode + { + private Guid _entityWithClassProxy2Id; + 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); + rc.Property(x => x.Name); + }); + + mapper.Class(rc => + { + rc.Table("IProxyDefinition"); + 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))); + }); + + 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()) + using (var transaction = session.BeginTransaction()) + { + var entityCP1 = new EntityWithClassProxyDefinition + { + Id = Guid.NewGuid(), + Name = "Name 1" + }; + + var entityCP2 = new EntityWithClassProxyDefinition + { + Id = Guid.NewGuid(), + Name = "Name 2" + }; + _entityWithClassProxy2Id = entityCP2.Id; + + var entityIP1 = new EntityWithInterfaceProxyDefinition + { + Id = Guid.NewGuid(), + Name = "Name 1" + }; + + 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(); + transaction.Commit(); + } + } + + 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.Commit(); + } + } + + [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); + + session.Update(entityToUpdate); + session.Flush(); + 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; + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH2043/IEntityProxy.cs b/src/NHibernate.Test/NHSpecificTest/GH2043/IEntityProxy.cs new file mode 100644 index 00000000000..76b48d30eb1 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH2043/IEntityProxy.cs @@ -0,0 +1,11 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH2043 +{ + public interface IEntityProxy + { + Guid Id { get; set; } + + string Name { get; set; } + } +} diff --git a/src/NHibernate/Async/Engine/ForeignKeys.cs b/src/NHibernate/Async/Engine/ForeignKeys.cs index 11597f5fcd1..441dbcb87d4 100644 --- a/src/NHibernate/Async/Engine/ForeignKeys.cs +++ b/src/NHibernate/Async/Engine/ForeignKeys.cs @@ -181,10 +181,29 @@ public static async Task IsNotTransientSlowAsync(string entityName, object return false; } + 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 3c6df63a815..c48dbefaca9 100644 --- a/src/NHibernate/Engine/ForeignKeys.cs +++ b/src/NHibernate/Engine/ForeignKeys.cs @@ -178,10 +178,29 @@ public static bool IsNotTransientSlow(string entityName, object entity, ISession return false; } + 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); } ///