From 05e148e5699174fc1b1a3cad49b9833d8e1885ff Mon Sep 17 00:00:00 2001 From: chen__h Date: Wed, 16 Nov 2022 23:24:53 +0800 Subject: [PATCH 1/3] fix issue that EntityUpdateAction increments version despite veto on update --- .../NHSpecificTest/GH3198/FixtureByCode.cs | 139 ++++++++++++++++++ .../NHSpecificTest/GH3198/FixtureByCode.cs | 113 ++++++++++++++ src/NHibernate/Action/EntityUpdateAction.cs | 16 +- .../Async/Action/EntityUpdateAction.cs | 16 +- 4 files changed, 270 insertions(+), 14 deletions(-) create mode 100644 src/NHibernate.Test/Async/NHSpecificTest/GH3198/FixtureByCode.cs create mode 100644 src/NHibernate.Test/NHSpecificTest/GH3198/FixtureByCode.cs diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3198/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3198/FixtureByCode.cs new file mode 100644 index 00000000000..36ac5bc4ef5 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3198/FixtureByCode.cs @@ -0,0 +1,139 @@ +//------------------------------------------------------------------------------ +// +// 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.Threading; +using System.Threading.Tasks; +using NHibernate.Cfg; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Event; +using NHibernate.Mapping.ByCode; +using NHibernate.Type; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH3198 +{ + /// + /// Fixture using 'by code' mappings + /// + /// + /// This fixture is identical to except the mapping is performed + /// by code in the GetMappings method, and does not require the Mappings.hbm.xml file. Use this approach + /// if you prefer. + /// + [TestFixture] + public partial class ByCodeFixtureAsync : TestCaseMappingByCode + { + private static readonly int EXAMPLE_ID_VALUE = 1; + private readonly testEventListener listener = new testEventListener(); + + protected override void Configure(Configuration configuration) + { + // A listener always returning true + configuration.EventListeners.PreUpdateEventListeners = new IPreUpdateEventListener[] + { + listener + }; + base.Configure(configuration); + } + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Table("Entity"); + rc.Id(x => x.id, m => m.Generator(Generators.Assigned)); + rc.Property(x => x.name, x=>x.Type()); + rc.Version(x => x.Version, vm => { }); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var e1 = new Entity { id = EXAMPLE_ID_VALUE, name = "old_name" }; + session.Save(e1); + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + } + + [Test] + public async Task testVersionNotChangedWhenPreUpdateEventVetodAsync() + { + using (var session = OpenSession()) + { + var entity = await (session.LoadAsync(EXAMPLE_ID_VALUE)); + + entity.name = "new_name"; + await (session.UpdateAsync(entity)); + + var versionBeforeFlush = entity.Version; + + await (session.FlushAsync()); + + var versionAfterflush = entity.Version; + + Assert.That(versionAfterflush, Is.EqualTo(versionBeforeFlush), "The entity version must not change when update is vetoed"); + } + } + + // A listener always returning true + public partial class testEventListener : IPreUpdateEventListener + { + public static testEventListener Instance = new testEventListener(); + + public bool Executed { get; set; } + + public bool FoundAny { get; set; } + public Task OnPreUpdateAsync(PreUpdateEvent @event, CancellationToken cancellationToken) + { + return Task.FromResult(true); + } + public bool OnPreUpdate(PreUpdateEvent @event) + { + return true; + } + + } + public partial class Entity + { + public virtual int id { get; set; } + public virtual string name { get; set; } + public virtual int Version { get; set; } + } + } + public partial class ByCodeFixture : TestCaseMappingByCode + { + + public partial class testEventListener : IPreUpdateEventListener + { + public Task OnPreUpdateAsync(PreUpdateEvent @event, CancellationToken cancellationToken) + { + return Task.FromResult(true); + } + + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH3198/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH3198/FixtureByCode.cs new file mode 100644 index 00000000000..0994a0b64a2 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3198/FixtureByCode.cs @@ -0,0 +1,113 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using NHibernate.Cfg; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Event; +using NHibernate.Mapping.ByCode; +using NHibernate.Type; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH3198 +{ + /// + /// Fixture using 'by code' mappings + /// + /// + /// This fixture is identical to except the mapping is performed + /// by code in the GetMappings method, and does not require the Mappings.hbm.xml file. Use this approach + /// if you prefer. + /// + [TestFixture] + public partial class ByCodeFixture : TestCaseMappingByCode + { + private static readonly int EXAMPLE_ID_VALUE = 1; + private readonly testEventListener listener = new testEventListener(); + + protected override void Configure(Configuration configuration) + { + // A listener always returning true + configuration.EventListeners.PreUpdateEventListeners = new IPreUpdateEventListener[] + { + listener + }; + base.Configure(configuration); + } + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Table("Entity"); + rc.Id(x => x.id, m => m.Generator(Generators.Assigned)); + rc.Property(x => x.name, x=>x.Type()); + rc.Version(x => x.Version, vm => { }); + }); + + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var e1 = new Entity { id = EXAMPLE_ID_VALUE, name = "old_name" }; + session.Save(e1); + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + } + + [Test] + public void testVersionNotChangedWhenPreUpdateEventVetod() + { + using (var session = OpenSession()) + { + var entity = session.Load(EXAMPLE_ID_VALUE); + + entity.name = "new_name"; + session.Update(entity); + + var versionBeforeFlush = entity.Version; + + session.Flush(); + + var versionAfterflush = entity.Version; + + Assert.That(versionAfterflush, Is.EqualTo(versionBeforeFlush), "The entity version must not change when update is vetoed"); + } + } + + // A listener always returning true + public partial class testEventListener : IPreUpdateEventListener + { + public static testEventListener Instance = new testEventListener(); + + public bool Executed { get; set; } + + public bool FoundAny { get; set; } + public bool OnPreUpdate(PreUpdateEvent @event) + { + return true; + } + + } + public partial class Entity + { + public virtual int id { get; set; } + public virtual string name { get; set; } + public virtual int Version { get; set; } + } + } +} diff --git a/src/NHibernate/Action/EntityUpdateAction.cs b/src/NHibernate/Action/EntityUpdateAction.cs index 8b3ded4b24d..76e957a860f 100644 --- a/src/NHibernate/Action/EntityUpdateAction.cs +++ b/src/NHibernate/Action/EntityUpdateAction.cs @@ -54,8 +54,11 @@ public override void Execute() { stopwatch = Stopwatch.StartNew(); } - - bool veto = PreUpdate(); + + if (PreUpdate()) + { + return; + } ISessionFactoryImplementor factory = Session.Factory; @@ -74,10 +77,9 @@ public override void Execute() slock = persister.Cache.Lock(ck, previousVersion); } - if (!veto) - { - persister.Update(id, state, dirtyFields, hasDirtyCollection, previousState, previousVersion, instance, null, session); - } + + persister.Update(id, state, dirtyFields, hasDirtyCollection, previousState, previousVersion, instance, null, session); + EntityEntry entry = Session.PersistenceContext.GetEntry(instance); if (entry == null) @@ -128,7 +130,7 @@ public override void Execute() PostUpdate(); - if (statsEnabled && !veto) + if (statsEnabled) { stopwatch.Stop(); factory.StatisticsImplementor.UpdateEntity(Persister.EntityName, stopwatch.Elapsed); diff --git a/src/NHibernate/Async/Action/EntityUpdateAction.cs b/src/NHibernate/Async/Action/EntityUpdateAction.cs index 89b065f40aa..677cc8b5103 100644 --- a/src/NHibernate/Async/Action/EntityUpdateAction.cs +++ b/src/NHibernate/Async/Action/EntityUpdateAction.cs @@ -39,8 +39,11 @@ public override async Task ExecuteAsync(CancellationToken cancellationToken) { stopwatch = Stopwatch.StartNew(); } - - bool veto = await (PreUpdateAsync(cancellationToken)).ConfigureAwait(false); + + if (await (PreUpdateAsync(cancellationToken)).ConfigureAwait(false)) + { + return; + } ISessionFactoryImplementor factory = Session.Factory; @@ -59,10 +62,9 @@ public override async Task ExecuteAsync(CancellationToken cancellationToken) slock = await (persister.Cache.LockAsync(ck, previousVersion, cancellationToken)).ConfigureAwait(false); } - if (!veto) - { - await (persister.UpdateAsync(id, state, dirtyFields, hasDirtyCollection, previousState, previousVersion, instance, null, session, cancellationToken)).ConfigureAwait(false); - } + + await (persister.UpdateAsync(id, state, dirtyFields, hasDirtyCollection, previousState, previousVersion, instance, null, session, cancellationToken)).ConfigureAwait(false); + EntityEntry entry = Session.PersistenceContext.GetEntry(instance); if (entry == null) @@ -113,7 +115,7 @@ public override async Task ExecuteAsync(CancellationToken cancellationToken) await (PostUpdateAsync(cancellationToken)).ConfigureAwait(false); - if (statsEnabled && !veto) + if (statsEnabled) { stopwatch.Stop(); factory.StatisticsImplementor.UpdateEntity(Persister.EntityName, stopwatch.Elapsed); From e497dc18c47742a9af06ab731a3bd5cfffab8009 Mon Sep 17 00:00:00 2001 From: Alex Zaytsev Date: Wed, 16 Nov 2022 20:18:31 +0000 Subject: [PATCH 2/3] Code cleanup --- .../NHSpecificTest/GH3198/FixtureByCode.cs | 38 +++++++------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/src/NHibernate.Test/NHSpecificTest/GH3198/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH3198/FixtureByCode.cs index 0994a0b64a2..5702dec91da 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH3198/FixtureByCode.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH3198/FixtureByCode.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; using NHibernate.Cfg; @@ -10,37 +10,29 @@ namespace NHibernate.Test.NHSpecificTest.GH3198 { - /// - /// Fixture using 'by code' mappings - /// - /// - /// This fixture is identical to except the mapping is performed - /// by code in the GetMappings method, and does not require the Mappings.hbm.xml file. Use this approach - /// if you prefer. - /// [TestFixture] public partial class ByCodeFixture : TestCaseMappingByCode { - private static readonly int EXAMPLE_ID_VALUE = 1; - private readonly testEventListener listener = new testEventListener(); + private const int EXAMPLE_ID_VALUE = 1; protected override void Configure(Configuration configuration) { // A listener always returning true configuration.EventListeners.PreUpdateEventListeners = new IPreUpdateEventListener[] { - listener + new TestEventListener() }; base.Configure(configuration); } + protected override HbmMapping GetMappings() { var mapper = new ModelMapper(); mapper.Class(rc => { rc.Table("Entity"); - rc.Id(x => x.id, m => m.Generator(Generators.Assigned)); - rc.Property(x => x.name, x=>x.Type()); + rc.Id(x => x.Id, m => m.Generator(Generators.Assigned)); + rc.Property(x => x.Name, x => x.Type()); rc.Version(x => x.Version, vm => { }); }); @@ -52,7 +44,7 @@ protected override void OnSetUp() using (var session = OpenSession()) using (var transaction = session.BeginTransaction()) { - var e1 = new Entity { id = EXAMPLE_ID_VALUE, name = "old_name" }; + var e1 = new Entity { Id = EXAMPLE_ID_VALUE, Name = "old_name" }; session.Save(e1); transaction.Commit(); } @@ -70,13 +62,13 @@ protected override void OnTearDown() } [Test] - public void testVersionNotChangedWhenPreUpdateEventVetod() + public void TestVersionNotChangedWhenPreUpdateEventVetod() { using (var session = OpenSession()) { var entity = session.Load(EXAMPLE_ID_VALUE); - entity.name = "new_name"; + entity.Name = "new_name"; session.Update(entity); var versionBeforeFlush = entity.Version; @@ -90,23 +82,21 @@ public void testVersionNotChangedWhenPreUpdateEventVetod() } // A listener always returning true - public partial class testEventListener : IPreUpdateEventListener + public partial class TestEventListener : IPreUpdateEventListener { - public static testEventListener Instance = new testEventListener(); - public bool Executed { get; set; } - public bool FoundAny { get; set; } + public bool OnPreUpdate(PreUpdateEvent @event) { return true; } - } + public partial class Entity { - public virtual int id { get; set; } - public virtual string name { get; set; } + public virtual int Id { get; set; } + public virtual string Name { get; set; } public virtual int Version { get; set; } } } From 6f9b9a0dc43881e2ef7981a8eb8e2300a62cfe86 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 16 Nov 2022 20:21:03 +0000 Subject: [PATCH 3/3] Generate async files --- .../NHSpecificTest/GH3198/FixtureByCode.cs | 41 ++++++++----------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3198/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3198/FixtureByCode.cs index 36ac5bc4ef5..a95cbcebfca 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH3198/FixtureByCode.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3198/FixtureByCode.cs @@ -20,37 +20,29 @@ namespace NHibernate.Test.NHSpecificTest.GH3198 { - /// - /// Fixture using 'by code' mappings - /// - /// - /// This fixture is identical to except the mapping is performed - /// by code in the GetMappings method, and does not require the Mappings.hbm.xml file. Use this approach - /// if you prefer. - /// [TestFixture] public partial class ByCodeFixtureAsync : TestCaseMappingByCode { - private static readonly int EXAMPLE_ID_VALUE = 1; - private readonly testEventListener listener = new testEventListener(); + private const int EXAMPLE_ID_VALUE = 1; protected override void Configure(Configuration configuration) { // A listener always returning true configuration.EventListeners.PreUpdateEventListeners = new IPreUpdateEventListener[] { - listener + new TestEventListener() }; base.Configure(configuration); } + protected override HbmMapping GetMappings() { var mapper = new ModelMapper(); mapper.Class(rc => { rc.Table("Entity"); - rc.Id(x => x.id, m => m.Generator(Generators.Assigned)); - rc.Property(x => x.name, x=>x.Type()); + rc.Id(x => x.Id, m => m.Generator(Generators.Assigned)); + rc.Property(x => x.Name, x => x.Type()); rc.Version(x => x.Version, vm => { }); }); @@ -62,7 +54,7 @@ protected override void OnSetUp() using (var session = OpenSession()) using (var transaction = session.BeginTransaction()) { - var e1 = new Entity { id = EXAMPLE_ID_VALUE, name = "old_name" }; + var e1 = new Entity { Id = EXAMPLE_ID_VALUE, Name = "old_name" }; session.Save(e1); transaction.Commit(); } @@ -80,13 +72,13 @@ protected override void OnTearDown() } [Test] - public async Task testVersionNotChangedWhenPreUpdateEventVetodAsync() + public async Task TestVersionNotChangedWhenPreUpdateEventVetodAsync() { using (var session = OpenSession()) { var entity = await (session.LoadAsync(EXAMPLE_ID_VALUE)); - entity.name = "new_name"; + entity.Name = "new_name"; await (session.UpdateAsync(entity)); var versionBeforeFlush = entity.Version; @@ -100,40 +92,39 @@ public async Task testVersionNotChangedWhenPreUpdateEventVetodAsync() } // A listener always returning true - public partial class testEventListener : IPreUpdateEventListener + public partial class TestEventListener : IPreUpdateEventListener { - public static testEventListener Instance = new testEventListener(); - public bool Executed { get; set; } - public bool FoundAny { get; set; } + public Task OnPreUpdateAsync(PreUpdateEvent @event, CancellationToken cancellationToken) { return Task.FromResult(true); } + public bool OnPreUpdate(PreUpdateEvent @event) { return true; } - } + public partial class Entity { - public virtual int id { get; set; } - public virtual string name { get; set; } + public virtual int Id { get; set; } + public virtual string Name { get; set; } public virtual int Version { get; set; } } } public partial class ByCodeFixture : TestCaseMappingByCode { - public partial class testEventListener : IPreUpdateEventListener + public partial class TestEventListener : IPreUpdateEventListener { + public Task OnPreUpdateAsync(PreUpdateEvent @event, CancellationToken cancellationToken) { return Task.FromResult(true); } - } } }