diff --git a/src/NHibernate.Test/Async/CacheTest/BatchableCacheFixture.cs b/src/NHibernate.Test/Async/CacheTest/BatchableCacheFixture.cs index 69bd5a578e4..cb918341eef 100644 --- a/src/NHibernate.Test/Async/CacheTest/BatchableCacheFixture.cs +++ b/src/NHibernate.Test/Async/CacheTest/BatchableCacheFixture.cs @@ -219,21 +219,23 @@ public async Task MultipleGetReadOnlyTestAsync() var persister = Sfi.GetEntityPersister(typeof(ReadOnly).FullName); Assert.That(persister.Cache.Cache, Is.Not.Null); Assert.That(persister.Cache.Cache, Is.TypeOf()); - var ids = new List(); + int[] getIds; + int[] loadIds; using (var s = Sfi.OpenSession()) using (var tx = s.BeginTransaction()) { var items = await (s.Query().ToListAsync()); - ids.AddRange(items.OrderBy(o => o.Id).Select(o => o.Id)); + loadIds = getIds = items.OrderBy(o => o.Id).Select(o => o.Id).ToArray(); await (tx.CommitAsync()); } // Batch size 3 - var parentTestCases = new List>> + var parentTestCases = new List>> { // When the cache is empty, GetMultiple will be called two times. One time in type // DefaultLoadEventListener and the other time in BatchingEntityLoader. - new Tuple>( + new Tuple>( + loadIds, 0, new[] { @@ -245,7 +247,8 @@ public async Task MultipleGetReadOnlyTestAsync() ), // When there are not enough uninitialized entities after the demanded one to fill the batch, // the nearest before the demanded entity are added. - new Tuple>( + new Tuple>( + loadIds, 4, new[] { @@ -255,7 +258,8 @@ public async Task MultipleGetReadOnlyTestAsync() new[] {3, 4, 5}, null ), - new Tuple>( + new Tuple>( + loadIds, 5, new[] { @@ -265,7 +269,8 @@ public async Task MultipleGetReadOnlyTestAsync() new[] {3, 4, 5}, null ), - new Tuple>( + new Tuple>( + loadIds, 0, new[] { @@ -274,7 +279,8 @@ public async Task MultipleGetReadOnlyTestAsync() null, (i) => i % 2 == 0 // Cache all even indexes before loading ), - new Tuple>( + new Tuple>( + loadIds, 1, new[] { @@ -284,7 +290,8 @@ public async Task MultipleGetReadOnlyTestAsync() new[] {1, 3, 5}, (i) => i % 2 == 0 ), - new Tuple>( + new Tuple>( + loadIds, 5, new[] { @@ -294,7 +301,8 @@ public async Task MultipleGetReadOnlyTestAsync() new[] {1, 3, 5}, (i) => i % 2 == 0 ), - new Tuple>( + new Tuple>( + loadIds, 0, new[] { @@ -304,7 +312,8 @@ public async Task MultipleGetReadOnlyTestAsync() new[] {0, 2, 4}, (i) => i % 2 != 0 ), - new Tuple>( + new Tuple>( + loadIds, 4, new[] { @@ -313,12 +322,56 @@ public async Task MultipleGetReadOnlyTestAsync() }, new[] {0, 2, 4}, (i) => i % 2 != 0 + ), + // Tests by loading different ids + new Tuple>( + loadIds.Where((v, i) => i != 0).ToArray(), + 0, + new[] + { + new[] {0, 5, 4}, // triggered by LoadFromSecondLevelCache method of DefaultLoadEventListener type + new[] {3, 4, 5}, // triggered by Load method of BatchingEntityLoader type + }, + new[] {0, 4, 5}, + null + ), + new Tuple>( + loadIds.Where((v, i) => i != 4).ToArray(), + 4, + new[] + { + new[] {4, 5, 3}, + new[] {5, 3, 2}, + }, + new[] {3, 4, 5}, + null + ), + new Tuple>( + loadIds.Where((v, i) => i != 0).ToArray(), + 0, + new[] + { + new[] {0, 5, 4} // 0 get assembled and no further processing is done + }, + null, + (i) => i % 2 == 0 // Cache all even indexes before loading + ), + new Tuple>( + loadIds.Where((v, i) => i != 1).ToArray(), + 1, + new[] + { + new[] {1, 5, 4}, // 4 gets assembled inside LoadFromSecondLevelCache + new[] {5, 3, 2} + }, + new[] {1, 3, 5}, + (i) => i % 2 == 0 ) }; foreach (var tuple in parentTestCases) { - await (AssertMultipleCacheCallsAsync(ids, tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4)); + await (AssertMultipleCacheCallsAsync(tuple.Item1, getIds, tuple.Item2, tuple.Item3, tuple.Item4, tuple.Item5)); } } @@ -328,21 +381,23 @@ public async Task MultipleGetReadOnlyItemTestAsync() var persister = Sfi.GetEntityPersister(typeof(ReadOnlyItem).FullName); Assert.That(persister.Cache.Cache, Is.Not.Null); Assert.That(persister.Cache.Cache, Is.TypeOf()); - var ids = new List(); + int[] getIds; + int[] loadIds; using (var s = Sfi.OpenSession()) using (var tx = s.BeginTransaction()) { var items = await (s.Query().Take(6).ToListAsync()); - ids.AddRange(items.OrderBy(o => o.Id).Select(o => o.Id)); + loadIds = getIds = items.OrderBy(o => o.Id).Select(o => o.Id).ToArray(); await (tx.CommitAsync()); } // Batch size 4 - var parentTestCases = new List>> + var parentTestCases = new List>> { // When the cache is empty, GetMultiple will be called two times. One time in type // DefaultLoadEventListener and the other time in BatchingEntityLoader. - new Tuple>( + new Tuple>( + loadIds, 0, new[] { @@ -354,7 +409,8 @@ public async Task MultipleGetReadOnlyItemTestAsync() ), // When there are not enough uninitialized entities after the demanded one to fill the batch, // the nearest before the demanded entity are added. - new Tuple>( + new Tuple>( + loadIds, 4, new[] { @@ -364,7 +420,8 @@ public async Task MultipleGetReadOnlyItemTestAsync() new[] {2, 3, 4, 5}, null ), - new Tuple>( + new Tuple>( + loadIds, 5, new[] { @@ -374,7 +431,8 @@ public async Task MultipleGetReadOnlyItemTestAsync() new[] {2, 3, 4, 5}, null ), - new Tuple>( + new Tuple>( + loadIds, 0, new[] { @@ -383,7 +441,8 @@ public async Task MultipleGetReadOnlyItemTestAsync() null, (i) => i % 2 == 0 // Cache all even indexes before loading ), - new Tuple>( + new Tuple>( + loadIds, 1, new[] { @@ -393,7 +452,8 @@ public async Task MultipleGetReadOnlyItemTestAsync() new[] {1, 3, 5}, (i) => i % 2 == 0 ), - new Tuple>( + new Tuple>( + loadIds, 5, new[] { @@ -403,7 +463,8 @@ public async Task MultipleGetReadOnlyItemTestAsync() new[] {1, 3, 5}, (i) => i % 2 == 0 ), - new Tuple>( + new Tuple>( + loadIds, 0, new[] { @@ -413,7 +474,8 @@ public async Task MultipleGetReadOnlyItemTestAsync() new[] {0, 2, 4}, (i) => i % 2 != 0 ), - new Tuple>( + new Tuple>( + loadIds, 4, new[] { @@ -422,12 +484,56 @@ public async Task MultipleGetReadOnlyItemTestAsync() }, new[] {0, 2, 4}, (i) => i % 2 != 0 - ) + ), + // Tests by loading different ids + new Tuple>( + loadIds.Where((v, i) => i != 0).ToArray(), + 0, + new[] + { + new[] {0, 5, 4, 3}, // triggered by LoadFromSecondLevelCache method of DefaultLoadEventListener type + new[] {5, 4, 3, 2}, // triggered by Load method of BatchingEntityLoader type + }, + new[] {0, 5, 4, 3}, + null + ), + new Tuple>( + loadIds.Where((v, i) => i != 5).ToArray(), + 5, + new[] + { + new[] {5, 4, 3, 2}, + new[] {4, 3, 2, 1}, + }, + new[] {2, 3, 4, 5}, + null + ), + new Tuple>( + loadIds.Where((v, i) => i != 0).ToArray(), + 0, + new[] + { + new[] {0, 5, 4, 3} // 0 get assembled and no further processing is done + }, + null, + (i) => i % 2 == 0 // Cache all even indexes before loading + ), + new Tuple>( + loadIds.Where((v, i) => i != 1).ToArray(), + 1, + new[] + { + new[] {1, 5, 4, 3}, // 4 get assembled inside LoadFromSecondLevelCache + new[] {5, 3, 2, 0} + }, + new[] {1, 3, 5}, + (i) => i % 2 == 0 + ), }; foreach (var tuple in parentTestCases) { - await (AssertMultipleCacheCallsAsync(ids, tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4)); + await (AssertMultipleCacheCallsAsync(tuple.Item1, getIds, tuple.Item2, tuple.Item3, tuple.Item4, tuple.Item5)); } } @@ -764,7 +870,8 @@ public async Task QueryCacheTestAsync() } } - private async Task AssertMultipleCacheCallsAsync(List ids, int idIndex, int[][] fetchedIdIndexes, int[] putIdIndexes, Func cacheBeforeLoadFn = null, CancellationToken cancellationToken = default(CancellationToken)) + private async Task AssertMultipleCacheCallsAsync(IEnumerable loadIds, IReadOnlyList getIds, int idIndex, + int[][] fetchedIdIndexes, int[] putIdIndexes, Func cacheBeforeLoadFn = null, CancellationToken cancellationToken = default(CancellationToken)) where TEntity : CacheEntity { var persister = Sfi.GetEntityPersister(typeof(TEntity).FullName); @@ -776,7 +883,7 @@ public async Task QueryCacheTestAsync() using (var s = Sfi.OpenSession()) using (var tx = s.BeginTransaction()) { - foreach (var id in ids.Where((o, i) => cacheBeforeLoadFn(i))) + foreach (var id in getIds.Where((o, i) => cacheBeforeLoadFn(i))) { await (s.GetAsync(id, cancellationToken)); } @@ -788,12 +895,11 @@ public async Task QueryCacheTestAsync() using (var tx = s.BeginTransaction()) { cache.ClearStatistics(); - - foreach (var id in ids) + foreach (var id in loadIds) { await (s.LoadAsync(id, cancellationToken)); } - var item = await (s.GetAsync(ids[idIndex], cancellationToken)); + var item = await (s.GetAsync(getIds[idIndex], cancellationToken)); Assert.That(item, Is.Not.Null); Assert.That(cache.GetCalls, Has.Count.EqualTo(0)); Assert.That(cache.PutCalls, Has.Count.EqualTo(0)); @@ -807,14 +913,14 @@ public async Task QueryCacheTestAsync() Assert.That(cache.PutMultipleCalls, Has.Count.EqualTo(1)); Assert.That( cache.PutMultipleCalls[0].OfType().Select(o => (int) o.Key), - Is.EquivalentTo(putIdIndexes.Select(o => ids[o]))); + Is.EquivalentTo(putIdIndexes.Select(o => getIds[o]))); } for (int i = 0; i < fetchedIdIndexes.GetLength(0); i++) { Assert.That( cache.GetMultipleCalls[i].OfType().Select(o => (int) o.Key), - Is.EquivalentTo(fetchedIdIndexes[i].Select(o => ids[o]))); + Is.EquivalentTo(fetchedIdIndexes[i].Select(o => getIds[o]))); } await (tx.CommitAsync(cancellationToken)); diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH1920/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH1920/Fixture.cs new file mode 100644 index 00000000000..75693b26430 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH1920/Fixture.cs @@ -0,0 +1,62 @@ +//------------------------------------------------------------------------------ +// +// 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 NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH1920 +{ + using System.Threading.Tasks; + using System.Threading; + [TestFixture] + public class FixtureAsync : BugTestCase + { + private Guid entityId; + private Guid someOtherEntityId; + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + entityId = (Guid) session.Save(new EntityWithBatchSize { Name = "some name" }); + someOtherEntityId = (Guid) session.Save(new EntityWithBatchSize()); + + transaction.Commit(); + } + } + + [TestCase(true)] + [TestCase(false)] + public async Task CanLoadEntityAsync(bool loadProxyOfOtherEntity, CancellationToken cancellationToken = default(CancellationToken)) + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + if (loadProxyOfOtherEntity) + await (session.LoadAsync(someOtherEntityId, cancellationToken)); + + var result = await (session.GetAsync(entityId, cancellationToken)); + + Assert.That(result.Name, Is.Not.Null); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from EntityWithBatchSize").ExecuteUpdate(); + transaction.Commit(); + } + } + } +} diff --git a/src/NHibernate.Test/CacheTest/BatchableCacheFixture.cs b/src/NHibernate.Test/CacheTest/BatchableCacheFixture.cs index 7a85ccbcff9..6b175e546f0 100644 --- a/src/NHibernate.Test/CacheTest/BatchableCacheFixture.cs +++ b/src/NHibernate.Test/CacheTest/BatchableCacheFixture.cs @@ -207,21 +207,23 @@ public void MultipleGetReadOnlyTest() var persister = Sfi.GetEntityPersister(typeof(ReadOnly).FullName); Assert.That(persister.Cache.Cache, Is.Not.Null); Assert.That(persister.Cache.Cache, Is.TypeOf()); - var ids = new List(); + int[] getIds; + int[] loadIds; using (var s = Sfi.OpenSession()) using (var tx = s.BeginTransaction()) { var items = s.Query().ToList(); - ids.AddRange(items.OrderBy(o => o.Id).Select(o => o.Id)); + loadIds = getIds = items.OrderBy(o => o.Id).Select(o => o.Id).ToArray(); tx.Commit(); } // Batch size 3 - var parentTestCases = new List>> + var parentTestCases = new List>> { // When the cache is empty, GetMultiple will be called two times. One time in type // DefaultLoadEventListener and the other time in BatchingEntityLoader. - new Tuple>( + new Tuple>( + loadIds, 0, new[] { @@ -233,7 +235,8 @@ public void MultipleGetReadOnlyTest() ), // When there are not enough uninitialized entities after the demanded one to fill the batch, // the nearest before the demanded entity are added. - new Tuple>( + new Tuple>( + loadIds, 4, new[] { @@ -243,7 +246,8 @@ public void MultipleGetReadOnlyTest() new[] {3, 4, 5}, null ), - new Tuple>( + new Tuple>( + loadIds, 5, new[] { @@ -253,7 +257,8 @@ public void MultipleGetReadOnlyTest() new[] {3, 4, 5}, null ), - new Tuple>( + new Tuple>( + loadIds, 0, new[] { @@ -262,7 +267,8 @@ public void MultipleGetReadOnlyTest() null, (i) => i % 2 == 0 // Cache all even indexes before loading ), - new Tuple>( + new Tuple>( + loadIds, 1, new[] { @@ -272,7 +278,8 @@ public void MultipleGetReadOnlyTest() new[] {1, 3, 5}, (i) => i % 2 == 0 ), - new Tuple>( + new Tuple>( + loadIds, 5, new[] { @@ -282,7 +289,8 @@ public void MultipleGetReadOnlyTest() new[] {1, 3, 5}, (i) => i % 2 == 0 ), - new Tuple>( + new Tuple>( + loadIds, 0, new[] { @@ -292,7 +300,8 @@ public void MultipleGetReadOnlyTest() new[] {0, 2, 4}, (i) => i % 2 != 0 ), - new Tuple>( + new Tuple>( + loadIds, 4, new[] { @@ -301,12 +310,56 @@ public void MultipleGetReadOnlyTest() }, new[] {0, 2, 4}, (i) => i % 2 != 0 + ), + // Tests by loading different ids + new Tuple>( + loadIds.Where((v, i) => i != 0).ToArray(), + 0, + new[] + { + new[] {0, 5, 4}, // triggered by LoadFromSecondLevelCache method of DefaultLoadEventListener type + new[] {3, 4, 5}, // triggered by Load method of BatchingEntityLoader type + }, + new[] {0, 4, 5}, + null + ), + new Tuple>( + loadIds.Where((v, i) => i != 4).ToArray(), + 4, + new[] + { + new[] {4, 5, 3}, + new[] {5, 3, 2}, + }, + new[] {3, 4, 5}, + null + ), + new Tuple>( + loadIds.Where((v, i) => i != 0).ToArray(), + 0, + new[] + { + new[] {0, 5, 4} // 0 get assembled and no further processing is done + }, + null, + (i) => i % 2 == 0 // Cache all even indexes before loading + ), + new Tuple>( + loadIds.Where((v, i) => i != 1).ToArray(), + 1, + new[] + { + new[] {1, 5, 4}, // 4 gets assembled inside LoadFromSecondLevelCache + new[] {5, 3, 2} + }, + new[] {1, 3, 5}, + (i) => i % 2 == 0 ) }; foreach (var tuple in parentTestCases) { - AssertMultipleCacheCalls(ids, tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4); + AssertMultipleCacheCalls(tuple.Item1, getIds, tuple.Item2, tuple.Item3, tuple.Item4, tuple.Item5); } } @@ -316,21 +369,23 @@ public void MultipleGetReadOnlyItemTest() var persister = Sfi.GetEntityPersister(typeof(ReadOnlyItem).FullName); Assert.That(persister.Cache.Cache, Is.Not.Null); Assert.That(persister.Cache.Cache, Is.TypeOf()); - var ids = new List(); + int[] getIds; + int[] loadIds; using (var s = Sfi.OpenSession()) using (var tx = s.BeginTransaction()) { var items = s.Query().Take(6).ToList(); - ids.AddRange(items.OrderBy(o => o.Id).Select(o => o.Id)); + loadIds = getIds = items.OrderBy(o => o.Id).Select(o => o.Id).ToArray(); tx.Commit(); } // Batch size 4 - var parentTestCases = new List>> + var parentTestCases = new List>> { // When the cache is empty, GetMultiple will be called two times. One time in type // DefaultLoadEventListener and the other time in BatchingEntityLoader. - new Tuple>( + new Tuple>( + loadIds, 0, new[] { @@ -342,7 +397,8 @@ public void MultipleGetReadOnlyItemTest() ), // When there are not enough uninitialized entities after the demanded one to fill the batch, // the nearest before the demanded entity are added. - new Tuple>( + new Tuple>( + loadIds, 4, new[] { @@ -352,7 +408,8 @@ public void MultipleGetReadOnlyItemTest() new[] {2, 3, 4, 5}, null ), - new Tuple>( + new Tuple>( + loadIds, 5, new[] { @@ -362,7 +419,8 @@ public void MultipleGetReadOnlyItemTest() new[] {2, 3, 4, 5}, null ), - new Tuple>( + new Tuple>( + loadIds, 0, new[] { @@ -371,7 +429,8 @@ public void MultipleGetReadOnlyItemTest() null, (i) => i % 2 == 0 // Cache all even indexes before loading ), - new Tuple>( + new Tuple>( + loadIds, 1, new[] { @@ -381,7 +440,8 @@ public void MultipleGetReadOnlyItemTest() new[] {1, 3, 5}, (i) => i % 2 == 0 ), - new Tuple>( + new Tuple>( + loadIds, 5, new[] { @@ -391,7 +451,8 @@ public void MultipleGetReadOnlyItemTest() new[] {1, 3, 5}, (i) => i % 2 == 0 ), - new Tuple>( + new Tuple>( + loadIds, 0, new[] { @@ -401,7 +462,8 @@ public void MultipleGetReadOnlyItemTest() new[] {0, 2, 4}, (i) => i % 2 != 0 ), - new Tuple>( + new Tuple>( + loadIds, 4, new[] { @@ -410,12 +472,56 @@ public void MultipleGetReadOnlyItemTest() }, new[] {0, 2, 4}, (i) => i % 2 != 0 - ) + ), + // Tests by loading different ids + new Tuple>( + loadIds.Where((v, i) => i != 0).ToArray(), + 0, + new[] + { + new[] {0, 5, 4, 3}, // triggered by LoadFromSecondLevelCache method of DefaultLoadEventListener type + new[] {5, 4, 3, 2}, // triggered by Load method of BatchingEntityLoader type + }, + new[] {0, 5, 4, 3}, + null + ), + new Tuple>( + loadIds.Where((v, i) => i != 5).ToArray(), + 5, + new[] + { + new[] {5, 4, 3, 2}, + new[] {4, 3, 2, 1}, + }, + new[] {2, 3, 4, 5}, + null + ), + new Tuple>( + loadIds.Where((v, i) => i != 0).ToArray(), + 0, + new[] + { + new[] {0, 5, 4, 3} // 0 get assembled and no further processing is done + }, + null, + (i) => i % 2 == 0 // Cache all even indexes before loading + ), + new Tuple>( + loadIds.Where((v, i) => i != 1).ToArray(), + 1, + new[] + { + new[] {1, 5, 4, 3}, // 4 get assembled inside LoadFromSecondLevelCache + new[] {5, 3, 2, 0} + }, + new[] {1, 3, 5}, + (i) => i % 2 == 0 + ), }; foreach (var tuple in parentTestCases) { - AssertMultipleCacheCalls(ids, tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4); + AssertMultipleCacheCalls(tuple.Item1, getIds, tuple.Item2, tuple.Item3, tuple.Item4, tuple.Item5); } } @@ -752,7 +858,8 @@ public void QueryCacheTest() } } - private void AssertMultipleCacheCalls(List ids, int idIndex, int[][] fetchedIdIndexes, int[] putIdIndexes, Func cacheBeforeLoadFn = null) + private void AssertMultipleCacheCalls(IEnumerable loadIds, IReadOnlyList getIds, int idIndex, + int[][] fetchedIdIndexes, int[] putIdIndexes, Func cacheBeforeLoadFn = null) where TEntity : CacheEntity { var persister = Sfi.GetEntityPersister(typeof(TEntity).FullName); @@ -764,7 +871,7 @@ private void AssertMultipleCacheCalls(List ids, int idIndex, int[] using (var s = Sfi.OpenSession()) using (var tx = s.BeginTransaction()) { - foreach (var id in ids.Where((o, i) => cacheBeforeLoadFn(i))) + foreach (var id in getIds.Where((o, i) => cacheBeforeLoadFn(i))) { s.Get(id); } @@ -776,12 +883,11 @@ private void AssertMultipleCacheCalls(List ids, int idIndex, int[] using (var tx = s.BeginTransaction()) { cache.ClearStatistics(); - - foreach (var id in ids) + foreach (var id in loadIds) { s.Load(id); } - var item = s.Get(ids[idIndex]); + var item = s.Get(getIds[idIndex]); Assert.That(item, Is.Not.Null); Assert.That(cache.GetCalls, Has.Count.EqualTo(0)); Assert.That(cache.PutCalls, Has.Count.EqualTo(0)); @@ -795,14 +901,14 @@ private void AssertMultipleCacheCalls(List ids, int idIndex, int[] Assert.That(cache.PutMultipleCalls, Has.Count.EqualTo(1)); Assert.That( cache.PutMultipleCalls[0].OfType().Select(o => (int) o.Key), - Is.EquivalentTo(putIdIndexes.Select(o => ids[o]))); + Is.EquivalentTo(putIdIndexes.Select(o => getIds[o]))); } for (int i = 0; i < fetchedIdIndexes.GetLength(0); i++) { Assert.That( cache.GetMultipleCalls[i].OfType().Select(o => (int) o.Key), - Is.EquivalentTo(fetchedIdIndexes[i].Select(o => ids[o]))); + Is.EquivalentTo(fetchedIdIndexes[i].Select(o => getIds[o]))); } tx.Commit(); diff --git a/src/NHibernate.Test/NHSpecificTest/GH1920/EntityWithBatchSize.cs b/src/NHibernate.Test/NHSpecificTest/GH1920/EntityWithBatchSize.cs new file mode 100644 index 00000000000..8cb470eaf51 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH1920/EntityWithBatchSize.cs @@ -0,0 +1,10 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH1920 +{ + public class EntityWithBatchSize + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH1920/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH1920/Fixture.cs new file mode 100644 index 00000000000..e8f21e9e436 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH1920/Fixture.cs @@ -0,0 +1,50 @@ +using System; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH1920 +{ + [TestFixture] + public class Fixture : BugTestCase + { + private Guid entityId; + private Guid someOtherEntityId; + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + entityId = (Guid) session.Save(new EntityWithBatchSize { Name = "some name" }); + someOtherEntityId = (Guid) session.Save(new EntityWithBatchSize()); + + transaction.Commit(); + } + } + + [TestCase(true)] + [TestCase(false)] + public void CanLoadEntity(bool loadProxyOfOtherEntity) + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + if (loadProxyOfOtherEntity) + session.Load(someOtherEntityId); + + var result = session.Get(entityId); + + Assert.That(result.Name, Is.Not.Null); + } + } + + protected override void OnTearDown() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from EntityWithBatchSize").ExecuteUpdate(); + transaction.Commit(); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH1920/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH1920/Mappings.hbm.xml new file mode 100644 index 00000000000..602b6c8cad5 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH1920/Mappings.hbm.xml @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/src/NHibernate/Async/Engine/BatchFetchQueue.cs b/src/NHibernate/Async/Engine/BatchFetchQueue.cs index d8a1807a043..83cbbd1a928 100644 --- a/src/NHibernate/Async/Engine/BatchFetchQueue.cs +++ b/src/NHibernate/Async/Engine/BatchFetchQueue.cs @@ -102,7 +102,7 @@ async Task CheckCacheAndProcessResultAsync() ? collectionKeys.Count - Math.Min(batchSize, collectionKeys.Count) : 0; var toIndex = collectionKeys.Count - 1; - var indexes = GetSortedKeyIndexes(collectionKeys, keyIndex.Value, fromIndex, toIndex); + var indexes = GetSortedKeyIndexes(collectionKeys, keyIndex, fromIndex, toIndex); if (batchableCache == null) { for (var j = 0; j < collectionKeys.Count; j++) @@ -116,7 +116,6 @@ async Task CheckCacheAndProcessResultAsync() else { var results = await (AreCachedAsync(collectionKeys, indexes, collectionPersister, batchableCache, checkCache, cancellationToken)).ConfigureAwait(false); - var k = toIndex; for (var j = 0; j < results.Length; j++) { if (!results[j] && await (ProcessKeyAsync(collectionKeys[indexes[j]].Key, true)).ConfigureAwait(false)) @@ -153,7 +152,7 @@ Task ProcessKeyAsync(KeyValuePair return Task.FromResult(false); } - if (checkForEnd && (index >= keyIndex.Value + batchSize || index == map.Count)) + if (checkForEnd && (index == map.Count || index >= keyIndex.Value + batchSize)) { return Task.FromResult(true); } @@ -167,7 +166,7 @@ Task ProcessKeyAsync(KeyValuePair } else if (!checkCache || batchableCache == null) { - if (!keyIndex.HasValue || index < keyIndex.Value) + if (index < map.Count && (!keyIndex.HasValue || index < keyIndex.Value)) { collectionKeys.Add(new KeyValuePair, int>(me, index)); return Task.FromResult(false); @@ -204,10 +203,10 @@ Task ProcessKeyAsync(KeyValuePair if (i == batchSize) { i = 1; // End of array, start filling again from start - if (keyIndex.HasValue) + if (index == map.Count || keyIndex.HasValue) { checkForEnd = true; - return Task.FromResult(index >= keyIndex.Value + batchSize || index == map.Count); + return Task.FromResult(index == map.Count || index >= keyIndex.Value + batchSize); } } return Task.FromResult(false); @@ -295,7 +294,7 @@ async Task CheckCacheAndProcessResultAsync() ? entityKeys.Count - Math.Min(batchSize, entityKeys.Count) : 0; var toIndex = entityKeys.Count - 1; - var indexes = GetSortedKeyIndexes(entityKeys, idIndex.Value, fromIndex, toIndex); + var indexes = GetSortedKeyIndexes(entityKeys, idIndex, fromIndex, toIndex); if (batchableCache == null) { for (var j = 0; j < entityKeys.Count; j++) @@ -309,7 +308,6 @@ async Task CheckCacheAndProcessResultAsync() else { var results = await (AreCachedAsync(entityKeys, indexes, persister, batchableCache, checkCache, cancellationToken)).ConfigureAwait(false); - var k = toIndex; for (var j = 0; j < results.Length; j++) { if (!results[j] && await (ProcessKeyAsync(entityKeys[indexes[j]].Key, true)).ConfigureAwait(false)) @@ -329,7 +327,7 @@ async Task CheckCacheAndProcessResultAsync() Task ProcessKeyAsync(EntityKey key, bool ignoreCache = false) { //TODO: this needn't exclude subclasses... - if (checkForEnd && (index >= idIndex.Value + batchSize || index == set.Count)) + if (checkForEnd && (index == set.Count || index >= idIndex.Value + batchSize)) { return Task.FromResult(true); } @@ -339,7 +337,7 @@ Task ProcessKeyAsync(EntityKey key, bool ignoreCache = false) } else if (!checkCache || batchableCache == null) { - if (!idIndex.HasValue || index < idIndex.Value) + if (index < set.Count && (!idIndex.HasValue || index < idIndex.Value)) { entityKeys.Add(new KeyValuePair(key, index)); return Task.FromResult(false); @@ -368,10 +366,10 @@ Task ProcessKeyAsync(EntityKey key, bool ignoreCache = false) if (i == batchSize) { i = 1; // End of array, start filling again from start - if (idIndex.HasValue) + if (index == set.Count || idIndex.HasValue) { checkForEnd = true; - return Task.FromResult(index >= idIndex.Value + batchSize || index == set.Count); + return Task.FromResult(index == set.Count || index >= idIndex.Value + batchSize); } } return Task.FromResult(false); @@ -381,7 +379,7 @@ Task ProcessKeyAsync(EntityKey key, bool ignoreCache = false) /// /// Checks whether the given entity key indexes are cached. /// - /// The list of pairs of entity keys and thier indexes. + /// The list of pairs of entity keys and their indexes. /// The array of indexes of that have to be checked. /// The entity persister. /// The batchable cache. @@ -419,7 +417,7 @@ private async Task AreCachedAsync(List> ent /// /// Checks whether the given collection key indexes are cached. /// - /// The list of pairs of collection entries and thier indexes. + /// The list of pairs of collection entries and their indexes. /// The array of indexes of that have to be checked. /// The collection persister. /// The batchable cache. diff --git a/src/NHibernate/Engine/BatchFetchQueue.cs b/src/NHibernate/Engine/BatchFetchQueue.cs index 05426554fb5..2a4c61160f4 100644 --- a/src/NHibernate/Engine/BatchFetchQueue.cs +++ b/src/NHibernate/Engine/BatchFetchQueue.cs @@ -271,7 +271,7 @@ bool CheckCacheAndProcessResult() ? collectionKeys.Count - Math.Min(batchSize, collectionKeys.Count) : 0; var toIndex = collectionKeys.Count - 1; - var indexes = GetSortedKeyIndexes(collectionKeys, keyIndex.Value, fromIndex, toIndex); + var indexes = GetSortedKeyIndexes(collectionKeys, keyIndex, fromIndex, toIndex); if (batchableCache == null) { for (var j = 0; j < collectionKeys.Count; j++) @@ -285,7 +285,6 @@ bool CheckCacheAndProcessResult() else { var results = AreCached(collectionKeys, indexes, collectionPersister, batchableCache, checkCache); - var k = toIndex; for (var j = 0; j < results.Length; j++) { if (!results[j] && ProcessKey(collectionKeys[indexes[j]].Key, true)) @@ -322,7 +321,7 @@ bool ProcessKey(KeyValuePair me, bool ig return false; } - if (checkForEnd && (index >= keyIndex.Value + batchSize || index == map.Count)) + if (checkForEnd && (index == map.Count || index >= keyIndex.Value + batchSize)) { return true; } @@ -336,7 +335,7 @@ bool ProcessKey(KeyValuePair me, bool ig } else if (!checkCache || batchableCache == null) { - if (!keyIndex.HasValue || index < keyIndex.Value) + if (index < map.Count && (!keyIndex.HasValue || index < keyIndex.Value)) { collectionKeys.Add(new KeyValuePair, int>(me, index)); return false; @@ -373,10 +372,10 @@ bool ProcessKey(KeyValuePair me, bool ig if (i == batchSize) { i = 1; // End of array, start filling again from start - if (keyIndex.HasValue) + if (index == map.Count || keyIndex.HasValue) { checkForEnd = true; - return index >= keyIndex.Value + batchSize || index == map.Count; + return index == map.Count || index >= keyIndex.Value + batchSize; } } return false; @@ -455,7 +454,7 @@ bool CheckCacheAndProcessResult() ? entityKeys.Count - Math.Min(batchSize, entityKeys.Count) : 0; var toIndex = entityKeys.Count - 1; - var indexes = GetSortedKeyIndexes(entityKeys, idIndex.Value, fromIndex, toIndex); + var indexes = GetSortedKeyIndexes(entityKeys, idIndex, fromIndex, toIndex); if (batchableCache == null) { for (var j = 0; j < entityKeys.Count; j++) @@ -469,7 +468,6 @@ bool CheckCacheAndProcessResult() else { var results = AreCached(entityKeys, indexes, persister, batchableCache, checkCache); - var k = toIndex; for (var j = 0; j < results.Length; j++) { if (!results[j] && ProcessKey(entityKeys[indexes[j]].Key, true)) @@ -489,7 +487,7 @@ bool CheckCacheAndProcessResult() bool ProcessKey(EntityKey key, bool ignoreCache = false) { //TODO: this needn't exclude subclasses... - if (checkForEnd && (index >= idIndex.Value + batchSize || index == set.Count)) + if (checkForEnd && (index == set.Count || index >= idIndex.Value + batchSize)) { return true; } @@ -499,7 +497,7 @@ bool ProcessKey(EntityKey key, bool ignoreCache = false) } else if (!checkCache || batchableCache == null) { - if (!idIndex.HasValue || index < idIndex.Value) + if (index < set.Count && (!idIndex.HasValue || index < idIndex.Value)) { entityKeys.Add(new KeyValuePair(key, index)); return false; @@ -528,10 +526,10 @@ bool ProcessKey(EntityKey key, bool ignoreCache = false) if (i == batchSize) { i = 1; // End of array, start filling again from start - if (idIndex.HasValue) + if (index == set.Count || idIndex.HasValue) { checkForEnd = true; - return index >= idIndex.Value + batchSize || index == set.Count; + return index == set.Count || index >= idIndex.Value + batchSize; } } return false; @@ -541,7 +539,7 @@ bool ProcessKey(EntityKey key, bool ignoreCache = false) /// /// Checks whether the given entity key indexes are cached. /// - /// The list of pairs of entity keys and thier indexes. + /// The list of pairs of entity keys and their indexes. /// The array of indexes of that have to be checked. /// The entity persister. /// The batchable cache. @@ -577,7 +575,7 @@ private bool[] AreCached(List> entityKeys, int[] ke /// /// Checks whether the given collection key indexes are cached. /// - /// The list of pairs of collection entries and thier indexes. + /// The list of pairs of collection entries and their indexes. /// The array of indexes of that have to be checked. /// The collection persister. /// The batchable cache. @@ -612,23 +610,23 @@ private bool[] AreCached(List - /// Sorts the given keys by thier indexes, where the keys that are after the demanded key will be located + /// Sorts the given keys by their indexes, where the keys that are after the demanded key will be located /// at the start and the remaining indexes at the end of the returned array. /// /// The type of the key - /// The list of pairs of keys and thier indexes. + /// The list of pairs of keys and their indexes. /// The index of the demanded key /// The index where the sorting will begin. /// The index where the sorting will end. /// An array of sorted key indexes. - private static int[] GetSortedKeyIndexes(List> keys, int keyIndex, int fromIndex, int toIndex) + private static int[] GetSortedKeyIndexes(List> keys, int? keyIndex, int fromIndex, int toIndex) { var result = new int[Math.Abs(toIndex - fromIndex) + 1]; var lowerIndexes = new List(); var i = 0; for (var j = fromIndex; j <= toIndex; j++) { - if (keys[j].Value < keyIndex) + if (!keyIndex.HasValue || keys[j].Value < keyIndex) { lowerIndexes.Add(j); }