Skip to content

Fix stateless batcher #2755

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/AsyncGenerator.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,12 @@
name: BestGuessEntityName
containingTypeName: ISessionImplementor
- conversion: Ignore
name: Contains
name: CloseSessionFromSystemTransaction
containingTypeName: ISessionImplementor
# TODO 6.0: Remove ignore rule for IStatelessSession.Close
- conversion: Ignore
name: Close
containingTypeName: IStatelessSession
- conversion: Ignore
name: GetUnsavedVersionValue
containingTypeName: UnsavedValueFactory
Expand Down
191 changes: 191 additions & 0 deletions src/NHibernate.Test/Async/NHSpecificTest/GH2750/FixtureByCode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by AsyncGenerator.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------


using System;
using System.Collections.Generic;
using System.Linq;
using System.Transactions;
using NHibernate.Cfg;
using NHibernate.Cfg.MappingSchema;
using NHibernate.Mapping.ByCode;
using NUnit.Framework;
using NHibernate.Linq;

namespace NHibernate.Test.NHSpecificTest.GH2750
{
using System.Threading.Tasks;
[TestFixture]
public class ByCodeFixtureAsync : TestCaseMappingByCode
{
protected override HbmMapping GetMappings()
{
var mapper = new ModelMapper();
mapper.Class<TestEntity>(rc => { rc.Id(x => x.Id, m => m.Generator(Generators.Assigned)); });

return mapper.CompileMappingForAllExplicitlyAddedEntities();
}

protected override void Configure(Configuration configuration)
{
configuration.SetProperty(Cfg.Environment.BatchSize, 10.ToString());
configuration.SetProperty(Cfg.Environment.UseConnectionOnSystemTransactionPrepare, true.ToString());
}

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 ShouldWorkWithOuterSystemTransactionAsync()
{
if (!Sfi.ConnectionProvider.Driver.SupportsSystemTransactions)
Assert.Ignore("System.Transactions support is required by this test");

const int count = 3;
var entities = new List<TestEntity>(count);
for (var i = 0; i < count; i++)
{
entities.Add(new TestEntity { Id = Guid.NewGuid() });
}

using (var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
using (var session = Sfi.OpenStatelessSession())
{
foreach (var entity in entities)
{
await (session.InsertAsync(entity));
}
}

transaction.Complete();
}

using (var session = OpenSession())
{
var results = await (session.QueryOver<TestEntity>().ListAsync<TestEntity>());

Assert.That(results.Count, Is.EqualTo(count));
}
}

[Test]
public async Task ShouldWorkWithInnerSystemTransactionAsync()
{
if (!Sfi.ConnectionProvider.Driver.SupportsSystemTransactions)
Assert.Ignore("System.Transactions support is required by this test");

const int count = 3;
var entities = new List<TestEntity>(count);
for (var i = 0; i < count; i++)
{
entities.Add(new TestEntity { Id = Guid.NewGuid() });
}

using (var session = Sfi.OpenStatelessSession())
{
using (var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
foreach (var entity in entities)
{
await (session.InsertAsync(entity));
}
await (session.FlushBatcherAsync());

transaction.Complete();
}
}

using (var session = OpenSession())
{
var results = await (session.QueryOver<TestEntity>().ListAsync<TestEntity>());

Assert.That(results.Count, Is.EqualTo(count));
}
}

[Test]
public async Task ShouldWorkWithoutTransactionAsync()
{
const int count = 3;
var entities = new List<TestEntity>(count);
for (var i = 0; i < count; i++)
{
entities.Add(new TestEntity { Id = Guid.NewGuid() });
}

using (var session = Sfi.OpenStatelessSession())
{
foreach (var entity in entities)
{
await (session.InsertAsync(entity));
}
}

using (var session = OpenSession())
{
var results = await (session.QueryOver<TestEntity>().ListAsync<TestEntity>());

Assert.That(results.Count, Is.EqualTo(count));
}
}

[Test]
public async Task ShouldGetEntityAfterInsertAsync()
{
var entity = new TestEntity { Id = Guid.NewGuid() };
using (var session = Sfi.OpenStatelessSession())
{
await (session.InsertAsync(entity));
Assert.That(await (session.GetAsync<TestEntity>(entity.Id)), Is.Not.Null);
}
}

[Test]
public async Task ShouldQueryEntityAfterInsertAsync()
{
var entity = new TestEntity { Id = Guid.NewGuid() };
using (var session = Sfi.OpenStatelessSession())
{
await (session.InsertAsync(entity));
Assert.That(await (session.Query<TestEntity>().FirstOrDefaultAsync(e => e.Id == entity.Id)), Is.Not.Null);
}
}

[Test]
public async Task ShouldQueryOverEntityAfterInsertAsync()
{
var entity = new TestEntity { Id = Guid.NewGuid() };
using (var session = Sfi.OpenStatelessSession())
{
await (session.InsertAsync(entity));
Assert.That(await (session.QueryOver<TestEntity>().Where(e => e.Id == entity.Id).SingleOrDefaultAsync()), Is.Not.Null);
}
}

[Test]
public async Task ShouldHqlQueryEntityAfterInsertAsync()
{
var entity = new TestEntity { Id = Guid.NewGuid() };
using (var session = Sfi.OpenStatelessSession())
{
await (session.InsertAsync(entity));
Assert.That(await (session.CreateQuery("from TestEntity where Id = :id").SetGuid("id", entity.Id).UniqueResultAsync()), Is.Not.Null);
}
}
}
}
9 changes: 9 additions & 0 deletions src/NHibernate.Test/NHSpecificTest/GH2750/Entity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;

namespace NHibernate.Test.NHSpecificTest.GH2750
{
public class TestEntity
{
public virtual Guid Id { get; set; }
}
}
179 changes: 179 additions & 0 deletions src/NHibernate.Test/NHSpecificTest/GH2750/FixtureByCode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Transactions;
using NHibernate.Cfg;
using NHibernate.Cfg.MappingSchema;
using NHibernate.Mapping.ByCode;
using NUnit.Framework;

namespace NHibernate.Test.NHSpecificTest.GH2750
{
[TestFixture]
public class ByCodeFixture : TestCaseMappingByCode
{
protected override HbmMapping GetMappings()
{
var mapper = new ModelMapper();
mapper.Class<TestEntity>(rc => { rc.Id(x => x.Id, m => m.Generator(Generators.Assigned)); });

return mapper.CompileMappingForAllExplicitlyAddedEntities();
}

protected override void Configure(Configuration configuration)
{
configuration.SetProperty(Cfg.Environment.BatchSize, 10.ToString());
configuration.SetProperty(Cfg.Environment.UseConnectionOnSystemTransactionPrepare, true.ToString());
}

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 ShouldWorkWithOuterSystemTransaction()
{
if (!Sfi.ConnectionProvider.Driver.SupportsSystemTransactions)
Assert.Ignore("System.Transactions support is required by this test");

const int count = 3;
var entities = new List<TestEntity>(count);
for (var i = 0; i < count; i++)
{
entities.Add(new TestEntity { Id = Guid.NewGuid() });
}

using (var transaction = new TransactionScope())
{
using (var session = Sfi.OpenStatelessSession())
{
foreach (var entity in entities)
{
session.Insert(entity);
}
}

transaction.Complete();
}

using (var session = OpenSession())
{
var results = session.QueryOver<TestEntity>().List<TestEntity>();

Assert.That(results.Count, Is.EqualTo(count));
}
}

[Test]
public void ShouldWorkWithInnerSystemTransaction()
{
if (!Sfi.ConnectionProvider.Driver.SupportsSystemTransactions)
Assert.Ignore("System.Transactions support is required by this test");

const int count = 3;
var entities = new List<TestEntity>(count);
for (var i = 0; i < count; i++)
{
entities.Add(new TestEntity { Id = Guid.NewGuid() });
}

using (var session = Sfi.OpenStatelessSession())
{
using (var transaction = new TransactionScope())
{
foreach (var entity in entities)
{
session.Insert(entity);
}
session.FlushBatcher();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StatelessSession has ManagedFlush method which is not exposed through interface. Maybe we should expose it instead?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I choosed FlushBatcher rather than just Flush because in principle the stateless session should never need a Flush, since it is stateless. So I want to stress what this flush is for: flushing the batcher that can be enabled with the stateless session, and which may retain some operations not yet sent to the database.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wanted to clarify that I'm not talking about the general Flush method, but about ManagedFlush.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the case of the stateless session, that is the same.

But anyway, what does it mean, ManagedFlush? I do not make any sense of it. FlushBatcher is explicit about its purpose, while that is not the case of ManagedFlush in my opinion.

public override void Flush()
{
ManagedFlush(); // NH Different behavior since ADOContext.Context is not implemented
}
public void ManagedFlush()
{
using (BeginProcess())
{
Batcher.ExecuteBatch();
}
}


transaction.Complete();
}
}

using (var session = OpenSession())
{
var results = session.QueryOver<TestEntity>().List<TestEntity>();

Assert.That(results.Count, Is.EqualTo(count));
}
}

[Test]
public void ShouldWorkWithoutTransaction()
{
const int count = 3;
var entities = new List<TestEntity>(count);
for (var i = 0; i < count; i++)
{
entities.Add(new TestEntity { Id = Guid.NewGuid() });
}

using (var session = Sfi.OpenStatelessSession())
{
foreach (var entity in entities)
{
session.Insert(entity);
}
}

using (var session = OpenSession())
{
var results = session.QueryOver<TestEntity>().List<TestEntity>();

Assert.That(results.Count, Is.EqualTo(count));
}
}

[Test]
public void ShouldGetEntityAfterInsert()
{
var entity = new TestEntity { Id = Guid.NewGuid() };
using (var session = Sfi.OpenStatelessSession())
{
session.Insert(entity);
Assert.That(session.Get<TestEntity>(entity.Id), Is.Not.Null);
}
}

[Test]
public void ShouldQueryEntityAfterInsert()
{
var entity = new TestEntity { Id = Guid.NewGuid() };
using (var session = Sfi.OpenStatelessSession())
{
session.Insert(entity);
Assert.That(session.Query<TestEntity>().FirstOrDefault(e => e.Id == entity.Id), Is.Not.Null);
}
}

[Test]
public void ShouldQueryOverEntityAfterInsert()
{
var entity = new TestEntity { Id = Guid.NewGuid() };
using (var session = Sfi.OpenStatelessSession())
{
session.Insert(entity);
Assert.That(session.QueryOver<TestEntity>().Where(e => e.Id == entity.Id).SingleOrDefault(), Is.Not.Null);
}
}

[Test]
public void ShouldHqlQueryEntityAfterInsert()
{
var entity = new TestEntity { Id = Guid.NewGuid() };
using (var session = Sfi.OpenStatelessSession())
{
session.Insert(entity);
Assert.That(session.CreateQuery("from TestEntity where Id = :id").SetGuid("id", entity.Id).UniqueResult(), Is.Not.Null);
}
}
}
}
Loading