diff --git a/src/AsyncGenerator.yml b/src/AsyncGenerator.yml index 68ae0a1c735..6cacdafcbfe 100644 --- a/src/AsyncGenerator.yml +++ b/src/AsyncGenerator.yml @@ -110,6 +110,10 @@ - conversion: Ignore name: GetEnumerator containingTypeName: IFutureEnumerable + - conversion: Ignore + name: AsyncEnumerable + - conversion: Ignore + name: AsyncEnumerableFilter - conversion: ToAsync name: ExecuteReader containingTypeName: IBatcher @@ -221,6 +225,8 @@ - conversion: Ignore anyBaseTypeRule: IsTestCase executionPhase: PostProviders + ignoreSearchForAsyncCounterparts: + - name: Enumerable ignoreDocuments: - filePathEndsWith: Linq/MathTests.cs - filePathEndsWith: Linq/ExpressionSessionLeakTest.cs diff --git a/src/NHibernate.DomainModel/Northwind/Entities/INamedEntity.cs b/src/NHibernate.DomainModel/Northwind/Entities/INamedEntity.cs new file mode 100644 index 00000000000..32d10beb376 --- /dev/null +++ b/src/NHibernate.DomainModel/Northwind/Entities/INamedEntity.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NHibernate.DomainModel.Northwind.Entities +{ + public interface INamedEntity + { + int Id { get; } + + string Name { get; } + } +} diff --git a/src/NHibernate.DomainModel/Northwind/Entities/Role.cs b/src/NHibernate.DomainModel/Northwind/Entities/Role.cs index 2643c03d285..93434dfadab 100644 --- a/src/NHibernate.DomainModel/Northwind/Entities/Role.cs +++ b/src/NHibernate.DomainModel/Northwind/Entities/Role.cs @@ -1,6 +1,6 @@ namespace NHibernate.DomainModel.Northwind.Entities { - public class Role + public class Role : INamedEntity { public virtual int Id { get; set; } public virtual string Name { get; set; } diff --git a/src/NHibernate.DomainModel/Northwind/Entities/User.cs b/src/NHibernate.DomainModel/Northwind/Entities/User.cs index c23e667be9b..2899037fdb2 100644 --- a/src/NHibernate.DomainModel/Northwind/Entities/User.cs +++ b/src/NHibernate.DomainModel/Northwind/Entities/User.cs @@ -30,7 +30,7 @@ public interface IUser IUser ModifiedBy { get; set; } } - public class User : IUser, IEntity + public class User : IUser, IEntity, INamedEntity { public virtual int Id { get; set; } diff --git a/src/NHibernate.Test/Async/CacheTest/BatchableCacheSubclassFixture.cs b/src/NHibernate.Test/Async/CacheTest/BatchableCacheSubclassFixture.cs index 7ac6d663aee..819e31c6c12 100644 --- a/src/NHibernate.Test/Async/CacheTest/BatchableCacheSubclassFixture.cs +++ b/src/NHibernate.Test/Async/CacheTest/BatchableCacheSubclassFixture.cs @@ -119,7 +119,7 @@ public async Task BatchableRootEntityTestAsync() using (var tx = s.BeginTransaction()) { var enumerator = - (await (s.CreateQuery("from foo in class NHibernate.DomainModel.Foo order by foo.String").EnumerableAsync())).GetEnumerator(); + s.CreateQuery("from foo in class NHibernate.DomainModel.Foo order by foo.String").Enumerable().GetEnumerator(); var i = 1; while (enumerator.MoveNext()) { diff --git a/src/NHibernate.Test/Async/CompositeId/CompositeIdFixture.cs b/src/NHibernate.Test/Async/CompositeId/CompositeIdFixture.cs index 5bc228546f8..f5eb58f2d29 100644 --- a/src/NHibernate.Test/Async/CompositeId/CompositeIdFixture.cs +++ b/src/NHibernate.Test/Async/CompositeId/CompositeIdFixture.cs @@ -116,7 +116,7 @@ public async Task CompositeIdsAsync() { Assert.AreEqual(2, stuff.Length); } - iter = await (s.CreateQuery("from Order o join o.LineItems li").EnumerableAsync()); + iter = s.CreateQuery("from Order o join o.LineItems li").Enumerable(); foreach (object[] stuff in iter) { Assert.AreEqual(2, stuff.Length); diff --git a/src/NHibernate.Test/Async/ConnectionTest/AggressiveReleaseTest.cs b/src/NHibernate.Test/Async/ConnectionTest/AggressiveReleaseTest.cs index 24dca7f8a36..2a5bb602546 100644 --- a/src/NHibernate.Test/Async/ConnectionTest/AggressiveReleaseTest.cs +++ b/src/NHibernate.Test/Async/ConnectionTest/AggressiveReleaseTest.cs @@ -91,7 +91,8 @@ public async Task SerializationFailsOnAfterStatementAggressiveReleaseWithOpenRes // both scroll() and iterate() cause the batcher to hold on // to resources, which should make aggresive-Release not Release // the connection (and thus cause serialization to fail) - IEnumerable en = await (s.CreateQuery("from Silly").EnumerableAsync()); + var en = s.CreateQuery("from Silly").Enumerable().GetEnumerator(); + en.MoveNext(); try { @@ -125,15 +126,15 @@ public async Task QueryIterationAsync() await (s.SaveAsync(silly)); await (s.FlushAsync()); - IEnumerable en = await (s.CreateQuery("from Silly").EnumerableAsync()); + IEnumerable en = s.CreateQuery("from Silly").Enumerable(); IEnumerator itr = en.GetEnumerator(); Assert.IsTrue(itr.MoveNext()); Silly silly2 = (Silly) itr.Current; Assert.AreEqual(silly, silly2); NHibernateUtil.Close(itr); - itr = (await (s.CreateQuery("from Silly").EnumerableAsync())).GetEnumerator(); - IEnumerator itr2 = (await (s.CreateQuery("from Silly where name = 'silly'").EnumerableAsync())).GetEnumerator(); + itr = s.CreateQuery("from Silly").Enumerable().GetEnumerator(); + IEnumerator itr2 = s.CreateQuery("from Silly where name = 'silly'").Enumerable().GetEnumerator(); Assert.IsTrue(itr.MoveNext()); Assert.AreEqual(silly, itr.Current); diff --git a/src/NHibernate.Test/Async/GenericTest/Methods/Fixture.cs b/src/NHibernate.Test/Async/GenericTest/Methods/Fixture.cs index d9c5c80ca52..442c6892df9 100644 --- a/src/NHibernate.Test/Async/GenericTest/Methods/Fixture.cs +++ b/src/NHibernate.Test/Async/GenericTest/Methods/Fixture.cs @@ -10,13 +10,13 @@ using System.Collections; using System.Collections.Generic; +using System.Threading.Tasks; using NHibernate.Criterion; using NHibernate.DomainModel; using NUnit.Framework; namespace NHibernate.Test.GenericTest.Methods { - using System.Threading.Tasks; [TestFixture] public class FixtureAsync : TestCase { @@ -24,7 +24,7 @@ protected override string[] Mappings { get { - return new string[] { "One.hbm.xml", "Many.hbm.xml" }; + return new string[] { "One.hbm.xml", "Many.hbm.xml", "Simple.hbm.xml" }; } } @@ -49,12 +49,15 @@ protected override void OnSetUp() many2.One = one; one.Manies.Add( many2 ); - using( ISession s = OpenSession() ) + var simple = new Simple(1) {Count = 1}; + + using ( ISession s = OpenSession() ) using( ITransaction t = s.BeginTransaction() ) { s.Save( one ); s.Save( many1 ); s.Save( many2 ); + s.Save(simple, 1); t.Commit(); } } @@ -66,6 +69,7 @@ protected override void OnTearDown() { session.Delete( "from Many" ); session.Delete( "from One" ); + session.Delete("from Simple"); tx.Commit(); } base.OnTearDown(); @@ -102,20 +106,6 @@ public async Task QueryListAsync() } } - [Test] - public async Task QueryEnumerableAsync() - { - using( ISession s = OpenSession() ) - using( ITransaction t = s.BeginTransaction() ) - { - IEnumerable results = await (s.CreateQuery( "from One" ).EnumerableAsync()); - IEnumerator en = results.GetEnumerator(); - - Assert.IsTrue( en.MoveNext() ); - Assert.IsFalse( en.MoveNext() ); - } - } - [Test] public async Task FilterAsync() { @@ -131,23 +121,5 @@ public async Task FilterAsync() await (t.CommitAsync()); } } - - [Test] - public async Task FilterEnumerableAsync() - { - using( ISession s = OpenSession() ) - using( ITransaction t = s.BeginTransaction() ) - { - One one2 = ( One ) await (s.CreateQuery( "from One" ).UniqueResultAsync()); - IEnumerable results = await ((await (s.CreateFilterAsync( one2.Manies, "where X = 10" ))) - .EnumerableAsync()); - IEnumerator en = results.GetEnumerator(); - - Assert.IsTrue( en.MoveNext() ); - Assert.AreEqual( 10, en.Current.X ); - Assert.IsFalse( en.MoveNext() ); - await (t.CommitAsync()); - } - } } } diff --git a/src/NHibernate.Test/Async/Legacy/FooBarTest.cs b/src/NHibernate.Test/Async/Legacy/FooBarTest.cs index 282c2b6754f..e8bb3d42097 100644 --- a/src/NHibernate.Test/Async/Legacy/FooBarTest.cs +++ b/src/NHibernate.Test/Async/Legacy/FooBarTest.cs @@ -28,6 +28,7 @@ using NHibernate.Type; using NHibernate.Util; using NUnit.Framework; +using System.Linq; namespace NHibernate.Test.Legacy { @@ -487,7 +488,7 @@ public async Task QueryAsync() if (Dialect is MsSql2000Dialect) { - await (s.CreateQuery("select baz from Baz as baz join baz.FooArray foo group by baz order by sum(foo.Float)").EnumerableAsync()); + s.CreateQuery("select baz from Baz as baz join baz.FooArray foo group by baz order by sum(foo.Float)").Enumerable(); } await (s.CreateQuery("from Foo as foo where foo.Component.Glarch.Name is not null").ListAsync()); @@ -511,9 +512,9 @@ public async Task QueryAsync() .ListAsync()); Assert.AreEqual(0, list.Count, "empty query"); IEnumerable enumerable = - await (s.CreateQuery( + s.CreateQuery( "from foo in class NHibernate.DomainModel.Foo where foo.String='osama bin laden' order by foo.String asc, foo.Component.Count desc") - .EnumerableAsync()); + .Enumerable(); Assert.IsTrue(IsEmpty(enumerable), "empty enumerator"); list = await (s.CreateQuery("select foo.TheFoo from foo in class NHibernate.DomainModel.Foo").ListAsync()); @@ -557,9 +558,9 @@ public async Task QueryAsync() await (s.CreateQuery("from foo in class Foo where foo = some(select x from x in class Foo where x.Long > foo.TheFoo.Long)") .ListAsync()); - await (s.CreateQuery( + s.CreateQuery( "select foo.String, foo.Date, foo.TheFoo.String, foo.id from foo in class Foo, baz in class Baz where foo in elements(baz.FooArray) and foo.String like 'foo'") - .EnumerableAsync()); + .Enumerable(); } list = await (s.CreateQuery("from foo in class Foo where foo.Component.Count is null order by foo.Component.Count").ListAsync()); @@ -604,12 +605,12 @@ public async Task QueryAsync() await (s.CreateQuery("from foo in class Foo where foo.TheFoo = ?").SetEntity(0, foo.TheFoo).ListAsync()); Assert.IsTrue( - IsEmpty(await (s.CreateQuery("from bar in class Bar where bar.String='a string' or bar.String='a string'").EnumerableAsync()) + IsEmpty(s.CreateQuery("from bar in class Bar where bar.String='a string' or bar.String='a string'").Enumerable() )); - enumerable = await (s.CreateQuery( + enumerable = s.CreateQuery( "select foo.Component.Name, elements(foo.Component.ImportantDates) from foo in class Foo where foo.TheFoo.id=?"). - SetString(0, foo.TheFoo.Key).EnumerableAsync()); + SetString(0, foo.TheFoo.Key).Enumerable(); int i = 0; foreach (object[] row in enumerable) @@ -620,8 +621,8 @@ public async Task QueryAsync() } Assert.AreEqual(3, i); //WAS: 4 - enumerable = await (s.CreateQuery("select max(elements(foo.Component.ImportantDates)) from foo in class Foo group by foo.id"). - EnumerableAsync()); + enumerable = s.CreateQuery("select max(elements(foo.Component.ImportantDates)) from foo in class Foo group by foo.id"). + Enumerable(); IEnumerator enumerator = enumerable.GetEnumerator(); @@ -668,24 +669,24 @@ public async Task QueryAsync() if (TestDialect.SupportsCountDistinct) { - enumerable = await (s.CreateQuery("select count(distinct foo.TheFoo) from foo in class Foo").EnumerableAsync()); + enumerable = s.CreateQuery("select count(distinct foo.TheFoo) from foo in class Foo").Enumerable(); Assert.IsTrue(ContainsSingleObject(enumerable, (long) 2), "count"); // changed to Int64 (HQLFunction H3.2) } - enumerable = await (s.CreateQuery("select count(foo.TheFoo.Boolean) from foo in class Foo").EnumerableAsync()); + enumerable = s.CreateQuery("select count(foo.TheFoo.Boolean) from foo in class Foo").Enumerable(); Assert.IsTrue(ContainsSingleObject(enumerable, (long) 2), "count"); // changed to Int64 (HQLFunction H3.2) - enumerable = await (s.CreateQuery("select count(*), foo.Int from foo in class Foo group by foo.Int").EnumerableAsync()); + enumerable = s.CreateQuery("select count(*), foo.Int from foo in class Foo group by foo.Int").Enumerable(); enumerator = enumerable.GetEnumerator(); Assert.IsTrue(enumerator.MoveNext()); Assert.AreEqual(3L, (long) ((object[]) enumerator.Current)[0]); Assert.IsFalse(enumerator.MoveNext()); - enumerable = await (s.CreateQuery("select sum(foo.TheFoo.Int) from foo in class Foo").EnumerableAsync()); + enumerable = s.CreateQuery("select sum(foo.TheFoo.Int) from foo in class Foo").Enumerable(); Assert.IsTrue(ContainsSingleObject(enumerable, (long) 4), "sum"); // changed to Int64 (HQLFunction H3.2) - enumerable = await (s.CreateQuery("select count(foo) from foo in class Foo where foo.id=?") - .SetString(0, foo.Key).EnumerableAsync()); + enumerable = s.CreateQuery("select count(foo) from foo in class Foo where foo.id=?") + .SetString(0, foo.Key).Enumerable(); Assert.IsTrue(ContainsSingleObject(enumerable, (long) 1), "id query count"); list = await (s.CreateQuery("from foo in class Foo where foo.Boolean = ?").SetBoolean(0, true).ListAsync()); @@ -702,7 +703,7 @@ public async Task QueryAsync() .ListAsync()); Assert.IsTrue(list.Count == 3); - enumerable = await (s.CreateQuery("select new Foo(fo.X) from Foo fo").EnumerableAsync()); + enumerable = s.CreateQuery("select new Foo(fo.X) from Foo fo").Enumerable(); enumerator = enumerable.GetEnumerator(); Assert.IsTrue(enumerator.MoveNext(), "projection iterate (results)"); Assert.IsTrue(typeof(Foo).IsAssignableFrom(enumerator.Current.GetType()), @@ -753,13 +754,13 @@ public async Task QueryAsync() Assert.AreEqual(7, (await (s.CreateQuery("from n in class INamed").ListAsync())).Count); Assert.IsTrue((await (s.CreateQuery("from n in class INamed where n.Name is not null").ListAsync())).Count == 4); - foreach (INamed named in await (s.CreateQuery("from n in class INamed").EnumerableAsync())) + foreach (INamed named in s.CreateQuery("from n in class INamed").Enumerable()) { Assert.IsNotNull(named); } await (s.SaveAsync(new Holder("bar"))); - enumerable = await (s.CreateQuery("from n0 in class INamed, n1 in class INamed where n0.Name = n1.Name").EnumerableAsync()); + enumerable = s.CreateQuery("from n0 in class INamed, n1 in class INamed where n0.Name = n1.Name").Enumerable(); int cnt = 0; foreach (object[] row in enumerable) { @@ -781,19 +782,19 @@ public async Task QueryAsync() int c = 0; - foreach (object obj in await (s.CreateQuery("from o in class System.Object").EnumerableAsync())) + foreach (object obj in s.CreateQuery("from o in class System.Object").Enumerable()) { c++; } Assert.IsTrue(c == 16); - await (s.CreateQuery("select baz.Code, min(baz.Count) from baz in class Baz group by baz.Code").EnumerableAsync()); + s.CreateQuery("select baz.Code, min(baz.Count) from baz in class Baz group by baz.Code").Enumerable(); Assert.IsTrue( IsEmpty( - await (s.CreateQuery( + s.CreateQuery( "selecT baz from baz in class Baz where baz.StringDateMap['foo'] is not null or baz.StringDateMap['bar'] = ?") - .SetDateTime(0, DateTime.Today).EnumerableAsync()))); + .SetDateTime(0, DateTime.Today).Enumerable())); list = await (s.CreateQuery("select baz from baz in class Baz where baz.StringDateMap['now'] is not null").ListAsync()); Assert.AreEqual(1, list.Count); @@ -925,7 +926,7 @@ public async Task CascadeDeleteDetachedAsync() await (s.DeleteAsync(baz)); await (s.FlushAsync()); - Assert.IsFalse((await (s.CreateQuery("from Fee").EnumerableAsync())).GetEnumerator().MoveNext()); + Assert.IsFalse(s.CreateQuery("from Fee").Enumerable().GetEnumerator().MoveNext()); } using (ISession s = OpenSession()) @@ -951,7 +952,7 @@ public async Task CascadeDeleteDetachedAsync() { await (s.DeleteAsync(baz)); await (s.FlushAsync()); - Assert.IsTrue(IsEmpty(await (s.CreateQuery("from Fee").EnumerableAsync()))); + Assert.IsTrue(IsEmpty(s.CreateQuery("from Fee").Enumerable())); } } @@ -1222,10 +1223,7 @@ public async Task BatchLoadAsync() await (s.DeleteAsync(baz2)); await (s.DeleteAsync(baz3)); - IEnumerable en = new JoinedEnumerable( - new IEnumerable[] {baz.FooSet, baz2.FooSet}); - - foreach (object obj in en) + foreach (object obj in baz.FooSet.Concat(baz2.FooSet)) { await (s.DeleteAsync(obj)); } @@ -1811,10 +1809,10 @@ public async Task LimitAsync() await (s.SaveAsync(new Foo())); } - IEnumerable enumerable = await (s.CreateQuery("from Foo foo") + IEnumerable enumerable = s.CreateQuery("from Foo foo") .SetMaxResults(4) .SetFirstResult(2) - .EnumerableAsync()); + .Enumerable(); int count = 0; IEnumerator e = enumerable.GetEnumerator(); @@ -2356,7 +2354,7 @@ public async Task CascadeSaveAsync() Assert.AreEqual(2, baz.Fees.Count); await (s.DeleteAsync(baz)); - Assert.IsTrue(IsEmpty(await (s.CreateQuery("from fee in class Fee").EnumerableAsync()))); + Assert.IsTrue(IsEmpty(s.CreateQuery("from fee in class Fee").Enumerable())); await (t.CommitAsync()); s.Close(); } @@ -2537,11 +2535,11 @@ public async Task NewFlushingAsync() await (s.FlushAsync()); baz.StringArray[0] = "a new value"; - IEnumerator enumer = (await (s.CreateQuery("from baz in class Baz").EnumerableAsync())).GetEnumerator(); // no flush + IEnumerator enumer = s.CreateQuery("from baz in class Baz").Enumerable().GetEnumerator(); // no flush Assert.IsTrue(enumer.MoveNext()); Assert.AreSame(baz, enumer.Current); - enumer = (await (s.CreateQuery("select elements(baz.StringArray) from baz in class Baz").EnumerableAsync())).GetEnumerator(); + enumer = s.CreateQuery("select elements(baz.StringArray) from baz in class Baz").Enumerable().GetEnumerator(); bool found = false; while (enumer.MoveNext()) @@ -2554,17 +2552,17 @@ public async Task NewFlushingAsync() Assert.IsTrue(found); baz.StringArray = null; - await (s.CreateQuery("from baz in class Baz").EnumerableAsync()); // no flush + s.CreateQuery("from baz in class Baz").Enumerable(); // no flush - enumer = (await (s.CreateQuery("select elements(baz.StringArray) from baz in class Baz").EnumerableAsync())).GetEnumerator(); + enumer = s.CreateQuery("select elements(baz.StringArray) from baz in class Baz").Enumerable().GetEnumerator(); Assert.IsFalse(enumer.MoveNext()); baz.StringList.Add("1E1"); - enumer = (await (s.CreateQuery("from foo in class Foo").EnumerableAsync())).GetEnumerator(); // no flush + enumer = s.CreateQuery("from foo in class Foo").Enumerable().GetEnumerator(); // no flush Assert.IsFalse(enumer.MoveNext()); - enumer = (await (s.CreateQuery("select elements(baz.StringList) from baz in class Baz").EnumerableAsync())).GetEnumerator(); + enumer = s.CreateQuery("select elements(baz.StringList) from baz in class Baz").Enumerable().GetEnumerator(); found = false; while (enumer.MoveNext()) @@ -2577,9 +2575,9 @@ public async Task NewFlushingAsync() Assert.IsTrue(found); baz.StringList.Remove("1E1"); - await (s.CreateQuery("select elements(baz.StringArray) from baz in class Baz").EnumerableAsync()); //no flush + s.CreateQuery("select elements(baz.StringArray) from baz in class Baz").Enumerable(); //no flush - enumer = (await (s.CreateQuery("select elements(baz.StringList) from baz in class Baz").EnumerableAsync())).GetEnumerator(); + enumer = s.CreateQuery("select elements(baz.StringList) from baz in class Baz").Enumerable().GetEnumerator(); found = false; while (enumer.MoveNext()) @@ -2595,11 +2593,11 @@ public async Task NewFlushingAsync() newList.Add("value"); baz.StringList = newList; - (await (s.CreateQuery("from foo in class Foo").EnumerableAsync())).GetEnumerator(); //no flush + s.CreateQuery("from foo in class Foo").Enumerable().GetEnumerator(); //no flush baz.StringList = null; - enumer = (await (s.CreateQuery("select elements(baz.StringList) from baz in class Baz").EnumerableAsync())).GetEnumerator(); + enumer = s.CreateQuery("select elements(baz.StringList) from baz in class Baz").Enumerable().GetEnumerator(); Assert.IsFalse(enumer.MoveNext()); @@ -2613,7 +2611,7 @@ public async Task PersistCollectionsAsync() { ISession s = OpenSession(); ITransaction txn = s.BeginTransaction(); - IEnumerator enumer = (await (s.CreateQuery("select count(*) from b in class Bar").EnumerableAsync())).GetEnumerator(); + IEnumerator enumer = s.CreateQuery("select count(*) from b in class Bar").Enumerable().GetEnumerator(); enumer.MoveNext(); Assert.AreEqual(0L, enumer.Current); @@ -3425,7 +3423,7 @@ public async Task EnumerableAsync() s = OpenSession(); IEnumerator enumer = - (await (s.CreateQuery("from q in class NHibernate.DomainModel.Qux where q.Stuff is null").EnumerableAsync())).GetEnumerator(); + s.CreateQuery("from q in class NHibernate.DomainModel.Qux where q.Stuff is null").Enumerable().GetEnumerator(); int count = 0; while (enumer.MoveNext()) { @@ -3453,7 +3451,7 @@ public async Task EnumerableAsync() s.Close(); s = OpenSession(); - enumer = (await (s.CreateQuery("from q in class NHibernate.DomainModel.Qux").EnumerableAsync())).GetEnumerator(); + enumer = s.CreateQuery("from q in class NHibernate.DomainModel.Qux").Enumerable().GetEnumerator(); Assert.IsFalse(enumer.MoveNext(), "no items in enumerator"); await (s.FlushAsync()); s.Close(); @@ -3489,7 +3487,7 @@ public async Task EnumerableDisposableAsync() // of an object. If the query is "from Simple as s" then it will be converted to a NDataReader // on the MoveNext so it can get the object from the id - thus needing another open DataReader so // it must convert to an NDataReader. - IEnumerable enumer = await (s.CreateQuery("select s.Count from Simple as s").EnumerableAsync()); + IEnumerable enumer = s.CreateQuery("select s.Count from Simple as s").Enumerable(); //int count = 0; foreach (object obj in enumer) { @@ -3512,7 +3510,7 @@ public async Task EnumerableDisposableAsync() s.Close(); s = OpenSession(); - enumer = await (s.CreateQuery("from Simple").EnumerableAsync()); + enumer = s.CreateQuery("from Simple").Enumerable(); Assert.IsFalse(enumer.GetEnumerator().MoveNext(), "no items in enumerator"); await (s.FlushAsync()); s.Close(); @@ -3762,7 +3760,7 @@ public async Task RecursiveLoadAsync() last.Order = (short) (i + 1); } - IEnumerator enumer = (await (s.CreateQuery("from g in class NHibernate.DomainModel.Glarch").EnumerableAsync())).GetEnumerator(); + IEnumerator enumer = s.CreateQuery("from g in class NHibernate.DomainModel.Glarch").Enumerable().GetEnumerator(); while (enumer.MoveNext()) { object objTemp = enumer.Current; @@ -3785,7 +3783,7 @@ public async Task RecursiveLoadAsync() s = OpenSession(); txn = s.BeginTransaction(); enumer = - (await (s.CreateQuery("from g in class NHibernate.DomainModel.Glarch order by g.Order asc").EnumerableAsync())).GetEnumerator(); + s.CreateQuery("from g in class NHibernate.DomainModel.Glarch order by g.Order asc").Enumerable().GetEnumerator(); while (enumer.MoveNext()) { GlarchProxy g = (GlarchProxy) enumer.Current; @@ -3812,7 +3810,7 @@ public async Task RecursiveLoadAsync() flast.String = "foo" + (i + 1); } - enumer = (await (s.CreateQuery("from foo in class NHibernate.DomainModel.Foo").EnumerableAsync())).GetEnumerator(); + enumer = s.CreateQuery("from foo in class NHibernate.DomainModel.Foo").Enumerable().GetEnumerator(); while (enumer.MoveNext()) { object objTemp = enumer.Current; @@ -3838,7 +3836,7 @@ public async Task RecursiveLoadAsync() s = OpenSession(); txn = s.BeginTransaction(); enumer = - (await (s.CreateQuery("from foo in class NHibernate.DomainModel.Foo order by foo.String asc").EnumerableAsync())).GetEnumerator(); + s.CreateQuery("from foo in class NHibernate.DomainModel.Foo order by foo.String asc").Enumerable().GetEnumerator(); string currentString = String.Empty; while (enumer.MoveNext()) @@ -3886,9 +3884,9 @@ public async Task MultiColumnQueriesAsync() if (TestDialect.SupportsCountDistinct) { rs = - (await (s.CreateQuery( + s.CreateQuery( "select count(distinct child.id), count(distinct parent.id) from parent in class NHibernate.DomainModel.Foo, child in class NHibernate.DomainModel.Foo where parent.TheFoo = child") - .EnumerableAsync())).GetEnumerator(); + .Enumerable().GetEnumerator(); Assert.IsTrue(rs.MoveNext()); row = (object[]) rs.Current; Assert.AreEqual(1, row[0], "multi-column count"); @@ -3897,9 +3895,9 @@ public async Task MultiColumnQueriesAsync() } rs = - (await (s.CreateQuery( + s.CreateQuery( "select child.id, parent.id, child.Long from parent in class NHibernate.DomainModel.Foo, child in class NHibernate.DomainModel.Foo where parent.TheFoo = child") - .EnumerableAsync())).GetEnumerator(); + .Enumerable().GetEnumerator(); Assert.IsTrue(rs.MoveNext()); row = (object[]) rs.Current; Assert.AreEqual(foo.TheFoo.Key, row[0], "multi-column id"); @@ -3908,9 +3906,9 @@ public async Task MultiColumnQueriesAsync() Assert.IsFalse(rs.MoveNext()); rs = - (await (s.CreateQuery( + s.CreateQuery( "select child.id, parent.id, child.Long, child, parent.TheFoo from parent in class NHibernate.DomainModel.Foo, child in class NHibernate.DomainModel.Foo where parent.TheFoo = child") - .EnumerableAsync())).GetEnumerator(); + .Enumerable().GetEnumerator(); Assert.IsTrue(rs.MoveNext()); row = (object[]) rs.Current; Assert.AreEqual(foo.TheFoo.Key, row[0], "multi-column id"); @@ -3929,9 +3927,9 @@ public async Task MultiColumnQueriesAsync() s = OpenSession(); txn = s.BeginTransaction(); IEnumerator enumer = - (await (s.CreateQuery( + s.CreateQuery( "select parent, child from parent in class NHibernate.DomainModel.Foo, child in class NHibernate.DomainModel.Foo where parent.TheFoo = child and parent.String='a string'") - .EnumerableAsync())).GetEnumerator(); + .Enumerable().GetEnumerator(); int deletions = 0; while (enumer.MoveNext()) { @@ -4513,7 +4511,7 @@ public async Task OrderByAsync() s = OpenSession(); IEnumerable enumerable = - await (s.CreateQuery("SELECT one FROM one IN CLASS " + typeof(One).Name + " ORDER BY one.Value ASC").EnumerableAsync()); + s.CreateQuery("SELECT one FROM one IN CLASS " + typeof(One).Name + " ORDER BY one.Value ASC").Enumerable(); int count = 0; foreach (One one in enumerable) { @@ -4537,8 +4535,8 @@ public async Task OrderByAsync() s = OpenSession(); enumerable = - await (s.CreateQuery("SELECT many.One FROM many IN CLASS " + typeof(Many).Name + - " ORDER BY many.One.Value ASC, many.One.id").EnumerableAsync()); + s.CreateQuery("SELECT many.One FROM many IN CLASS " + typeof(Many).Name + + " ORDER BY many.One.Value ASC, many.One.id").Enumerable(); count = 0; foreach (One one in enumerable) { @@ -4642,7 +4640,7 @@ public async Task ProxyArrayAsync() Assert.AreEqual(g, g.ProxyArray[1].ProxyArray[2], "deferred load test"); Assert.AreEqual(2, g.ProxySet.Count, "set of proxies"); - IEnumerator enumer = (await (s.CreateQuery("from g in class NHibernate.DomainModel.Glarch").EnumerableAsync())).GetEnumerator(); + IEnumerator enumer = s.CreateQuery("from g in class NHibernate.DomainModel.Glarch").Enumerable().GetEnumerator(); while (enumer.MoveNext()) { await (s.DeleteAsync(enumer.Current)); @@ -4878,7 +4876,7 @@ public async Task AutoFlushCollectionsAsync() IEnumerator e; - e = (await (s.CreateQuery("select elements(baz.StringArray) from baz in class NHibernate.DomainModel.Baz").EnumerableAsync())). + e = s.CreateQuery("select elements(baz.StringArray) from baz in class NHibernate.DomainModel.Baz").Enumerable(). GetEnumerator(); bool found = false; @@ -4892,13 +4890,13 @@ public async Task AutoFlushCollectionsAsync() Assert.IsTrue(found); baz.StringArray = null; - e = (await (s.CreateQuery("select distinct elements(baz.StringArray) from baz in class NHibernate.DomainModel.Baz").EnumerableAsync())) + e = s.CreateQuery("select distinct elements(baz.StringArray) from baz in class NHibernate.DomainModel.Baz").Enumerable() .GetEnumerator(); Assert.IsFalse(e.MoveNext()); baz.StringArray = new string[] {"foo", "bar"}; - e = (await (s.CreateQuery("select elements(baz.StringArray) from baz in class NHibernate.DomainModel.Baz").EnumerableAsync())). + e = s.CreateQuery("select elements(baz.StringArray) from baz in class NHibernate.DomainModel.Baz").Enumerable(). GetEnumerator(); Assert.IsTrue(e.MoveNext()); @@ -4908,7 +4906,7 @@ public async Task AutoFlushCollectionsAsync() await (s.FlushAsync()); baz.FooArray = new Foo[] {foo}; - e = (await (s.CreateQuery("select foo from baz in class NHibernate.DomainModel.Baz, foo in elements(baz.FooArray)").EnumerableAsync())) + e = s.CreateQuery("select foo from baz in class NHibernate.DomainModel.Baz, foo in elements(baz.FooArray)").Enumerable() .GetEnumerator(); found = false; @@ -4923,13 +4921,13 @@ public async Task AutoFlushCollectionsAsync() baz.FooArray[0] = null; - e = (await (s.CreateQuery("select foo from baz in class NHibernate.DomainModel.Baz, foo in elements(baz.FooArray)").EnumerableAsync())) + e = s.CreateQuery("select foo from baz in class NHibernate.DomainModel.Baz, foo in elements(baz.FooArray)").Enumerable() .GetEnumerator(); Assert.IsFalse(e.MoveNext()); baz.FooArray[0] = foo; - e = (await (s.CreateQuery("select elements(baz.FooArray) from baz in class NHibernate.DomainModel.Baz").EnumerableAsync())). + e = s.CreateQuery("select elements(baz.FooArray) from baz in class NHibernate.DomainModel.Baz").Enumerable(). GetEnumerator(); Assert.IsTrue(e.MoveNext()); @@ -4937,14 +4935,14 @@ public async Task AutoFlushCollectionsAsync() if (Dialect.SupportsSubSelects && !(Dialect is FirebirdDialect)) { baz.FooArray[0] = null; - e = (await (s.CreateQuery("from baz in class NHibernate.DomainModel.Baz where ? in elements(baz.FooArray)").SetEntity(0, foo). - EnumerableAsync())).GetEnumerator(); + e = s.CreateQuery("from baz in class NHibernate.DomainModel.Baz where ? in elements(baz.FooArray)").SetEntity(0, foo). + Enumerable().GetEnumerator(); Assert.IsFalse(e.MoveNext()); baz.FooArray[0] = foo; - e = (await (s.CreateQuery("select foo from foo in class NHibernate.DomainModel.Foo where foo in " + e = s.CreateQuery("select foo from foo in class NHibernate.DomainModel.Foo where foo in " + "(select elt from baz in class NHibernate.DomainModel.Baz, elt in elements(baz.FooArray))"). - EnumerableAsync())).GetEnumerator(); + Enumerable().GetEnumerator(); Assert.IsTrue(e.MoveNext()); } @@ -5018,7 +5016,7 @@ public async Task ComplicatedQueryAsync() q.Foo.String = "foo2"; IEnumerator enumer = - (await (s.CreateQuery("from foo in class Foo where foo.Dependent.Qux.Foo.String = 'foo2'").EnumerableAsync())).GetEnumerator(); + s.CreateQuery("from foo in class Foo where foo.Dependent.Qux.Foo.String = 'foo2'").Enumerable().GetEnumerator(); Assert.IsTrue(enumer.MoveNext()); await (s.DeleteAsync(foo)); await (txn.CommitAsync()); @@ -5279,10 +5277,9 @@ public async Task TransientOrphanDeleteAsync() baz.FooBag = foos; await (s.SaveAsync(baz)); - IEnumerator enumer = new JoinedEnumerable(new IEnumerable[] {foos, bars}).GetEnumerator(); - while (enumer.MoveNext()) + foreach (var foo in foos.Concat(bars.Cast())) { - FooComponent cmp = ((Foo) enumer.Current).Component; + FooComponent cmp = foo.Component; await (s.DeleteAsync(cmp.Glarch)); cmp.Glarch = null; } diff --git a/src/NHibernate.Test/Async/Legacy/FumTest.cs b/src/NHibernate.Test/Async/Legacy/FumTest.cs index 0c234e2b96f..9992ccc96d4 100644 --- a/src/NHibernate.Test/Async/Legacy/FumTest.cs +++ b/src/NHibernate.Test/Async/Legacy/FumTest.cs @@ -211,8 +211,8 @@ public async Task ListIdentifiersAsync() Assert.AreEqual(2, list.Count, "List Identifiers"); IEnumerator enumerator = - (await (s.CreateQuery("select fum.Id from fum in class NHibernate.DomainModel.Fum where not fum.FumString='FRIEND'"). - EnumerableAsync())).GetEnumerator(); + s.CreateQuery("select fum.Id from fum in class NHibernate.DomainModel.Fum where not fum.FumString='FRIEND'"). + Enumerable().GetEnumerator(); int i = 0; while (enumerator.MoveNext()) { @@ -304,7 +304,7 @@ public async Task CompositeIDAsync() s = OpenSession(); t = s.BeginTransaction(); IEnumerator enumerator = - (await (s.CreateQuery("from fum in class NHibernate.DomainModel.Fum where not fum.FumString='FRIEND'").EnumerableAsync())). + s.CreateQuery("from fum in class NHibernate.DomainModel.Fum where not fum.FumString='FRIEND'").Enumerable(). GetEnumerator(); int i = 0; while (enumerator.MoveNext()) @@ -385,10 +385,10 @@ public async Task CompositeIDQueryAsync() s = OpenSession(); Assert.IsTrue( - (await (s.CreateQuery("select fum.Id.Short, fum.Id.Date, fum.Id.String from fum in class NHibernate.DomainModel.Fum"). - EnumerableAsync())).GetEnumerator().MoveNext()); + s.CreateQuery("select fum.Id.Short, fum.Id.Date, fum.Id.String from fum in class NHibernate.DomainModel.Fum"). + Enumerable().GetEnumerator().MoveNext()); Assert.IsTrue( - (await (s.CreateQuery("select fum.Id from fum in class NHibernate.DomainModel.Fum").EnumerableAsync())).GetEnumerator().MoveNext()); + s.CreateQuery("select fum.Id from fum in class NHibernate.DomainModel.Fum").Enumerable().GetEnumerator().MoveNext()); IQuery qu = s.CreateQuery("select fum.FumString, fum, fum.FumString, fum.Id.Date from fum in class NHibernate.DomainModel.Fum"); @@ -402,7 +402,7 @@ public async Task CompositeIDQueryAsync() Assert.IsTrue(types[1] is EntityType); Assert.IsTrue(types[2] is StringType); Assert.IsTrue(types[3] is DateTimeType); - IEnumerator enumer = (await (qu.EnumerableAsync())).GetEnumerator(); + IEnumerator enumer = qu.Enumerable().GetEnumerator(); int j = 0; while (enumer.MoveNext()) { @@ -416,10 +416,10 @@ public async Task CompositeIDQueryAsync() (await (s.CreateFilterAsync(fum.QuxArray, "where this.Foo.id = ?"))).SetString(0, "fooid"); IQuery f = await (s.CreateFilterAsync(fum.QuxArray, "where this.Foo.id = :fooId")); f.SetString("fooId", "abc"); - Assert.IsFalse((await (f.EnumerableAsync())).GetEnumerator().MoveNext()); + Assert.IsFalse(f.Enumerable().GetEnumerator().MoveNext()); enumer = - (await (s.CreateQuery("from fum in class NHibernate.DomainModel.Fum where not fum.FumString='FRIEND'").EnumerableAsync())). + s.CreateQuery("from fum in class NHibernate.DomainModel.Fum where not fum.FumString='FRIEND'").Enumerable(). GetEnumerator(); int i = 0; while (enumer.MoveNext()) @@ -431,9 +431,9 @@ public async Task CompositeIDQueryAsync() Assert.AreEqual(4, i, "iterate on composite key"); await (s.FlushAsync()); - await (s.CreateQuery( + s.CreateQuery( "from fu in class Fum, fo in class Fum where fu.Fo.Id.String = fo.Id.String and fo.FumString is not null"). - EnumerableAsync()); + Enumerable(); await (s.CreateQuery("from Fumm f1 inner join f1.Fum f2").ListAsync()); s.Close(); } @@ -529,7 +529,7 @@ public async Task CompositeIDsAsync() fo = (Fo) await (s.LoadAsync(typeof(Fo), FumKey("an instance of fo"))); Assert.AreEqual(5, fo.X); IEnumerator enumer = - (await (s.CreateQuery("from fo in class NHibernate.DomainModel.Fo where fo.id.String like 'an instance of fo'").EnumerableAsync())) + s.CreateQuery("from fo in class NHibernate.DomainModel.Fo where fo.id.String like 'an instance of fo'").Enumerable() .GetEnumerator(); Assert.IsTrue(enumer.MoveNext()); Assert.AreSame(fo, enumer.Current); diff --git a/src/NHibernate.Test/Async/Legacy/MasterDetailTest.cs b/src/NHibernate.Test/Async/Legacy/MasterDetailTest.cs index 97d7277918f..3f835d0845a 100644 --- a/src/NHibernate.Test/Async/Legacy/MasterDetailTest.cs +++ b/src/NHibernate.Test/Async/Legacy/MasterDetailTest.cs @@ -240,7 +240,7 @@ public async Task SelfManyToOneAsync() s = OpenSession(); t = s.BeginTransaction(); - IEnumerator enumer = (await (s.CreateQuery("from m in class Master").EnumerableAsync())).GetEnumerator(); + IEnumerator enumer = s.CreateQuery("from m in class Master").Enumerable().GetEnumerator(); enumer.MoveNext(); m = (Master) enumer.Current; Assert.AreSame(m, m.OtherMaster); @@ -375,11 +375,11 @@ public async Task CollectionQueryAsync() if (Dialect.SupportsSubSelects) { - await (s.CreateQuery("FROM m IN CLASS Master WHERE NOT EXISTS ( FROM " + path + " d WHERE NOT d.I=5 )").EnumerableAsync()); - await (s.CreateQuery("FROM m IN CLASS Master WHERE NOT 5 IN ( SELECT d.I FROM d IN " + path + " )").EnumerableAsync()); + s.CreateQuery("FROM m IN CLASS Master WHERE NOT EXISTS ( FROM " + path + " d WHERE NOT d.I=5 )").Enumerable(); + s.CreateQuery("FROM m IN CLASS Master WHERE NOT 5 IN ( SELECT d.I FROM d IN " + path + " )").Enumerable(); } - await (s.CreateQuery("SELECT m FROM m in CLASS NHibernate.DomainModel.Master join m.Details d WHERE d.I=5").EnumerableAsync()); + s.CreateQuery("SELECT m FROM m in CLASS NHibernate.DomainModel.Master join m.Details d WHERE d.I=5").Enumerable(); await (s.CreateQuery("SELECT m FROM m in CLASS NHibernate.DomainModel.Master join m.Details d WHERE d.I=5").ListAsync()); await (s.CreateQuery("SELECT m.id FROM m IN CLASS NHibernate.DomainModel.Master join m.Details d WHERE d.I=5").ListAsync()); await (t.CommitAsync()); @@ -499,7 +499,7 @@ public async Task MasterDetailAsync() q.SetParameterList("ids", new[] {did, (long) -1}); Assert.AreEqual(1, (await (q.ListAsync())).Count); - Assert.IsTrue((await (q.EnumerableAsync())).GetEnumerator().MoveNext()); + Assert.IsTrue(q.Enumerable().GetEnumerator().MoveNext()); Assert.AreEqual(2, (await ((await (s.CreateFilterAsync(master.Details, "where this.id > -1"))).ListAsync())).Count); Assert.AreEqual(2, (await ((await (s.CreateFilterAsync(master.Details, "select this.Master where this.id > -1"))).ListAsync())).Count); @@ -509,17 +509,17 @@ public async Task MasterDetailAsync() Assert.AreEqual(0, (await ((await (s.CreateFilterAsync(master.Incoming, "where this.id > -1 and this.Name is not null"))).ListAsync())).Count); IQuery filter = await (s.CreateFilterAsync(master.Details, "select max(this.I)")); - enumer = (await (filter.EnumerableAsync())).GetEnumerator(); + enumer = filter.Enumerable().GetEnumerator(); Assert.IsTrue(enumer.MoveNext()); Assert.IsTrue(enumer.Current is Int32); filter = await (s.CreateFilterAsync(master.Details, "select max(this.I) group by this.id")); - enumer = (await (filter.EnumerableAsync())).GetEnumerator(); + enumer = filter.Enumerable().GetEnumerator(); Assert.IsTrue(enumer.MoveNext()); Assert.IsTrue(enumer.Current is Int32); filter = await (s.CreateFilterAsync(master.Details, "select count(*)")); - enumer = (await (filter.EnumerableAsync())).GetEnumerator(); + enumer = filter.Enumerable().GetEnumerator(); Assert.IsTrue(enumer.MoveNext()); Assert.IsTrue(enumer.Current is Int64); @@ -529,18 +529,18 @@ public async Task MasterDetailAsync() f.SetInt32("top", 100); f.SetInt32("bottom", 0); - enumer = (await (f.EnumerableAsync())).GetEnumerator(); + enumer = f.Enumerable().GetEnumerator(); Assert.IsTrue(enumer.MoveNext()); Assert.AreEqual(12, enumer.Current); f.SetInt32("top", 2); - enumer = (await (f.EnumerableAsync())).GetEnumerator(); + enumer = f.Enumerable().GetEnumerator(); Assert.IsTrue(enumer.MoveNext()); Assert.AreEqual(0, enumer.Current); f = await (s.CreateFilterAsync(master.Details, "select max(this.I) where this.I not in (:list)")); f.SetParameterList("list", new List {-666, 22, 0}); - enumer = (await (f.EnumerableAsync())).GetEnumerator(); + enumer = f.Enumerable().GetEnumerator(); Assert.IsTrue(enumer.MoveNext()); Assert.AreEqual(12, enumer.Current); @@ -1016,7 +1016,7 @@ public async Task CategoriesAsync() Assert.IsNotNull(list[1]); IEnumerator enumer = - (await (s.CreateQuery("from c in class Category where c.Name = NHibernate.DomainModel.Category.RootCategory").EnumerableAsync())). + s.CreateQuery("from c in class Category where c.Name = NHibernate.DomainModel.Category.RootCategory").Enumerable(). GetEnumerator(); Assert.IsTrue(enumer.MoveNext()); } diff --git a/src/NHibernate.Test/Async/Legacy/MultiTableTest.cs b/src/NHibernate.Test/Async/Legacy/MultiTableTest.cs index f61cbeb88ff..ff03fc5ee3e 100644 --- a/src/NHibernate.Test/Async/Legacy/MultiTableTest.cs +++ b/src/NHibernate.Test/Async/Legacy/MultiTableTest.cs @@ -107,7 +107,7 @@ public async Task SubclassCollectionAsync() Assert.AreEqual("FOO", sm.Derived, "should have uppercased the column in a formula"); IEnumerator enumer = - (await (s.CreateQuery("select distinct s from s in class SubMulti where s.MoreChildren[1].Amount < 1.0").EnumerableAsync())). + s.CreateQuery("select distinct s from s in class SubMulti where s.MoreChildren[1].Amount < 1.0").Enumerable(). GetEnumerator(); Assert.IsTrue(enumer.MoveNext()); Assert.AreSame(sm, enumer.Current); @@ -171,8 +171,8 @@ public async Task QueriesAsync() await (s.CreateQuery("from s in class Top where s.Count=1").ListAsync()); await (s.CreateQuery("select s.Count from s in class Top, ls in class Lower where ls.Another=s").ListAsync()); await (s.CreateQuery("select elements(ls.Bag), elements(ls.Set) from ls in class Lower").ListAsync()); - await (s.CreateQuery("from s in class Lower").EnumerableAsync()); - await (s.CreateQuery("from s in class Top").EnumerableAsync()); + s.CreateQuery("from s in class Lower").Enumerable(); + s.CreateQuery("from s in class Top").Enumerable(); await (s.DeleteAsync(tc)); await (s.FlushAsync()); s.Close(); @@ -280,7 +280,7 @@ public async Task MultiTableAsync() s = OpenSession(); t = s.BeginTransaction(); - IEnumerator enumer = (await (s.CreateQuery("select\n\ns from s in class Top where s.Count>0").EnumerableAsync())).GetEnumerator(); + IEnumerator enumer = s.CreateQuery("select\n\ns from s in class Top where s.Count>0").Enumerable().GetEnumerator(); bool foundSimp = false; bool foundMulti = false; bool foundSubMulti = false; @@ -394,7 +394,7 @@ public async Task MultiTableGeneratedIdAsync() s = OpenSession(); t = s.BeginTransaction(); - IEnumerable enumer = await (s.CreateQuery("select\n\ns from s in class Top where s.Count>0").EnumerableAsync()); + IEnumerable enumer = s.CreateQuery("select\n\ns from s in class Top where s.Count>0").Enumerable(); bool foundSimp = false; bool foundMulti = false; bool foundSubMulti = false; diff --git a/src/NHibernate.Test/Async/Legacy/ParentChildTest.cs b/src/NHibernate.Test/Async/Legacy/ParentChildTest.cs index 78f75864144..eff460efa85 100644 --- a/src/NHibernate.Test/Async/Legacy/ParentChildTest.cs +++ b/src/NHibernate.Test/Async/Legacy/ParentChildTest.cs @@ -473,9 +473,9 @@ public async Task ParentChildAsync() await (s.CreateQuery( "select c, c.Parent from c in class NHibernate.DomainModel.Child where c.Parent.Count=66 order by c.Parent.Count"). ListAsync()); - await (s.CreateQuery( + s.CreateQuery( "select c, c.Parent, c.Parent.Count from c in class NHibernate.DomainModel.Child order by c.Parent.Count"). - EnumerableAsync()); + Enumerable(); Assert.AreEqual(1, (await (s.CreateQuery("FROM p in CLASS NHibernate.DomainModel.Parent WHERE p.Count=?").SetInt32(0, 66).ListAsync())) .Count, "1-1 query"); @@ -727,7 +727,7 @@ public async Task CascadeCompositeElementsAsync() s.Close(); s = OpenSession(); - foreach (Container obj in await (s.CreateQuery("from c in class ContainerX").EnumerableAsync())) + foreach (Container obj in s.CreateQuery("from c in class ContainerX").Enumerable()) { c = obj; break; @@ -756,7 +756,7 @@ public async Task CascadeCompositeElementsAsync() s.Close(); s = OpenSession(); - foreach (Container obj in await (s.CreateQuery("from c in class ContainerX").EnumerableAsync())) + foreach (Container obj in s.CreateQuery("from c in class ContainerX").Enumerable()) { c = obj; } diff --git a/src/NHibernate.Test/Async/Legacy/SQLFunctionsTest.cs b/src/NHibernate.Test/Async/Legacy/SQLFunctionsTest.cs index 5acc3e36f8f..1ea557e0d2f 100644 --- a/src/NHibernate.Test/Async/Legacy/SQLFunctionsTest.cs +++ b/src/NHibernate.Test/Async/Legacy/SQLFunctionsTest.cs @@ -40,7 +40,7 @@ public async Task DialectSQLFunctionsAsync() ISession s = OpenSession(); ITransaction t = s.BeginTransaction(); - IEnumerator iter = (await (s.CreateQuery("select max(s.Count) from s in class Simple").EnumerableAsync())) + IEnumerator iter = s.CreateQuery("select max(s.Count) from s in class Simple").Enumerable() .GetEnumerator(); if (Dialect is MySQLDialect @@ -504,7 +504,7 @@ public async Task SQLFunctionsAsync() } IEnumerator enumer = - (await (s.CreateQuery("select sum(s.Count) from s in class Simple group by s.Count having sum(s.Count) > 10 ").EnumerableAsync())) + s.CreateQuery("select sum(s.Count) from s in class Simple group by s.Count having sum(s.Count) > 10 ").Enumerable() .GetEnumerator(); Assert.IsTrue(enumer.MoveNext()); Assert.AreEqual(12, (Int64)enumer.Current); // changed cast from Int32 to Int64 (H3.2) @@ -513,15 +513,15 @@ public async Task SQLFunctionsAsync() if (Dialect.SupportsSubSelects) { enumer = - (await (s.CreateQuery("select s.Count from s in class Simple group by s.Count having s.Count = 12").EnumerableAsync())). + s.CreateQuery("select s.Count from s in class Simple group by s.Count having s.Count = 12").Enumerable(). GetEnumerator(); Assert.IsTrue(enumer.MoveNext()); } enumer = - (await (s.CreateQuery( + s.CreateQuery( "select s.id, s.Count, count(t), max(t.Date) from s in class Simple, t in class Simple where s.Count = t.Count group by s.id, s.Count order by s.Count") - .EnumerableAsync())).GetEnumerator(); + .Enumerable().GetEnumerator(); IQuery q = s.CreateQuery("from s in class Simple"); q.SetMaxResults(10); @@ -542,7 +542,7 @@ public async Task SQLFunctionsAsync() q.SetString(1, "SIMPLE 1"); q.SetString(0, "Simple 1"); q.SetFirstResult(0); - Assert.IsTrue((await (q.EnumerableAsync())).GetEnumerator().MoveNext()); + Assert.IsTrue(q.Enumerable().GetEnumerator().MoveNext()); q = s.CreateQuery( @@ -551,12 +551,12 @@ public async Task SQLFunctionsAsync() q.SetString("foo", "Simple 1"); q.SetInt32("count", 69); q.SetFirstResult(0); - Assert.IsTrue((await (q.EnumerableAsync())).GetEnumerator().MoveNext()); + Assert.IsTrue(q.Enumerable().GetEnumerator().MoveNext()); q = s.CreateQuery("select s.id from s in class Simple"); q.SetFirstResult(1); q.SetMaxResults(2); - IEnumerable enumerable = await (q.EnumerableAsync()); + IEnumerable enumerable = q.Enumerable(); int i = 0; foreach (object obj in enumerable) { diff --git a/src/NHibernate.Test/Async/ReadOnly/ReadOnlySessionTest.cs b/src/NHibernate.Test/Async/ReadOnly/ReadOnlySessionTest.cs index 2541e43a9c5..95703e51570 100644 --- a/src/NHibernate.Test/Async/ReadOnly/ReadOnlySessionTest.cs +++ b/src/NHibernate.Test/Async/ReadOnly/ReadOnlySessionTest.cs @@ -10,6 +10,7 @@ using System.Collections; using System.Collections.Generic; +using System.Threading.Tasks; using NHibernate.Cfg; using NHibernate.Criterion; using NHibernate.Dialect; @@ -23,7 +24,6 @@ namespace NHibernate.Test.ReadOnly { - using System.Threading.Tasks; using System.Threading; [TestFixture] public class ReadOnlySessionTestAsync : TestCase @@ -120,7 +120,7 @@ public async Task ReadOnlySessionDefaultQueryIterateAsync() using (ITransaction t = s.BeginTransaction()) { s.DefaultReadOnly = true; - IEnumerable enumerable = await (s.CreateQuery("from DataPoint dp order by dp.X asc").EnumerableAsync()); + IEnumerable enumerable = s.CreateQuery("from DataPoint dp order by dp.X asc").Enumerable(); s.DefaultReadOnly = false; int i = 0; @@ -181,9 +181,9 @@ public async Task ReadOnlySessionModifiableQueryIterateAsync() using (ITransaction t = s.BeginTransaction()) { s.DefaultReadOnly = true; - IEnumerable enumerable = await (s.CreateQuery("from DataPoint dp order by dp.X asc") + IEnumerable enumerable = s.CreateQuery("from DataPoint dp order by dp.X asc") .SetReadOnly(false) - .EnumerableAsync()); + .Enumerable(); int i = 0; foreach (DataPoint dp in enumerable) @@ -244,9 +244,9 @@ public async Task ModifiableSessionReadOnlyQueryIterateAsync() { Assert.That(s.DefaultReadOnly, Is.False); - IEnumerable enumerable = await (s.CreateQuery("from DataPoint dp order by dp.X asc") + IEnumerable enumerable = s.CreateQuery("from DataPoint dp order by dp.X asc") .SetReadOnly(true) - .EnumerableAsync()); + .Enumerable(); int i = 0; foreach (DataPoint dp in enumerable) @@ -310,7 +310,7 @@ public async Task ModifiableSessionDefaultQueryReadOnlySessionIterateAsync() IQuery query = s.CreateQuery("from DataPoint dp order by dp.X asc"); s.DefaultReadOnly = true; - IEnumerable enumerable = await (query.EnumerableAsync()); + IEnumerable enumerable = query.Enumerable(); s.DefaultReadOnly = false; int i = 0; @@ -348,30 +348,9 @@ public async Task ModifiableSessionDefaultQueryReadOnlySessionIterateAsync() [Test] public async Task QueryReadOnlyIterateAsync() { - long lastDataPointId = 0; int nExpectedChanges = 0; - - using (ISession s = OpenSession()) - { - s.CacheMode = CacheMode.Ignore; - - using (ITransaction t = s.BeginTransaction()) - { - DataPoint dp = null; - - for (int i = 0; i < 100; i++) - { - dp = new DataPoint(); - dp.X = 0.1M * i; - dp.Y = (decimal)System.Math.Cos((double)dp.X); - await (s.SaveAsync(dp)); - } - await (t.CommitAsync()); - - lastDataPointId = dp.Id; - } - } - + QueryIterateCreate(out var lastDataPointId); + using (ISession s = OpenSession()) { s.CacheMode = CacheMode.Ignore; @@ -381,113 +360,83 @@ public async Task QueryReadOnlyIterateAsync() s.DefaultReadOnly = false; IQuery query = s.CreateQuery("from DataPoint dp order by dp.X asc"); - - Assert.That(query.IsReadOnly, Is.False); - s.DefaultReadOnly = true; - Assert.That(query.IsReadOnly, Is.True); - s.DefaultReadOnly = false; - Assert.That(query.IsReadOnly, Is.False); - query.SetReadOnly(true); - Assert.That(query.IsReadOnly, Is.True); - s.DefaultReadOnly = true; - Assert.That(query.IsReadOnly, Is.True); - s.DefaultReadOnly = false; - Assert.That(query.IsReadOnly, Is.True); - query.SetReadOnly(false); - Assert.That(query.IsReadOnly, Is.False); - s.DefaultReadOnly = true; - Assert.That(query.IsReadOnly, Is.False); - query.SetReadOnly(true); - Assert.That(query.IsReadOnly, Is.True); - s.DefaultReadOnly = false; - Assert.That(s.DefaultReadOnly, Is.False); - IEnumerator it = (await (query.EnumerableAsync())).GetEnumerator(); + QueryReadOnlyIterateAssertReadOnly(query, s); + + var it = query.Enumerable().GetEnumerator(); Assert.That(query.IsReadOnly, Is.True); DataPoint dpLast = await (s.GetAsync(lastDataPointId)); Assert.That(s.IsReadOnly(dpLast), Is.False); query.SetReadOnly(false); Assert.That(query.IsReadOnly, Is.False); Assert.That(s.DefaultReadOnly, Is.False); - + int i = 0; - while (it.MoveNext()) { - Assert.That(s.DefaultReadOnly, Is.False); - DataPoint dp = it.Current; - Assert.That(s.DefaultReadOnly, Is.False); - - if (dp.Id == dpLast.Id) - { - //dpLast existed in the session before executing the read-only query - Assert.That(s.IsReadOnly(dp), Is.False); - } - else - { - Assert.That(s.IsReadOnly(dp), Is.True); - } - - if (++i == 50) - { - s.SetReadOnly(dp, false); - nExpectedChanges = (dp == dpLast ? 1 : 2 ); - } - - dp.Description = "done!"; + QueryReadOnlyIterateAssertRow(s, it.Current, dpLast, ref i, ref nExpectedChanges); } Assert.That(s.DefaultReadOnly, Is.False); await (t.CommitAsync()); } - - s.Clear(); - - using (ITransaction t = s.BeginTransaction()) - { - try - { - IList single = await (s.CreateQuery("from DataPoint where Description = 'done!'").ListAsync()); - Assert.That(single.Count, Is.EqualTo(nExpectedChanges)); - } - finally - { - // cleanup - await (s.CreateQuery("delete from DataPoint").ExecuteUpdateAsync()); - } - - await (t.CommitAsync()); - } + + await (QueryIterateClearAsync(s, nExpectedChanges)); } } + + private void QueryReadOnlyIterateAssertReadOnly(IQuery query, ISession s) + { + Assert.That(query.IsReadOnly, Is.False); + s.DefaultReadOnly = true; + Assert.That(query.IsReadOnly, Is.True); + s.DefaultReadOnly = false; + Assert.That(query.IsReadOnly, Is.False); + query.SetReadOnly(true); + Assert.That(query.IsReadOnly, Is.True); + s.DefaultReadOnly = true; + Assert.That(query.IsReadOnly, Is.True); + s.DefaultReadOnly = false; + Assert.That(query.IsReadOnly, Is.True); + query.SetReadOnly(false); + Assert.That(query.IsReadOnly, Is.False); + s.DefaultReadOnly = true; + Assert.That(query.IsReadOnly, Is.False); + query.SetReadOnly(true); + Assert.That(query.IsReadOnly, Is.True); + s.DefaultReadOnly = false; + Assert.That(s.DefaultReadOnly, Is.False); + } + + private void QueryReadOnlyIterateAssertRow(ISession s, DataPoint dp, DataPoint dpLast, ref int i, ref int nExpectedChanges) + { + Assert.That(s.DefaultReadOnly, Is.False); + + if (dp.Id == dpLast.Id) + { + //dpLast existed in the session before executing the read-only query + Assert.That(s.IsReadOnly(dp), Is.False); + } + else + { + Assert.That(s.IsReadOnly(dp), Is.True); + } + + if (++i == 50) + { + s.SetReadOnly(dp, false); + nExpectedChanges = (dp == dpLast ? 1 : 2); + } + + dp.Description = "done!"; + } [Test] public async Task QueryModifiableIterateAsync() { - long lastDataPointId = 0; int nExpectedChanges = 0; - - using (ISession s = OpenSession()) - { - s.CacheMode = CacheMode.Ignore; - - using (ITransaction t = s.BeginTransaction()) - { - DataPoint dp = null; - - for (int i = 0; i < 100; i++) - { - dp = new DataPoint(); - dp.X = 0.1M * i; - dp.Y = (decimal)System.Math.Cos((double)dp.X); - await (s.SaveAsync(dp)); - } - await (t.CommitAsync()); - - lastDataPointId = dp.Id; - } - } - + QueryIterateCreate(out var lastDataPointId); + using (ISession s = OpenSession()) { s.CacheMode = CacheMode.Ignore; @@ -497,29 +446,9 @@ public async Task QueryModifiableIterateAsync() s.DefaultReadOnly = true; IQuery query = s.CreateQuery("from DataPoint dp order by dp.X asc"); - - Assert.That(query.IsReadOnly, Is.True); - s.DefaultReadOnly = false; - Assert.That(query.IsReadOnly, Is.False); - s.DefaultReadOnly = true; - Assert.That(query.IsReadOnly, Is.True); - query.SetReadOnly(false); - Assert.That(query.IsReadOnly, Is.False); - s.DefaultReadOnly = false; - Assert.That(query.IsReadOnly, Is.False); - s.DefaultReadOnly = true; - Assert.That(query.IsReadOnly, Is.False); - query.SetReadOnly(true); - Assert.That(query.IsReadOnly, Is.True); - s.DefaultReadOnly = false; - Assert.That(query.IsReadOnly, Is.True); - - query.SetReadOnly(false); - Assert.That(query.IsReadOnly, Is.False); - s.DefaultReadOnly = true; - Assert.That(s.DefaultReadOnly, Is.True); - - IEnumerator it = (await (query.EnumerableAsync())).GetEnumerator(); + QueryModifiableIterateAssertReadOnly(query, s); + + IEnumerator it = query.Enumerable().GetEnumerator(); Assert.That(query.IsReadOnly, Is.False); DataPoint dpLast = await (s.GetAsync(lastDataPointId)); Assert.That(s.IsReadOnly(dpLast), Is.True); @@ -528,57 +457,111 @@ public async Task QueryModifiableIterateAsync() Assert.That(s.DefaultReadOnly, Is.True); int i = 0; - while (it.MoveNext()) { - Assert.That(s.DefaultReadOnly, Is.True); - DataPoint dp = it.Current; - Assert.That(s.DefaultReadOnly, Is.True); - - if (dp.Id == dpLast.Id) - { - //dpLast existed in the session before executing the read-only query - Assert.That(s.IsReadOnly(dp), Is.True); - } - else - { - Assert.That(s.IsReadOnly(dp), Is.False); - } - - if (++i == 50) - { - s.SetReadOnly(dp, true); - nExpectedChanges = (dp == dpLast ? 99 : 98); - } - - dp.Description = "done!"; + QueryModifiableIterateAssertRow(s, it.Current, dpLast, ref i, ref nExpectedChanges); } Assert.That(s.DefaultReadOnly, Is.True); - await (t.CommitAsync()); } - - s.Clear(); - + + await (QueryIterateClearAsync(s, nExpectedChanges)); + } + } + + private void QueryModifiableIterateAssertReadOnly(IQuery query, ISession s) + { + Assert.That(query.IsReadOnly, Is.True); + s.DefaultReadOnly = false; + Assert.That(query.IsReadOnly, Is.False); + s.DefaultReadOnly = true; + Assert.That(query.IsReadOnly, Is.True); + query.SetReadOnly(false); + Assert.That(query.IsReadOnly, Is.False); + s.DefaultReadOnly = false; + Assert.That(query.IsReadOnly, Is.False); + s.DefaultReadOnly = true; + Assert.That(query.IsReadOnly, Is.False); + query.SetReadOnly(true); + Assert.That(query.IsReadOnly, Is.True); + s.DefaultReadOnly = false; + Assert.That(query.IsReadOnly, Is.True); + + query.SetReadOnly(false); + Assert.That(query.IsReadOnly, Is.False); + s.DefaultReadOnly = true; + Assert.That(s.DefaultReadOnly, Is.True); + } + + private void QueryModifiableIterateAssertRow(ISession s, DataPoint dp, DataPoint dpLast, ref int i, ref int nExpectedChanges) + { + Assert.That(s.DefaultReadOnly, Is.True); + Assert.That(s.DefaultReadOnly, Is.True); + + if (dp.Id == dpLast.Id) + { + //dpLast existed in the session before executing the read-only query + Assert.That(s.IsReadOnly(dp), Is.True); + } + else + { + Assert.That(s.IsReadOnly(dp), Is.False); + } + + if (++i == 50) + { + s.SetReadOnly(dp, true); + nExpectedChanges = (dp == dpLast ? 99 : 98); + } + + dp.Description = "done!"; + } + + private void QueryIterateCreate(out long lastDataPointId) + { + using (ISession s = OpenSession()) + { + s.CacheMode = CacheMode.Ignore; + using (ITransaction t = s.BeginTransaction()) { - try - { - IList list = await (s.CreateQuery("from DataPoint where Description = 'done!'").ListAsync()); - Assert.That(list.Count, Is.EqualTo(nExpectedChanges)); - } - finally + DataPoint dp = null; + + for (int i = 0; i < 100; i++) { - // cleanup - await (s.CreateQuery("delete from DataPoint").ExecuteUpdateAsync()); + dp = new DataPoint(); + dp.X = 0.1M * i; + dp.Y = (decimal) System.Math.Cos((double) dp.X); + s.Save(dp); } - - await (t.CommitAsync()); + t.Commit(); + + lastDataPointId = dp.Id; } } } - + + private async Task QueryIterateClearAsync(ISession s, int nExpectedChanges, CancellationToken cancellationToken = default(CancellationToken)) + { + s.Clear(); + using (ITransaction t = s.BeginTransaction()) + { + try + { + IList single = await (s.CreateQuery("from DataPoint where Description = 'done!'").ListAsync(cancellationToken)); + Assert.That(single.Count, Is.EqualTo(nExpectedChanges)); + } + finally + { + // cleanup + await (s.CreateQuery("delete from DataPoint").ExecuteUpdateAsync(cancellationToken)); + } + + await (t.CommitAsync(cancellationToken)); + } + } + [Test] public async Task ReadOnlyRefreshAsync() { diff --git a/src/NHibernate.Test/Async/Stats/StatsFixture.cs b/src/NHibernate.Test/Async/Stats/StatsFixture.cs index f36c5cb7cd8..ed938daf459 100644 --- a/src/NHibernate.Test/Async/Stats/StatsFixture.cs +++ b/src/NHibernate.Test/Async/Stats/StatsFixture.cs @@ -173,7 +173,8 @@ public async Task QueryStatGatheringAsync() Assert.AreEqual(maxTime, stats.QueryExecutionMaxTime); Assert.AreEqual( continents, stats.QueryExecutionMaxTimeQueryString ); - IEnumerable itr = await (s.CreateQuery(continents).EnumerableAsync()); + var itr = s.CreateQuery(continents).Enumerable().GetEnumerator(); + itr.MoveNext(); // Enumerable() should increment the execution count Assert.AreEqual(2, continentStats.ExecutionCount, "unexpected execution count"); // but should not effect the cumulative row count diff --git a/src/NHibernate.Test/ConnectionTest/AggressiveReleaseTest.cs b/src/NHibernate.Test/ConnectionTest/AggressiveReleaseTest.cs index efb6d5488fc..0b5798f50ff 100644 --- a/src/NHibernate.Test/ConnectionTest/AggressiveReleaseTest.cs +++ b/src/NHibernate.Test/ConnectionTest/AggressiveReleaseTest.cs @@ -79,7 +79,8 @@ public void SerializationFailsOnAfterStatementAggressiveReleaseWithOpenResources // both scroll() and iterate() cause the batcher to hold on // to resources, which should make aggresive-Release not Release // the connection (and thus cause serialization to fail) - IEnumerable en = s.CreateQuery("from Silly").Enumerable(); + var en = s.CreateQuery("from Silly").Enumerable().GetEnumerator(); + en.MoveNext(); try { diff --git a/src/NHibernate.Test/GenericTest/Methods/Fixture.cs b/src/NHibernate.Test/GenericTest/Methods/Fixture.cs index 903d3c49e6d..80c2b22a698 100644 --- a/src/NHibernate.Test/GenericTest/Methods/Fixture.cs +++ b/src/NHibernate.Test/GenericTest/Methods/Fixture.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Collections.Generic; +using System.Threading.Tasks; using NHibernate.Criterion; using NHibernate.DomainModel; using NUnit.Framework; @@ -13,7 +14,7 @@ protected override string[] Mappings { get { - return new string[] { "One.hbm.xml", "Many.hbm.xml" }; + return new string[] { "One.hbm.xml", "Many.hbm.xml", "Simple.hbm.xml" }; } } @@ -38,12 +39,15 @@ protected override void OnSetUp() many2.One = one; one.Manies.Add( many2 ); - using( ISession s = OpenSession() ) + var simple = new Simple(1) {Count = 1}; + + using ( ISession s = OpenSession() ) using( ITransaction t = s.BeginTransaction() ) { s.Save( one ); s.Save( many1 ); s.Save( many2 ); + s.Save(simple, 1); t.Commit(); } } @@ -55,6 +59,7 @@ protected override void OnTearDown() { session.Delete( "from Many" ); session.Delete( "from One" ); + session.Delete("from Simple"); tx.Commit(); } base.OnTearDown(); @@ -105,6 +110,88 @@ public void QueryEnumerable() } } + [Test] + public void AutoFlushQueryEnumerable() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + Assert.That(s.FlushMode, Is.EqualTo(FlushMode.Auto)); + var results = s.CreateQuery("from Simple").Enumerable(); + + var id = 2; + var simple = new Simple(id) {Count = id}; + s.Save(simple, id); + var enumerator = results.GetEnumerator(); + + Assert.That(enumerator.MoveNext(), Is.True); + Assert.That(enumerator.MoveNext(), Is.True); + Assert.That(enumerator.MoveNext(), Is.False); + enumerator.Dispose(); + + id++; + simple = new Simple(id) {Count = id}; + s.Save(simple, id); + enumerator = results.GetEnumerator(); + + Assert.That(enumerator.MoveNext(), Is.True); + Assert.That(enumerator.MoveNext(), Is.True); + Assert.That(enumerator.MoveNext(), Is.True); + Assert.That(enumerator.MoveNext(), Is.False); + enumerator.Dispose(); + + t.Rollback(); + } + } + + [Test] + public async Task QueryEnumerableAsync() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + var results = s.CreateQuery("from One").AsyncEnumerable(); + var enumerator = results.GetAsyncEnumerator(); + + Assert.That(await enumerator.MoveNextAsync(), Is.True); + Assert.That(await enumerator.MoveNextAsync(), Is.False); + } + } + + [Test] + public async Task AutoFlushQueryEnumerableAsync() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + Assert.That(s.FlushMode, Is.EqualTo(FlushMode.Auto)); + var results = s.CreateQuery("from Simple").AsyncEnumerable(); + + var id = 2; + var simple = new Simple(id) {Count = id}; + s.Save(simple, id); + var enumerator = results.GetAsyncEnumerator(); + + Assert.That(await enumerator.MoveNextAsync(), Is.True); + Assert.That(await enumerator.MoveNextAsync(), Is.True); + Assert.That(await enumerator.MoveNextAsync(), Is.False); + await enumerator.DisposeAsync(); + + id++; + simple = new Simple(id) {Count = id}; + s.Save(simple, id); + enumerator = results.GetAsyncEnumerator(); + + Assert.That(await enumerator.MoveNextAsync(), Is.True); + Assert.That(await enumerator.MoveNextAsync(), Is.True); + Assert.That(await enumerator.MoveNextAsync(), Is.True); + Assert.That(await enumerator.MoveNextAsync(), Is.False); + await enumerator.DisposeAsync(); + + await t.RollbackAsync(); + } + } + [Test] public void Filter() { @@ -138,5 +225,22 @@ public void FilterEnumerable() t.Commit(); } } + + [Test] + public async Task FilterEnumerableAsync() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + One one2 = (One) await s.CreateQuery("from One").UniqueResultAsync(); + var results = s.CreateFilter(one2.Manies, "where X = 10").AsyncEnumerable(); + var en = results.GetAsyncEnumerator(); + + Assert.That(await en.MoveNextAsync(), Is.True); + Assert.That(en.Current.X, Is.EqualTo(10)); + Assert.That(await en.MoveNextAsync(), Is.False); + await t.CommitAsync(); + } + } } } diff --git a/src/NHibernate.Test/Legacy/FooBarTest.cs b/src/NHibernate.Test/Legacy/FooBarTest.cs index cd95652152b..98541877c5e 100644 --- a/src/NHibernate.Test/Legacy/FooBarTest.cs +++ b/src/NHibernate.Test/Legacy/FooBarTest.cs @@ -18,6 +18,7 @@ using NHibernate.Type; using NHibernate.Util; using NUnit.Framework; +using System.Linq; namespace NHibernate.Test.Legacy { @@ -1210,10 +1211,7 @@ public void BatchLoad() s.Delete(baz2); s.Delete(baz3); - IEnumerable en = new JoinedEnumerable( - new IEnumerable[] {baz.FooSet, baz2.FooSet}); - - foreach (object obj in en) + foreach (object obj in baz.FooSet.Concat(baz2.FooSet)) { s.Delete(obj); } @@ -5267,10 +5265,9 @@ public void TransientOrphanDelete() baz.FooBag = foos; s.Save(baz); - IEnumerator enumer = new JoinedEnumerable(new IEnumerable[] {foos, bars}).GetEnumerator(); - while (enumer.MoveNext()) + foreach (var foo in foos.Concat(bars.Cast())) { - FooComponent cmp = ((Foo) enumer.Current).Component; + FooComponent cmp = foo.Component; s.Delete(cmp.Glarch); cmp.Glarch = null; } diff --git a/src/NHibernate.Test/Linq/ByMethod/AsAsyncEnumerableTests.cs b/src/NHibernate.Test/Linq/ByMethod/AsAsyncEnumerableTests.cs new file mode 100644 index 00000000000..2dee844e477 --- /dev/null +++ b/src/NHibernate.Test/Linq/ByMethod/AsAsyncEnumerableTests.cs @@ -0,0 +1,47 @@ +using System.Linq; +using System.Threading.Tasks; +using NHibernate.DomainModel.Northwind.Entities; +using NHibernate.Linq; +using NUnit.Framework; + +namespace NHibernate.Test.Linq.ByMethod +{ + [TestFixture] + public class AsAsyncEnumerableTests : LinqTestCase + { + [Test] + public async Task MultipleEnumerationTestAsync() + { + var asyncEnumerable = db.Customers.Where(c => c.Address.City == "London").OrderBy(c => c.CustomerId).AsAsyncEnumerable(); + for (var i = 0; i < 3; i++) + { + await AssertByPropertyValueAsync(asyncEnumerable, new[] + { + "AROUT", + "BSBEV", + "CONSH", + "EASTC", + "NORTS", + "SEVES" + }, x => x.CustomerId); + } + } + + [Test] + public async Task PolymorphismTestAsync() + { + var asyncEnumerable = session.Query().OrderBy(o => o.Name).AsAsyncEnumerable(); + for (var i = 0; i < 3; i++) + { + await AssertByPropertyValueAsync(asyncEnumerable, new[] + { + "Admin", + "User", + "ayende", + "nhibernate", + "rahien" + }, x => x.Name); + } + } + } +} diff --git a/src/NHibernate.Test/Linq/LinqTestCase.cs b/src/NHibernate.Test/Linq/LinqTestCase.cs index e047732d7ad..08efe248377 100755 --- a/src/NHibernate.Test/Linq/LinqTestCase.cs +++ b/src/NHibernate.Test/Linq/LinqTestCase.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using NHibernate.DomainModel.Northwind.Entities; using NUnit.Framework; @@ -69,5 +70,22 @@ public static void AssertByIds(IEnumerable entities, TId[ { Assert.That(entities.Select(x => entityIdGetter(x)), Is.EquivalentTo(expectedIds)); } + + public static async Task AssertByPropertyValueAsync(IAsyncEnumerable entities, TId[] expectedIds, Converter propertyValueGetter) + { + var enumerator = entities.GetAsyncEnumerator(); + bool hasNext; + for (var i = 0; i < expectedIds.Length; i++) + { + hasNext = await enumerator.MoveNextAsync(); + Assert.That(hasNext, Is.True, $"The collection contains less entities than expected (Expected: {expectedIds.Length}, but was: {i})"); + Assert.That(propertyValueGetter(enumerator.Current), Is.EqualTo(expectedIds[i]), $"Not expected entity on index {i}."); + } + + hasNext = await enumerator.MoveNextAsync(); + Assert.That(hasNext, Is.False, $"The collection contains more entities than expected (Expected: {expectedIds.Length})"); + + await enumerator.DisposeAsync(); + } } } diff --git a/src/NHibernate.Test/NHibernate.Test.csproj b/src/NHibernate.Test/NHibernate.Test.csproj index 365e1dcbf99..61221bf36cb 100644 --- a/src/NHibernate.Test/NHibernate.Test.csproj +++ b/src/NHibernate.Test/NHibernate.Test.csproj @@ -6,6 +6,7 @@ true $(NoWarn);3001;3002;3003;3005 true + 7.2 Exe diff --git a/src/NHibernate.Test/ReadOnly/ReadOnlySessionTest.cs b/src/NHibernate.Test/ReadOnly/ReadOnlySessionTest.cs index a5d01ebd78e..fe4752c3b18 100644 --- a/src/NHibernate.Test/ReadOnly/ReadOnlySessionTest.cs +++ b/src/NHibernate.Test/ReadOnly/ReadOnlySessionTest.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Collections.Generic; +using System.Threading.Tasks; using NHibernate.Cfg; using NHibernate.Criterion; using NHibernate.Dialect; @@ -372,237 +373,299 @@ public void ModifiableSessionDefaultQueryReadOnlySessionIterate() [Test] public void QueryReadOnlyIterate() { - long lastDataPointId = 0; int nExpectedChanges = 0; - + QueryIterateCreate(out var lastDataPointId); + using (ISession s = OpenSession()) { s.CacheMode = CacheMode.Ignore; using (ITransaction t = s.BeginTransaction()) { - DataPoint dp = null; - - for (int i = 0; i < 100; i++) + s.DefaultReadOnly = false; + + IQuery query = s.CreateQuery("from DataPoint dp order by dp.X asc"); + QueryReadOnlyIterateAssertReadOnly(query, s); + + var it = query.Enumerable().GetEnumerator(); + Assert.That(query.IsReadOnly, Is.True); + DataPoint dpLast = s.Get(lastDataPointId); + Assert.That(s.IsReadOnly(dpLast), Is.False); + query.SetReadOnly(false); + Assert.That(query.IsReadOnly, Is.False); + Assert.That(s.DefaultReadOnly, Is.False); + + int i = 0; + while (it.MoveNext()) { - dp = new DataPoint(); - dp.X = 0.1M * i; - dp.Y = (decimal)System.Math.Cos((double)dp.X); - s.Save(dp); + QueryReadOnlyIterateAssertRow(s, it.Current, dpLast, ref i, ref nExpectedChanges); } - t.Commit(); - lastDataPointId = dp.Id; + Assert.That(s.DefaultReadOnly, Is.False); + + t.Commit(); } + + QueryIterateClear(s, nExpectedChanges); } - + } + + [Test] + public async Task QueryReadOnlyAsyncIterate() + { + int nExpectedChanges = 0; + QueryIterateCreate(out var lastDataPointId); + using (ISession s = OpenSession()) { s.CacheMode = CacheMode.Ignore; - + using (ITransaction t = s.BeginTransaction()) { s.DefaultReadOnly = false; IQuery query = s.CreateQuery("from DataPoint dp order by dp.X asc"); - - Assert.That(query.IsReadOnly, Is.False); - s.DefaultReadOnly = true; - Assert.That(query.IsReadOnly, Is.True); - s.DefaultReadOnly = false; - Assert.That(query.IsReadOnly, Is.False); - query.SetReadOnly(true); - Assert.That(query.IsReadOnly, Is.True); - s.DefaultReadOnly = true; - Assert.That(query.IsReadOnly, Is.True); - s.DefaultReadOnly = false; - Assert.That(query.IsReadOnly, Is.True); - query.SetReadOnly(false); - Assert.That(query.IsReadOnly, Is.False); - s.DefaultReadOnly = true; - Assert.That(query.IsReadOnly, Is.False); - query.SetReadOnly(true); - Assert.That(query.IsReadOnly, Is.True); - s.DefaultReadOnly = false; - Assert.That(s.DefaultReadOnly, Is.False); - IEnumerator it = query.Enumerable().GetEnumerator(); + QueryReadOnlyIterateAssertReadOnly(query, s); + + var it = query.AsyncEnumerable().GetAsyncEnumerator(); Assert.That(query.IsReadOnly, Is.True); DataPoint dpLast = s.Get(lastDataPointId); Assert.That(s.IsReadOnly(dpLast), Is.False); query.SetReadOnly(false); Assert.That(query.IsReadOnly, Is.False); Assert.That(s.DefaultReadOnly, Is.False); - + int i = 0; - - while (it.MoveNext()) + while (await it.MoveNextAsync()) { - Assert.That(s.DefaultReadOnly, Is.False); - DataPoint dp = it.Current; - Assert.That(s.DefaultReadOnly, Is.False); - - if (dp.Id == dpLast.Id) - { - //dpLast existed in the session before executing the read-only query - Assert.That(s.IsReadOnly(dp), Is.False); - } - else - { - Assert.That(s.IsReadOnly(dp), Is.True); - } - - if (++i == 50) - { - s.SetReadOnly(dp, false); - nExpectedChanges = (dp == dpLast ? 1 : 2 ); - } - - dp.Description = "done!"; + QueryReadOnlyIterateAssertRow(s, it.Current, dpLast, ref i, ref nExpectedChanges); } - + Assert.That(s.DefaultReadOnly, Is.False); - - t.Commit(); - } - - s.Clear(); - - using (ITransaction t = s.BeginTransaction()) - { - try - { - IList single = s.CreateQuery("from DataPoint where Description = 'done!'").List(); - Assert.That(single.Count, Is.EqualTo(nExpectedChanges)); - } - finally - { - // cleanup - s.CreateQuery("delete from DataPoint").ExecuteUpdate(); - } - + t.Commit(); } + + QueryIterateClear(s, nExpectedChanges); + } + } + + private void QueryReadOnlyIterateAssertReadOnly(IQuery query, ISession s) + { + Assert.That(query.IsReadOnly, Is.False); + s.DefaultReadOnly = true; + Assert.That(query.IsReadOnly, Is.True); + s.DefaultReadOnly = false; + Assert.That(query.IsReadOnly, Is.False); + query.SetReadOnly(true); + Assert.That(query.IsReadOnly, Is.True); + s.DefaultReadOnly = true; + Assert.That(query.IsReadOnly, Is.True); + s.DefaultReadOnly = false; + Assert.That(query.IsReadOnly, Is.True); + query.SetReadOnly(false); + Assert.That(query.IsReadOnly, Is.False); + s.DefaultReadOnly = true; + Assert.That(query.IsReadOnly, Is.False); + query.SetReadOnly(true); + Assert.That(query.IsReadOnly, Is.True); + s.DefaultReadOnly = false; + Assert.That(s.DefaultReadOnly, Is.False); + } + + private void QueryReadOnlyIterateAssertRow(ISession s, DataPoint dp, DataPoint dpLast, ref int i, ref int nExpectedChanges) + { + Assert.That(s.DefaultReadOnly, Is.False); + + if (dp.Id == dpLast.Id) + { + //dpLast existed in the session before executing the read-only query + Assert.That(s.IsReadOnly(dp), Is.False); + } + else + { + Assert.That(s.IsReadOnly(dp), Is.True); + } + + if (++i == 50) + { + s.SetReadOnly(dp, false); + nExpectedChanges = (dp == dpLast ? 1 : 2); } + + dp.Description = "done!"; } [Test] public void QueryModifiableIterate() { - long lastDataPointId = 0; int nExpectedChanges = 0; - + QueryIterateCreate(out var lastDataPointId); + using (ISession s = OpenSession()) { s.CacheMode = CacheMode.Ignore; using (ITransaction t = s.BeginTransaction()) { - DataPoint dp = null; - - for (int i = 0; i < 100; i++) + s.DefaultReadOnly = true; + + IQuery query = s.CreateQuery("from DataPoint dp order by dp.X asc"); + QueryModifiableIterateAssertReadOnly(query, s); + + IEnumerator it = query.Enumerable().GetEnumerator(); + Assert.That(query.IsReadOnly, Is.False); + DataPoint dpLast = s.Get(lastDataPointId); + Assert.That(s.IsReadOnly(dpLast), Is.True); + query.SetReadOnly(true); + Assert.That(query.IsReadOnly, Is.True); + Assert.That(s.DefaultReadOnly, Is.True); + + int i = 0; + while (it.MoveNext()) { - dp = new DataPoint(); - dp.X = 0.1M * i; - dp.Y = (decimal)System.Math.Cos((double)dp.X); - s.Save(dp); + QueryModifiableIterateAssertRow(s, it.Current, dpLast, ref i, ref nExpectedChanges); } - t.Commit(); - lastDataPointId = dp.Id; + Assert.That(s.DefaultReadOnly, Is.True); + t.Commit(); } + + QueryIterateClear(s, nExpectedChanges); } - + } + + [Test] + public async Task QueryModifiableAsyncIterate() + { + int nExpectedChanges = 0; + QueryIterateCreate(out var lastDataPointId); + using (ISession s = OpenSession()) { s.CacheMode = CacheMode.Ignore; - + using (ITransaction t = s.BeginTransaction()) { s.DefaultReadOnly = true; IQuery query = s.CreateQuery("from DataPoint dp order by dp.X asc"); - - Assert.That(query.IsReadOnly, Is.True); - s.DefaultReadOnly = false; - Assert.That(query.IsReadOnly, Is.False); - s.DefaultReadOnly = true; - Assert.That(query.IsReadOnly, Is.True); - query.SetReadOnly(false); - Assert.That(query.IsReadOnly, Is.False); - s.DefaultReadOnly = false; - Assert.That(query.IsReadOnly, Is.False); - s.DefaultReadOnly = true; - Assert.That(query.IsReadOnly, Is.False); - query.SetReadOnly(true); - Assert.That(query.IsReadOnly, Is.True); - s.DefaultReadOnly = false; - Assert.That(query.IsReadOnly, Is.True); - - query.SetReadOnly(false); - Assert.That(query.IsReadOnly, Is.False); - s.DefaultReadOnly = true; - Assert.That(s.DefaultReadOnly, Is.True); - - IEnumerator it = query.Enumerable().GetEnumerator(); + QueryModifiableIterateAssertReadOnly(query, s); + + var it = query.AsyncEnumerable().GetAsyncEnumerator(); Assert.That(query.IsReadOnly, Is.False); DataPoint dpLast = s.Get(lastDataPointId); Assert.That(s.IsReadOnly(dpLast), Is.True); query.SetReadOnly(true); Assert.That(query.IsReadOnly, Is.True); Assert.That(s.DefaultReadOnly, Is.True); - + int i = 0; - - while (it.MoveNext()) + while (await it.MoveNextAsync()) { - Assert.That(s.DefaultReadOnly, Is.True); - DataPoint dp = it.Current; - Assert.That(s.DefaultReadOnly, Is.True); - - if (dp.Id == dpLast.Id) - { - //dpLast existed in the session before executing the read-only query - Assert.That(s.IsReadOnly(dp), Is.True); - } - else - { - Assert.That(s.IsReadOnly(dp), Is.False); - } - - if (++i == 50) - { - s.SetReadOnly(dp, true); - nExpectedChanges = (dp == dpLast ? 99 : 98); - } - - dp.Description = "done!"; + QueryModifiableIterateAssertRow(s, it.Current, dpLast, ref i, ref nExpectedChanges); } - + Assert.That(s.DefaultReadOnly, Is.True); - t.Commit(); } - - s.Clear(); - + + QueryIterateClear(s, nExpectedChanges); + } + } + + private void QueryModifiableIterateAssertReadOnly(IQuery query, ISession s) + { + Assert.That(query.IsReadOnly, Is.True); + s.DefaultReadOnly = false; + Assert.That(query.IsReadOnly, Is.False); + s.DefaultReadOnly = true; + Assert.That(query.IsReadOnly, Is.True); + query.SetReadOnly(false); + Assert.That(query.IsReadOnly, Is.False); + s.DefaultReadOnly = false; + Assert.That(query.IsReadOnly, Is.False); + s.DefaultReadOnly = true; + Assert.That(query.IsReadOnly, Is.False); + query.SetReadOnly(true); + Assert.That(query.IsReadOnly, Is.True); + s.DefaultReadOnly = false; + Assert.That(query.IsReadOnly, Is.True); + + query.SetReadOnly(false); + Assert.That(query.IsReadOnly, Is.False); + s.DefaultReadOnly = true; + Assert.That(s.DefaultReadOnly, Is.True); + } + + private void QueryModifiableIterateAssertRow(ISession s, DataPoint dp, DataPoint dpLast, ref int i, ref int nExpectedChanges) + { + Assert.That(s.DefaultReadOnly, Is.True); + Assert.That(s.DefaultReadOnly, Is.True); + + if (dp.Id == dpLast.Id) + { + //dpLast existed in the session before executing the read-only query + Assert.That(s.IsReadOnly(dp), Is.True); + } + else + { + Assert.That(s.IsReadOnly(dp), Is.False); + } + + if (++i == 50) + { + s.SetReadOnly(dp, true); + nExpectedChanges = (dp == dpLast ? 99 : 98); + } + + dp.Description = "done!"; + } + + private void QueryIterateCreate(out long lastDataPointId) + { + using (ISession s = OpenSession()) + { + s.CacheMode = CacheMode.Ignore; + using (ITransaction t = s.BeginTransaction()) { - try - { - IList list = s.CreateQuery("from DataPoint where Description = 'done!'").List(); - Assert.That(list.Count, Is.EqualTo(nExpectedChanges)); - } - finally + DataPoint dp = null; + + for (int i = 0; i < 100; i++) { - // cleanup - s.CreateQuery("delete from DataPoint").ExecuteUpdate(); + dp = new DataPoint(); + dp.X = 0.1M * i; + dp.Y = (decimal) System.Math.Cos((double) dp.X); + s.Save(dp); } - t.Commit(); + + lastDataPointId = dp.Id; } } } - + + private void QueryIterateClear(ISession s, int nExpectedChanges) + { + s.Clear(); + using (ITransaction t = s.BeginTransaction()) + { + try + { + IList single = s.CreateQuery("from DataPoint where Description = 'done!'").List(); + Assert.That(single.Count, Is.EqualTo(nExpectedChanges)); + } + finally + { + // cleanup + s.CreateQuery("delete from DataPoint").ExecuteUpdate(); + } + + t.Commit(); + } + } + [Test] public void ReadOnlyRefresh() { diff --git a/src/NHibernate.Test/Stats/StatsFixture.cs b/src/NHibernate.Test/Stats/StatsFixture.cs index 98beb3c37a3..79d92136177 100644 --- a/src/NHibernate.Test/Stats/StatsFixture.cs +++ b/src/NHibernate.Test/Stats/StatsFixture.cs @@ -161,7 +161,8 @@ public void QueryStatGathering() Assert.AreEqual(maxTime, stats.QueryExecutionMaxTime); Assert.AreEqual( continents, stats.QueryExecutionMaxTimeQueryString ); - IEnumerable itr = s.CreateQuery(continents).Enumerable(); + var itr = s.CreateQuery(continents).Enumerable().GetEnumerator(); + itr.MoveNext(); // Enumerable() should increment the execution count Assert.AreEqual(2, continentStats.ExecutionCount, "unexpected execution count"); // but should not effect the cumulative row count diff --git a/src/NHibernate.Test/UtilityTest/AsyncJoinedEnumerableFixture.cs b/src/NHibernate.Test/UtilityTest/AsyncJoinedEnumerableFixture.cs new file mode 100644 index 00000000000..7588b61e288 --- /dev/null +++ b/src/NHibernate.Test/UtilityTest/AsyncJoinedEnumerableFixture.cs @@ -0,0 +1,191 @@ +using System.Linq; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using NHibernate.Util; +using NUnit.Framework; +using NUnit.Framework.Constraints; + +namespace NHibernate.Test.UtilityTest +{ + /// + /// Test cases for the JoinedAsyncEnumerable class. + /// + // 6.0 TODO: Remove + [TestFixture] + public class AsyncJoinedEnumerableFixture + { + /// + /// Test that the JoinedAsyncEnumerable works with a single wrapped + /// IAsyncEnumerable as expected when fully enumerating the collection. + /// + [Test] + public async Task WrapsSingleAsync() + { + var expected = new int[] {1, 2, 3}; + var joined = InitSingle(out var first).GetAsyncEnumerator(default); + var index = 0; + + // Simulate await foreach by using MoveNextAsync and DisposeAsync + while (await joined.MoveNextAsync()) + { + Assert.That(joined.Current, Is.EqualTo(expected[index]), "Failure at " + index.ToString()); + index++; + } + + await joined.DisposeAsync(); + + Assert.AreEqual(expected.Length, index, "Every expected value was found"); + Assert.IsTrue(first.EnumeratorDisposed, "Should have been disposed of."); + } + + /// + /// Test that the wrapped IAsyncEnumerable has DisposeAsync called even when + /// the JoinedAsyncEnumerable doesn't enumerate all the way through the + /// collection. + /// + [Test] + public async Task WrapsSingleBreakAsync() + { + int[] expected = new int[] {1, 2, 3}; + var joined = InitSingle(out var first).GetAsyncEnumerator(default); + + await joined.MoveNextAsync(); + Assert.That(joined.Current, Is.EqualTo(expected[0]), "First one was not what was expected."); + await joined.DisposeAsync(); + + // ensure that the first was disposed of even though we didn't enumerate + // all the way through + Assert.IsTrue(first.EnumeratorDisposed, "Should have been disposed of."); + } + + /// + /// Test that the JoinedAsyncEnumerable works with multiple wrapped + /// IAsyncEnumerable as expected when fully enumerating the collections. + /// + [Test] + public async Task WrapsMultipleAsync() + { + var expected = new int[] {1, 2, 3, 4, 5, 6}; + var joined = InitMultiple(out var first, out var second).GetAsyncEnumerator(default); + var index = 0; + + // Simulate await foreach by using MoveNextAsync and DisposeAsync + while (await joined.MoveNextAsync()) + { + Assert.That(joined.Current, Is.EqualTo(expected[index]), "Failure at " + index.ToString()); + index++; + } + + await joined.DisposeAsync(); + + Assert.AreEqual(expected.Length, index, "Every expected value was found"); + Assert.IsTrue(first.EnumeratorDisposed, "First should have been disposed of."); + Assert.IsTrue(second.EnumeratorDisposed, "Second should have been disposed of."); + } + + /// + /// Test that the JoinedAsyncEnumerable works with multiple wrapped + /// IAsyncEnumerable as expected when breaking out. + /// + [Test] + public async Task WrapsMultipleBreak() + { + // break in the first IEnumerator + await WrapsMultipleBreakAsync(2); + + // ensure behavior is consistent if break in 2nd IEnumerator + await WrapsMultipleBreakAsync(4); + } + + private async Task WrapsMultipleBreakAsync(int breakIndex) + { + var expected = new int[] {1, 2, 3, 4, 5, 6}; + var joined = InitMultiple(out var first, out var second).GetAsyncEnumerator(default); + var index = 0; + + // Simulate await foreach by using MoveNextAsync and DisposeAsync + while (index != breakIndex && await joined.MoveNextAsync()) + { + Assert.That(joined.Current, Is.EqualTo(expected[index]), "Failure at " + index.ToString()); + index++; + } + + await joined.DisposeAsync(); + + Assert.That(first.EnumeratorDisposed, Is.True, "First should have been disposed of."); + Assert.That(second.EnumeratorCreated, breakIndex > 3 ? (IConstraint) Is.True : Is.False, "Second should have been created."); + Assert.That(second.EnumeratorDisposed, breakIndex > 3 ? (IConstraint) Is.True : Is.False, "Second should have been disposed of."); + } + + private IAsyncEnumerable InitSingle(out AsyncEnumerableTester first) + { + first = new AsyncEnumerableTester(new List{1, 2, 3}); + return CreateJoinedAsyncEnumerable(new List> { first }); + } + + private IAsyncEnumerable InitMultiple(out AsyncEnumerableTester first, out AsyncEnumerableTester second) + { + first = new AsyncEnumerableTester(new List{1, 2, 3}); + second = new AsyncEnumerableTester(new List{4, 5, 6}); + return CreateJoinedAsyncEnumerable(new List> { first, second }); + } + + private IAsyncEnumerable CreateJoinedAsyncEnumerable(IEnumerable> asyncEnumerables) + { + var type = typeof(ISession).Assembly.GetType("NHibernate.Util.JoinedAsyncEnumerable`1").MakeGenericType(typeof(T)); + return (IAsyncEnumerable) type.GetConstructors() + .First() + .Invoke(new object[] { asyncEnumerables }); + } + + /// + /// Simple class that wraps an array list for testing purposes. + /// + private class AsyncEnumerableTester : IAsyncEnumerable + { + private readonly List _list; + private AsyncEnumerableTesterEnumerator _enumerator; + + public AsyncEnumerableTester(List wrappedList) + { + _list = wrappedList; + } + + public bool EnumeratorCreated => _enumerator != null; + + public bool EnumeratorDisposed => _enumerator?.IsDisposed == true; + + public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + _enumerator = new AsyncEnumerableTesterEnumerator(_list.GetEnumerator()); + return _enumerator; + } + + private sealed class AsyncEnumerableTesterEnumerator : IAsyncEnumerator + { + private readonly IEnumerator _enumerator; + + public AsyncEnumerableTesterEnumerator(IEnumerator enumerator) + { + _enumerator = enumerator; + } + + public bool IsDisposed { get; private set; } + + public T Current => _enumerator.Current; + + public ValueTask MoveNextAsync() + { + return new ValueTask(_enumerator.MoveNext()); + } + + public ValueTask DisposeAsync() + { + IsDisposed = true; + return default; + } + } + } + } +} diff --git a/src/NHibernate.Test/UtilityTest/JoinedEnumerableFixture.cs b/src/NHibernate.Test/UtilityTest/JoinedEnumerableFixture.cs index c037121dc7c..5544df59b66 100644 --- a/src/NHibernate.Test/UtilityTest/JoinedEnumerableFixture.cs +++ b/src/NHibernate.Test/UtilityTest/JoinedEnumerableFixture.cs @@ -8,6 +8,8 @@ namespace NHibernate.Test.UtilityTest /// /// Test cases for the class. /// + // Since 5.3 + [Obsolete] [TestFixture] public class JoinedEnumerableFixture { diff --git a/src/NHibernate/Async/Engine/ISessionImplementor.cs b/src/NHibernate/Async/Engine/ISessionImplementor.cs index d5108c3e8f8..f84be7743df 100644 --- a/src/NHibernate/Async/Engine/ISessionImplementor.cs +++ b/src/NHibernate/Async/Engine/ISessionImplementor.cs @@ -12,6 +12,8 @@ using System.Collections; using System.Collections.Generic; using System.Data.Common; +using System.Threading; +using System.Threading.Tasks; using NHibernate.AdoNet; using NHibernate.Cache; using NHibernate.Collection; @@ -28,9 +30,7 @@ namespace NHibernate.Engine { - using System.Threading.Tasks; - using System.Threading; - internal static partial class SessionImplementorExtensions + public static partial class SessionImplementorExtensions { internal static async Task AutoFlushIfRequiredAsync(this ISessionImplementor implementor, ISet querySpaces, CancellationToken cancellationToken) @@ -105,20 +105,6 @@ public partial interface ISessionImplementor Task ListAsync(CriteriaImpl criteria, CancellationToken cancellationToken); - /// - /// Execute an Iterate() query - /// - /// - /// - /// A cancellation token that can be used to cancel the work - /// - Task EnumerableAsync(IQueryExpression query, QueryParameters parameters, CancellationToken cancellationToken); - - /// - /// Strongly-typed version of - /// - Task> EnumerableAsync(IQueryExpression query, QueryParameters queryParameters, CancellationToken cancellationToken); - /// /// Execute a filter /// @@ -134,16 +120,6 @@ public partial interface ISessionImplementor /// Task> ListFilterAsync(object collection, string filter, QueryParameters parameters, CancellationToken cancellationToken); - /// - /// Collection from a filter - /// - Task EnumerableFilterAsync(object collection, string filter, QueryParameters parameters, CancellationToken cancellationToken); - - /// - /// Strongly-typed version of - /// - Task> EnumerableFilterAsync(object collection, string filter, QueryParameters parameters, CancellationToken cancellationToken); - /// /// Notify the session that the transaction is about to complete /// diff --git a/src/NHibernate/Async/Engine/Query/HQLQueryPlan.cs b/src/NHibernate/Async/Engine/Query/HQLQueryPlan.cs index 853d1ad1261..671c2a7c081 100644 --- a/src/NHibernate/Async/Engine/Query/HQLQueryPlan.cs +++ b/src/NHibernate/Async/Engine/Query/HQLQueryPlan.cs @@ -11,7 +11,8 @@ using System; using System.Collections; using System.Collections.Generic; - +using System.Threading; +using System.Threading.Tasks; using NHibernate.Event; using NHibernate.Hql; using NHibernate.Linq; @@ -20,15 +21,11 @@ namespace NHibernate.Engine.Query { - using System.Threading.Tasks; - using System.Threading; public partial interface IQueryPlan { Task PerformListAsync(QueryParameters queryParameters, ISessionImplementor statelessSessionImpl, IList results, CancellationToken cancellationToken); Task PerformExecuteUpdateAsync(QueryParameters queryParameters, ISessionImplementor statelessSessionImpl, CancellationToken cancellationToken); - Task> PerformIterateAsync(QueryParameters queryParameters, IEventSource session, CancellationToken cancellationToken); - Task PerformIterateAsync(QueryParameters queryParameters, IEventSource session, CancellationToken cancellationToken); - } + } public partial class HQLQueryPlan : IQueryPlan { @@ -99,32 +96,27 @@ public async Task PerformListAsync(QueryParameters queryParameters, ISessionImpl ArrayHelper.AddAll(combinedResults, tmp); } } - - public async Task PerformIterateAsync(QueryParameters queryParameters, IEventSource session, CancellationToken cancellationToken) + // Since v5.3 + /// + [Obsolete("This method has no more usages and will be removed in a future version")] + public Task PerformIterateAsync(QueryParameters queryParameters, IEventSource session, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); - if (Log.IsDebugEnabled()) + if (cancellationToken.IsCancellationRequested) { - Log.Debug("enumerable: {0}", _sourceQuery); - queryParameters.LogParameters(session.Factory); + return Task.FromCanceled(cancellationToken); } - if (Translators.Length == 0) + try { - return CollectionHelper.EmptyEnumerable; + return Task.FromResult(PerformIterate(queryParameters, session)); } - if (Translators.Length == 1) + catch (Exception ex) { - return await (Translators[0].GetEnumerableAsync(queryParameters, session, cancellationToken)).ConfigureAwait(false); + return Task.FromException(ex); } - var results = new IEnumerable[Translators.Length]; - for (int i = 0; i < Translators.Length; i++) - { - var result = await (Translators[i].GetEnumerableAsync(queryParameters, session, cancellationToken)).ConfigureAwait(false); - results[i] = result; - } - return new JoinedEnumerable(results); } - + // Since v5.3 + /// + [Obsolete("This method has no more usages and will be removed in a future version")] public async Task> PerformIterateAsync(QueryParameters queryParameters, IEventSource session, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/NHibernate/Async/Hql/Ast/ANTLR/QueryTranslatorImpl.cs b/src/NHibernate/Async/Hql/Ast/ANTLR/QueryTranslatorImpl.cs index e36847d2988..2edd6969e7c 100644 --- a/src/NHibernate/Async/Hql/Ast/ANTLR/QueryTranslatorImpl.cs +++ b/src/NHibernate/Async/Hql/Ast/ANTLR/QueryTranslatorImpl.cs @@ -105,6 +105,8 @@ public async Task ListAsync(ISessionImplementor session, QueryParameters return results; } + // Since v5.3 + [Obsolete("This method has no more usages and will be removed in a future version")] public Task GetEnumerableAsync(QueryParameters queryParameters, IEventSource session, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) @@ -113,8 +115,7 @@ public Task GetEnumerableAsync(QueryParameters queryParameters, IEv } try { - ErrorIfDML(); - return _queryLoader.GetEnumerableAsync(queryParameters, session, cancellationToken); + return Task.FromResult(GetEnumerable(queryParameters, session)); } catch (Exception ex) { diff --git a/src/NHibernate/Async/Hql/IQueryTranslator.cs b/src/NHibernate/Async/Hql/IQueryTranslator.cs index fd11e81d03b..bba04614da2 100644 --- a/src/NHibernate/Async/Hql/IQueryTranslator.cs +++ b/src/NHibernate/Async/Hql/IQueryTranslator.cs @@ -8,17 +8,20 @@ //------------------------------------------------------------------------------ +using System; using System.Collections; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using NHibernate.Engine; using NHibernate.Engine.Query; using NHibernate.Event; +using NHibernate.Hql.Ast.ANTLR; using NHibernate.Type; +using NHibernate.Util; namespace NHibernate.Hql { - using System.Threading.Tasks; - using System.Threading; public partial interface IQueryTranslator { @@ -32,8 +35,6 @@ public partial interface IQueryTranslator /// Task ListAsync(ISessionImplementor session, QueryParameters queryParameters, CancellationToken cancellationToken); - Task GetEnumerableAsync(QueryParameters queryParameters, IEventSource session, CancellationToken cancellationToken); - // Not ported: //IScrollableResults scroll(QueryParameters queryParameters, ISessionImplementor session); diff --git a/src/NHibernate/Async/IQuery.cs b/src/NHibernate/Async/IQuery.cs index 31912e9ae7d..e6c15c974b1 100644 --- a/src/NHibernate/Async/IQuery.cs +++ b/src/NHibernate/Async/IQuery.cs @@ -13,39 +13,16 @@ using NHibernate.Transform; using NHibernate.Type; using System.Collections.Generic; +using NHibernate.Impl; +using System.Threading.Tasks; +using System.Threading; +using NHibernate.Util; namespace NHibernate { - using System.Threading.Tasks; - using System.Threading; public partial interface IQuery { - /// - /// Return the query results as an . If the query contains multiple results - /// per row, the results are returned in an instance of object[]. - /// - /// A cancellation token that can be used to cancel the work - /// - ///

- /// Entities returned as results are initialized on demand. The first SQL query returns - /// identifiers only. - ///

- ///

- /// This is a good strategy to use if you expect a high number of the objects - /// returned to be already loaded in the or in the 2nd level cache. - ///

- ///
- Task EnumerableAsync(CancellationToken cancellationToken = default(CancellationToken)); - - /// - /// Strongly-typed version of . - /// - /// A cancellation token that can be used to cancel the work - /// - /// - Task> EnumerableAsync(CancellationToken cancellationToken = default(CancellationToken)); - /// /// Return the query results as an . If the query contains multiple results per row, /// the results are returned in an instance of object[]. diff --git a/src/NHibernate/Async/Impl/AbstractQueryImpl.cs b/src/NHibernate/Async/Impl/AbstractQueryImpl.cs index 24588054de3..0907cbece08 100644 --- a/src/NHibernate/Async/Impl/AbstractQueryImpl.cs +++ b/src/NHibernate/Async/Impl/AbstractQueryImpl.cs @@ -32,8 +32,7 @@ public abstract partial class AbstractQueryImpl : IQuery #region Execution methods public abstract Task ExecuteUpdateAsync(CancellationToken cancellationToken = default(CancellationToken)); - public abstract Task EnumerableAsync(CancellationToken cancellationToken = default(CancellationToken)); - public abstract Task> EnumerableAsync(CancellationToken cancellationToken = default(CancellationToken)); + public abstract Task ListAsync(CancellationToken cancellationToken = default(CancellationToken)); public abstract Task ListAsync(IList results, CancellationToken cancellationToken = default(CancellationToken)); public abstract Task> ListAsync(CancellationToken cancellationToken = default(CancellationToken)); diff --git a/src/NHibernate/Async/Impl/AbstractQueryImpl2.cs b/src/NHibernate/Async/Impl/AbstractQueryImpl2.cs index f61abfe4c17..f1727ef56d0 100644 --- a/src/NHibernate/Async/Impl/AbstractQueryImpl2.cs +++ b/src/NHibernate/Async/Impl/AbstractQueryImpl2.cs @@ -38,35 +38,41 @@ public abstract partial class AbstractQueryImpl2 : AbstractQueryImpl } } - public override async Task EnumerableAsync(CancellationToken cancellationToken = default(CancellationToken)) + /// + // Since v5.3 + [Obsolete("Use AsyncEnumerable extension method instead.")] + public override Task EnumerableAsync(CancellationToken cancellationToken = default(CancellationToken)) { - cancellationToken.ThrowIfCancellationRequested(); - VerifyParameters(); - var namedParams = NamedParams; - Before(); + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } try { - return await (Session.EnumerableAsync(ExpandParameters(namedParams), GetQueryParameters(namedParams), cancellationToken)).ConfigureAwait(false); + return Task.FromResult(Enumerable()); } - finally + catch (System.Exception ex) { - After(); + return Task.FromException(ex); } } - public override async Task> EnumerableAsync(CancellationToken cancellationToken = default(CancellationToken)) + /// + // Since v5.3 + [Obsolete("Use AsyncEnumerable extension method instead.")] + public override Task> EnumerableAsync(CancellationToken cancellationToken = default(CancellationToken)) { - cancellationToken.ThrowIfCancellationRequested(); - VerifyParameters(); - var namedParams = NamedParams; - Before(); + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled>(cancellationToken); + } try { - return await (Session.EnumerableAsync(ExpandParameters(namedParams), GetQueryParameters(namedParams), cancellationToken)).ConfigureAwait(false); + return Task.FromResult>(Enumerable()); } - finally + catch (System.Exception ex) { - After(); + return Task.FromException>(ex); } } diff --git a/src/NHibernate/Async/Impl/AbstractSessionImpl.cs b/src/NHibernate/Async/Impl/AbstractSessionImpl.cs index 2d28c429495..714e3053864 100644 --- a/src/NHibernate/Async/Impl/AbstractSessionImpl.cs +++ b/src/NHibernate/Async/Impl/AbstractSessionImpl.cs @@ -14,6 +14,8 @@ using System.Data; using System.Data.Common; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using NHibernate.AdoNet; using NHibernate.Cache; using NHibernate.Collection; @@ -33,8 +35,6 @@ namespace NHibernate.Impl { - using System.Threading.Tasks; - using System.Threading; public abstract partial class AbstractSessionImpl : ISessionImplementor { @@ -106,8 +106,6 @@ public async Task ListFilterAsync(object collection, IQueryExpression que protected abstract Task ListFilterAsync(object collection, IQueryExpression queryExpression, QueryParameters parameters, IList results, CancellationToken cancellationToken); public abstract Task> ListFilterAsync(object collection, string filter, QueryParameters parameters, CancellationToken cancellationToken); - public abstract Task EnumerableFilterAsync(object collection, string filter, QueryParameters parameters, CancellationToken cancellationToken); - public abstract Task> EnumerableFilterAsync(object collection, string filter, QueryParameters parameters, CancellationToken cancellationToken); public abstract Task BeforeTransactionCompletionAsync(ITransaction tx, CancellationToken cancellationToken); public abstract Task FlushBeforeTransactionCompletionAsync(CancellationToken cancellationToken); public abstract Task AfterTransactionCompletionAsync(bool successful, ITransaction tx, CancellationToken cancellationToken); @@ -211,10 +209,6 @@ protected async Task AfterOperationAsync(bool success, CancellationToken cancell public abstract Task CreateFilterAsync(object collection, IQueryExpression queryExpression, CancellationToken cancellationToken); - public abstract Task EnumerableAsync(IQueryExpression queryExpression, QueryParameters queryParameters, CancellationToken cancellationToken); - - public abstract Task> EnumerableAsync(IQueryExpression queryExpression, QueryParameters queryParameters, CancellationToken cancellationToken); - public abstract Task ExecuteUpdateAsync(IQueryExpression queryExpression, QueryParameters queryParameters, CancellationToken cancellationToken); } } diff --git a/src/NHibernate/Async/Impl/AsyncEnumerableImpl.cs b/src/NHibernate/Async/Impl/AsyncEnumerableImpl.cs new file mode 100644 index 00000000000..bfa0e3513e2 --- /dev/null +++ b/src/NHibernate/Async/Impl/AsyncEnumerableImpl.cs @@ -0,0 +1,107 @@ +//------------------------------------------------------------------------------ +// +// 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.Collections; +using System.Collections.Generic; +using System.Data.Common; +using System.Threading; +using System.Threading.Tasks; +using NHibernate.Engine; +using NHibernate.Exceptions; +using NHibernate.SqlCommand; +using NHibernate.Transform; +using NHibernate.Type; + +namespace NHibernate.Impl +{ + + internal partial class AsyncEnumerableImpl : IAsyncEnumerable, IEnumerable + { + + internal sealed partial class AsyncEnumeratorImpl : IAsyncEnumerator, IEnumerator + { + + private async Task ReadAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + try + { + _hasNext = await (_reader.ReadAsync(cancellationToken)).ConfigureAwait(false); + _startedReading = true; + _currentRow++; + } + catch (DbException e) + { + throw ADOExceptionHelper.Convert(_session.Factory.SQLExceptionConverter, e, "Error executing Enumerable() query", + new SqlString(_command.CommandText)); + } + + await (PostReadAsync(cancellationToken)).ConfigureAwait(false); + } + + private async Task PostReadAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + if (_selection != null && _selection.MaxRows != RowSelection.NoValue) + { + _hasNext = _hasNext && (_currentRow < _selection.MaxRows); + } + + bool sessionDefaultReadOnlyOrig = _session.PersistenceContext.DefaultReadOnly; + _session.PersistenceContext.DefaultReadOnly = _readOnly; + try + { + await (MaterializeAndSetCurrentAsync(cancellationToken)).ConfigureAwait(false); + } + finally + { + _session.PersistenceContext.DefaultReadOnly = sessionDefaultReadOnlyOrig; + } + } + + private async Task MaterializeAndSetCurrentAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + if (!_hasNext) + { + // there are no more records in the DataReader so clean up + Log.Debug("exhausted results"); + Current = default; + _session.Batcher.CloseCommand(_command, _reader); + return; + } + + Log.Debug("retrieving next results"); + if (_single && _resultTransformer == null) + { + SetCurrent(await (_types[0].NullSafeGetAsync(_reader, _columnNames[0], _session, null, cancellationToken)).ConfigureAwait(false)); + return; + } + + var currentResults = new object[_types.Length]; + // move through each of the ITypes contained in the DbDataReader and convert them + // to their objects. + for (int i = 0; i < _types.Length; i++) + { + // The IType knows how to extract its value out of the DbDataReader. If the IType + // is a value type then the value will simply be pulled out of the DbDataReader. If + // the IType is an Entity type then the IType will extract the id from the DbDataReader + // and use the ISession to load an instance of the object. + currentResults[i] = await (_types[i].NullSafeGetAsync(_reader, _columnNames[i], _session, null, cancellationToken)).ConfigureAwait(false); + } + + SetCurrent(_resultTransformer != null + ? _resultTransformer.TransformTuple(currentResults, _returnAliases) + : currentResults); + } + } + } +} diff --git a/src/NHibernate/Async/Impl/CollectionFilterImpl.cs b/src/NHibernate/Async/Impl/CollectionFilterImpl.cs index af6b061848e..767fb906dca 100644 --- a/src/NHibernate/Async/Impl/CollectionFilterImpl.cs +++ b/src/NHibernate/Async/Impl/CollectionFilterImpl.cs @@ -24,6 +24,9 @@ namespace NHibernate.Impl public partial class CollectionFilterImpl : QueryImpl { + /// + // Since v5.3 + [Obsolete("Use AsyncEnumerable extension method instead.")] public override Task EnumerableAsync(CancellationToken cancellationToken = default(CancellationToken)) { if (cancellationToken.IsCancellationRequested) @@ -32,9 +35,7 @@ public partial class CollectionFilterImpl : QueryImpl } try { - VerifyParameters(); - IDictionary namedParams = NamedParams; - return Session.EnumerableFilterAsync(collection, ExpandParameterLists(namedParams), GetQueryParameters(namedParams), cancellationToken); + return Task.FromResult(Enumerable()); } catch (Exception ex) { @@ -42,6 +43,9 @@ public partial class CollectionFilterImpl : QueryImpl } } + /// + // Since v5.3 + [Obsolete("Use AsyncEnumerable extension method instead.")] public override Task> EnumerableAsync(CancellationToken cancellationToken = default(CancellationToken)) { if (cancellationToken.IsCancellationRequested) @@ -50,9 +54,7 @@ public partial class CollectionFilterImpl : QueryImpl } try { - VerifyParameters(); - IDictionary namedParams = NamedParams; - return Session.EnumerableFilterAsync(collection, ExpandParameterLists(namedParams), GetQueryParameters(namedParams), cancellationToken); + return Task.FromResult>(Enumerable()); } catch (Exception ex) { diff --git a/src/NHibernate/Async/Impl/ExpressionQueryImpl.cs b/src/NHibernate/Async/Impl/ExpressionQueryImpl.cs index d98e200c5e9..9800444c5df 100644 --- a/src/NHibernate/Async/Impl/ExpressionQueryImpl.cs +++ b/src/NHibernate/Async/Impl/ExpressionQueryImpl.cs @@ -70,11 +70,17 @@ internal partial class ExpressionFilterImpl : ExpressionQueryImpl ArrayHelper.AddAll(results, await (ListAsync(cancellationToken)).ConfigureAwait(false)); } + /// + // Since v5.3 + [Obsolete("Use AsyncEnumerable extension method instead.")] public override Task EnumerableAsync(CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); } + /// + // Since v5.3 + [Obsolete("Use AsyncEnumerable extension method instead.")] public override Task> EnumerableAsync(CancellationToken cancellationToken = default(CancellationToken)) { throw new NotImplementedException(); diff --git a/src/NHibernate/Async/Impl/SessionImpl.cs b/src/NHibernate/Async/Impl/SessionImpl.cs index 7843a87a307..bed0017e5b7 100644 --- a/src/NHibernate/Async/Impl/SessionImpl.cs +++ b/src/NHibernate/Async/Impl/SessionImpl.cs @@ -299,35 +299,41 @@ public override async Task GetQueriesAsync(IQueryExpression } } - public override async Task> EnumerableAsync(IQueryExpression queryExpression, QueryParameters queryParameters, CancellationToken cancellationToken) + /// + // Since v5.3 + [Obsolete("Use AsyncEnumerable extension method instead.")] + public override Task> EnumerableAsync(IQueryExpression queryExpression, QueryParameters queryParameters, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); - using (BeginProcess()) + if (cancellationToken.IsCancellationRequested) { - queryParameters.ValidateParameters(); - var plan = GetHQLQueryPlan(queryExpression, true); - await (AutoFlushIfRequiredAsync(plan.QuerySpaces, cancellationToken)).ConfigureAwait(false); - - using (SuspendAutoFlush()) //stops flush being called multiple times if this method is recursively called - { - return await (plan.PerformIterateAsync(queryParameters, this, cancellationToken)).ConfigureAwait(false); - } + return Task.FromCanceled>(cancellationToken); + } + try + { + return Task.FromResult>(Enumerable(queryExpression, queryParameters)); + } + catch (Exception ex) + { + return Task.FromException>(ex); } } - public override async Task EnumerableAsync(IQueryExpression queryExpression, QueryParameters queryParameters, CancellationToken cancellationToken) + /// + // Since v5.3 + [Obsolete("Use AsyncEnumerable extension method instead.")] + public override Task EnumerableAsync(IQueryExpression queryExpression, QueryParameters queryParameters, CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); - using (BeginProcess()) + if (cancellationToken.IsCancellationRequested) { - queryParameters.ValidateParameters(); - var plan = GetHQLQueryPlan(queryExpression, true); - await (AutoFlushIfRequiredAsync(plan.QuerySpaces, cancellationToken)).ConfigureAwait(false); - - using (SuspendAutoFlush()) //stops flush being called multiple times if this method is recursively called - { - return await (plan.PerformIterateAsync(queryParameters, this, cancellationToken)).ConfigureAwait(false); - } + return Task.FromCanceled(cancellationToken); + } + try + { + return Task.FromResult(Enumerable(queryExpression, queryParameters)); + } + catch (Exception ex) + { + return Task.FromException(ex); } } @@ -1101,23 +1107,29 @@ public override async Task> ListFilterAsync(object collection, strin return results; } + /// + // Since v5.3 + [Obsolete("Use AsyncEnumerableFilter extension method instead.")] public override async Task EnumerableFilterAsync(object collection, string filter, QueryParameters queryParameters, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); using (BeginProcess()) { var plan = await (GetFilterQueryPlanAsync(collection, filter, queryParameters, true, cancellationToken)).ConfigureAwait(false); - return await (plan.PerformIterateAsync(queryParameters, this, cancellationToken)).ConfigureAwait(false); + return plan.PerformIterate(queryParameters, this); } } + /// + // Since v5.3 + [Obsolete("Use AsyncEnumerableFilter extension method instead.")] public override async Task> EnumerableFilterAsync(object collection, string filter, QueryParameters queryParameters, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); using (BeginProcess()) { var plan = await (GetFilterQueryPlanAsync(collection, filter, queryParameters, true, cancellationToken)).ConfigureAwait(false); - return await (plan.PerformIterateAsync(queryParameters, this, cancellationToken)).ConfigureAwait(false); + return plan.PerformIterate(queryParameters, this); } } diff --git a/src/NHibernate/Async/Impl/SqlQueryImpl.cs b/src/NHibernate/Async/Impl/SqlQueryImpl.cs index ec46826430a..ea9e947e940 100644 --- a/src/NHibernate/Async/Impl/SqlQueryImpl.cs +++ b/src/NHibernate/Async/Impl/SqlQueryImpl.cs @@ -81,11 +81,17 @@ public partial class SqlQueryImpl : AbstractQueryImpl, ISQLQuery, ISynchronizabl } } + /// + // Since v5.3 + [Obsolete("Use AsyncEnumerable extension method instead.")] public override Task EnumerableAsync(CancellationToken cancellationToken = default(CancellationToken)) { throw new NotSupportedException("SQL queries do not currently support enumeration"); } + /// + // Since v5.3 + [Obsolete("Use AsyncEnumerable extension method instead.")] public override Task> EnumerableAsync(CancellationToken cancellationToken = default(CancellationToken)) { throw new NotSupportedException("SQL queries do not currently support enumeration"); diff --git a/src/NHibernate/Async/Impl/StatelessSessionImpl.cs b/src/NHibernate/Async/Impl/StatelessSessionImpl.cs index f1ba521b612..2181a5b3c12 100644 --- a/src/NHibernate/Async/Impl/StatelessSessionImpl.cs +++ b/src/NHibernate/Async/Impl/StatelessSessionImpl.cs @@ -164,11 +164,17 @@ public override async Task ListAsync(CriteriaImpl criteria, IList results, Cance } } + /// + // Since v5.3 + [Obsolete("Use AsyncEnumerable extension method instead.")] public override Task EnumerableAsync(IQueryExpression queryExpression, QueryParameters queryParameters, CancellationToken cancellationToken) { throw new NotImplementedException(); } + /// + // Since v5.3 + [Obsolete("Use AsyncEnumerable extension method instead.")] public override Task> EnumerableAsync(IQueryExpression queryExpression, QueryParameters queryParameters, CancellationToken cancellationToken) { throw new NotImplementedException(); @@ -189,11 +195,17 @@ public override Task> ListFilterAsync(object collection, string filt throw new NotSupportedException(); } + /// + // Since v5.3 + [Obsolete("Use AsyncEnumerableFilter extension method instead.")] public override Task EnumerableFilterAsync(object collection, string filter, QueryParameters parameters, CancellationToken cancellationToken) { throw new NotSupportedException(); } + /// + // Since v5.3 + [Obsolete("Use AsyncEnumerableFilter extension method instead.")] public override Task> EnumerableFilterAsync(object collection, string filter, QueryParameters parameters, CancellationToken cancellationToken) { throw new NotSupportedException(); diff --git a/src/NHibernate/Async/Loader/Hql/QueryLoader.cs b/src/NHibernate/Async/Loader/Hql/QueryLoader.cs index 9135a5b8481..9e335f61698 100644 --- a/src/NHibernate/Async/Loader/Hql/QueryLoader.cs +++ b/src/NHibernate/Async/Loader/Hql/QueryLoader.cs @@ -14,6 +14,8 @@ using System.Data.Common; using System.Diagnostics; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using NHibernate.Engine; using NHibernate.Event; using NHibernate.Hql.Ast.ANTLR; @@ -30,8 +32,6 @@ namespace NHibernate.Loader.Hql { - using System.Threading.Tasks; - using System.Threading; public partial class QueryLoader : BasicLoader { @@ -87,34 +87,29 @@ protected override async Task GetResultRowAsync(object[] row, DbDataRe return resultRow; } - internal async Task GetEnumerableAsync(QueryParameters queryParameters, IEventSource session, CancellationToken cancellationToken) + internal async Task InitializeEnumerableAsync(QueryParameters queryParameters, ISessionImplementor session, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - CheckQuery(queryParameters); - Stopwatch stopWatch = null; - if (session.Factory.Statistics.IsStatisticsEnabled) + await (session.AutoFlushIfRequiredAsync(_queryTranslator.QuerySpaces, cancellationToken)).ConfigureAwait(false); + using (session.SuspendAutoFlush()) { - stopWatch = Stopwatch.StartNew(); - } - - var cmd = await (PrepareQueryCommandAsync(queryParameters, false, session, cancellationToken)).ConfigureAwait(false); - - // This DbDataReader is disposed of in EnumerableImpl.Dispose - var rs = await (GetResultSetAsync(cmd, queryParameters, session, null, cancellationToken)).ConfigureAwait(false); + Stopwatch stopWatch = null; + if (session.Factory.Statistics.IsStatisticsEnabled) + { + stopWatch = Stopwatch.StartNew(); + } - var resultTransformer = _selectNewTransformer ?? queryParameters.ResultTransformer; - IEnumerable result = - new EnumerableImpl(rs, cmd, session, queryParameters.IsReadOnly(session), _queryTranslator.ReturnTypes, _queryTranslator.GetColumnNames(), queryParameters.RowSelection, resultTransformer, _queryReturnAliases); + var command = await (PrepareQueryCommandAsync(queryParameters, false, session, cancellationToken)).ConfigureAwait(false); + var dataReader = await (GetResultSetAsync(command, queryParameters, session, null, cancellationToken)).ConfigureAwait(false); + if (stopWatch != null) + { + stopWatch.Stop(); + session.Factory.StatisticsImplementor.QueryExecuted("HQL: " + _queryTranslator.QueryString, 0, stopWatch.Elapsed); + session.Factory.StatisticsImplementor.QueryExecuted(QueryIdentifier, 0, stopWatch.Elapsed); + } - if (stopWatch != null) - { - stopWatch.Stop(); - session.Factory.StatisticsImplementor.QueryExecuted("HQL: " + _queryTranslator.QueryString, 0, stopWatch.Elapsed); - // NH: Different behavior (H3.2 use QueryLoader in AST parser) we need statistic for orginal query too. - // probably we have a bug some where else for statistic RowCount - session.Factory.StatisticsImplementor.QueryExecuted(QueryIdentifier, 0, stopWatch.Elapsed); + return new InitializeEnumerableResult(command, dataReader); } - return result; } } } diff --git a/src/NHibernate/Engine/ISessionImplementor.cs b/src/NHibernate/Engine/ISessionImplementor.cs index 84b30da88ae..c2720902d5d 100644 --- a/src/NHibernate/Engine/ISessionImplementor.cs +++ b/src/NHibernate/Engine/ISessionImplementor.cs @@ -2,6 +2,8 @@ using System.Collections; using System.Collections.Generic; using System.Data.Common; +using System.Threading; +using System.Threading.Tasks; using NHibernate.AdoNet; using NHibernate.Cache; using NHibernate.Collection; @@ -19,7 +21,7 @@ namespace NHibernate.Engine { // 6.0 TODO: Convert to interface methods, excepted SwitchCacheMode - internal static partial class SessionImplementorExtensions + public static partial class SessionImplementorExtensions { /// /// Instantiate the entity class, initializing with the given identifier @@ -62,6 +64,38 @@ internal static void AutoFlushIfRequired(this ISessionImplementor implementor, I (implementor as AbstractSessionImpl)?.AutoFlushIfRequired(querySpaces); } + internal static IDisposable SuspendAutoFlush(this ISessionImplementor implementor) + { + return (implementor as IEventSource)?.SuspendAutoFlush(); + } + + /// + /// Returns an which can be enumerated asynchronously. + /// + /// The element type. + /// The session. + /// The query to execute. + /// The query parameters. + public static IAsyncEnumerable AsyncEnumerable(this ISessionImplementor session, IQueryExpression query, QueryParameters queryParameters) + { + return ReflectHelper.CastOrThrow(session, "async enumerable") + .AsyncEnumerable(query, queryParameters); + } + + /// + /// Returns an which can be enumerated asynchronously. + /// + /// The element type. + /// The session. + /// The collection to filter. + /// The filter to apply. + /// The query parameters. + public static IAsyncEnumerable AsyncEnumerableFilter(this ISessionImplementor session, object collection, string filter, QueryParameters parameters) + { + return ReflectHelper.CastOrThrow(session, "async enumerable") + .AsyncEnumerableFilter(collection, filter, parameters); + } + /// /// Switch the session current cache mode. /// @@ -202,6 +236,24 @@ public partial interface ISessionImplementor /// IEnumerable Enumerable(IQueryExpression query, QueryParameters queryParameters); + /// + /// Execute an Iterate() query + /// + /// + /// + /// A cancellation token that can be used to cancel the work + /// + // Since v5.3 + [Obsolete("Use AsyncEnumerable extension method instead.")] + Task EnumerableAsync(IQueryExpression query, QueryParameters parameters, CancellationToken cancellationToken); + + /// + /// Strongly-typed version of + /// + // Since v5.3 + [Obsolete("Use AsyncEnumerable extension method instead.")] + Task> EnumerableAsync(IQueryExpression query, QueryParameters queryParameters, CancellationToken cancellationToken); + /// /// Execute a filter /// @@ -227,6 +279,20 @@ public partial interface ISessionImplementor /// IEnumerable EnumerableFilter(object collection, string filter, QueryParameters parameters); + /// + /// Collection from a filter + /// + // Since v5.3 + [Obsolete("Use AsyncEnumerableFilter extension method instead.")] + Task EnumerableFilterAsync(object collection, string filter, QueryParameters parameters, CancellationToken cancellationToken); + + /// + /// Strongly-typed version of + /// + // Since v5.3 + [Obsolete("Use AsyncEnumerableFilter extension method instead.")] + Task> EnumerableFilterAsync(object collection, string filter, QueryParameters parameters, CancellationToken cancellationToken); + /// Get the for any instance /// optional entity name /// the entity instance diff --git a/src/NHibernate/Engine/Query/HQLQueryPlan.cs b/src/NHibernate/Engine/Query/HQLQueryPlan.cs index ee1062a26e9..f49fe021fa7 100644 --- a/src/NHibernate/Engine/Query/HQLQueryPlan.cs +++ b/src/NHibernate/Engine/Query/HQLQueryPlan.cs @@ -1,7 +1,8 @@ using System; using System.Collections; using System.Collections.Generic; - +using System.Threading; +using System.Threading.Tasks; using NHibernate.Event; using NHibernate.Hql; using NHibernate.Linq; @@ -18,9 +19,34 @@ public partial interface IQueryPlan ReturnMetadata ReturnMetadata { get; } void PerformList(QueryParameters queryParameters, ISessionImplementor statelessSessionImpl, IList results); int PerformExecuteUpdate(QueryParameters queryParameters, ISessionImplementor statelessSessionImpl); - IEnumerable PerformIterate(QueryParameters queryParameters, IEventSource session); - IEnumerable PerformIterate(QueryParameters queryParameters, IEventSource session); - } + // 6.0 TODO: Change IEventSource to ISessionImplementor + IEnumerable PerformIterate(QueryParameters queryParameters, IEventSource session); + // 6.0 TODO: Change IEventSource to ISessionImplementor + IEnumerable PerformIterate(QueryParameters queryParameters, IEventSource session); + // Since v5.3 + [Obsolete("This method has no more usages and will be removed in a future version")] + Task> PerformIterateAsync(QueryParameters queryParameters, IEventSource session, CancellationToken cancellationToken); + // Since v5.3 + [Obsolete("This method has no more usages and will be removed in a future version")] + Task PerformIterateAsync(QueryParameters queryParameters, IEventSource session, CancellationToken cancellationToken); + } + + // 6.0 TODO: Move into IQueryPlan + internal static class QueryPlanExtensions + { + /// + /// Returns an which can be enumerated asynchronously. + /// + /// The element type. + /// The query plan. + /// The query parameters. + /// The session. + public static IAsyncEnumerable PerformAsyncIterate(this IQueryPlan queryPlan, QueryParameters queryParameters, ISessionImplementor session) + { + return ReflectHelper.CastOrThrow(queryPlan, "async enumerable") + .PerformAsyncIterate(queryParameters, session); + } + } public interface IQueryExpressionPlan : IQueryPlan { @@ -150,6 +176,7 @@ public void PerformList(QueryParameters queryParameters, ISessionImplementor ses } } + // 6.0 TODO: Make it generic and use it for non generic version public IEnumerable PerformIterate(QueryParameters queryParameters, IEventSource session) { if (Log.IsDebugEnabled()) @@ -157,23 +184,58 @@ public IEnumerable PerformIterate(QueryParameters queryParameters, IEventSource Log.Debug("enumerable: {0}", _sourceQuery); queryParameters.LogParameters(session.Factory); } - if (Translators.Length == 0) + + // Avoid nested enumeration when possible + if (Translators.Length == 1) + { +#pragma warning disable CS0618 + return Translators[0].GetEnumerable(queryParameters, session); +#pragma warning restore CS0618 + } + + return GetEnumerable(queryParameters, session); + } + + private IEnumerable GetEnumerable(QueryParameters queryParameters, IEventSource session) + { + foreach (var t in Translators) + { +#pragma warning disable CS0618 + foreach (var obj in t.GetEnumerable(queryParameters, session)) +#pragma warning restore CS0618 + { + yield return obj; + } + } + } + + public IAsyncEnumerable PerformAsyncIterate(QueryParameters queryParameters, ISessionImplementor session) + { + if (Log.IsDebugEnabled()) { - return CollectionHelper.EmptyEnumerable; + Log.Debug("enumerable: {0}", _sourceQuery); + queryParameters.LogParameters(session.Factory); } + + // Avoid nested enumeration when possible if (Translators.Length == 1) { - return Translators[0].GetEnumerable(queryParameters, session); + return Translators[0].GetAsyncEnumerable(queryParameters, session); } - var results = new IEnumerable[Translators.Length]; - for (int i = 0; i < Translators.Length; i++) + + // 6.0 TODO: Use await foreach + return new JoinedAsyncEnumerable(GetAsyncEnumerables(queryParameters, session)); + } + + private IEnumerable> GetAsyncEnumerables(QueryParameters queryParameters, ISessionImplementor session) + { + foreach (var t in Translators) { - var result = Translators[i].GetEnumerable(queryParameters, session); - results[i] = result; + yield return t.GetAsyncEnumerable(queryParameters, session); } - return new JoinedEnumerable(results); } + // 6.0 TODO: Remove public IEnumerable PerformIterate(QueryParameters queryParameters, IEventSource session) { return new SafetyEnumerable(PerformIterate(queryParameters, session)); diff --git a/src/NHibernate/Hql/Ast/ANTLR/QueryTranslatorImpl.cs b/src/NHibernate/Hql/Ast/ANTLR/QueryTranslatorImpl.cs index 04ec2db6769..924851a3e30 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/QueryTranslatorImpl.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/QueryTranslatorImpl.cs @@ -151,10 +151,24 @@ public IList List(ISessionImplementor session, QueryParameters queryParameters) return results; } + // Since v5.3 + [Obsolete("This method has no more usages and will be removed in a future version")] public IEnumerable GetEnumerable(QueryParameters queryParameters, IEventSource session) { ErrorIfDML(); - return _queryLoader.GetEnumerable(queryParameters, session); + return _queryLoader.GetAsyncEnumerable(queryParameters, session); + } + + public IEnumerable GetEnumerable(QueryParameters queryParameters, IEventSource session) + { + ErrorIfDML(); + return _queryLoader.GetAsyncEnumerable(queryParameters, session); + } + + public IAsyncEnumerable GetAsyncEnumerable(QueryParameters queryParameters, ISessionImplementor session) + { + ErrorIfDML(); + return _queryLoader.GetAsyncEnumerable(queryParameters, session); } public int ExecuteUpdate(QueryParameters queryParameters, ISessionImplementor session) diff --git a/src/NHibernate/Hql/IQueryTranslator.cs b/src/NHibernate/Hql/IQueryTranslator.cs index b74bcfd2f8e..0f13900d263 100644 --- a/src/NHibernate/Hql/IQueryTranslator.cs +++ b/src/NHibernate/Hql/IQueryTranslator.cs @@ -1,9 +1,14 @@ +using System; using System.Collections; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using NHibernate.Engine; using NHibernate.Engine.Query; using NHibernate.Event; +using NHibernate.Hql.Ast.ANTLR; using NHibernate.Type; +using NHibernate.Util; namespace NHibernate.Hql { @@ -37,8 +42,14 @@ public partial interface IQueryTranslator /// IList List(ISessionImplementor session, QueryParameters queryParameters); + // Since v5.3 + [Obsolete("Use the generic extension method instead.")] IEnumerable GetEnumerable(QueryParameters queryParameters, IEventSource session); + // Since v5.3 + [Obsolete("Use GetAsyncEnumerable extension method instead.")] + Task GetEnumerableAsync(QueryParameters queryParameters, IEventSource session, CancellationToken cancellationToken); + // Not ported: //IScrollableResults scroll(QueryParameters queryParameters, ISessionImplementor session); @@ -119,4 +130,34 @@ public partial interface IQueryTranslator ParameterMetadata BuildParameterMetadata(); } + + // 6.0 TODO: Move into IQueryTranslator + internal static class QueryTranslatorExtensions + { + /// + /// Returns an which can be enumerated asynchronously. + /// + /// The element type. + /// The query translator. + /// The query parameters. + /// The session. + public static IAsyncEnumerable GetAsyncEnumerable(this IQueryTranslator queryTranslator, QueryParameters queryParameters, ISessionImplementor session) + { + return ReflectHelper.CastOrThrow(queryTranslator, "async enumerable") + .GetAsyncEnumerable(queryParameters, session); + } + + /// + /// Returns an which can be enumerated asynchronously. + /// + /// The element type. + /// The query translator. + /// The query parameters. + /// The session. + public static IEnumerable GetEnumerable(this IQueryTranslator queryTranslator, QueryParameters queryParameters, ISessionImplementor session) + { + return ReflectHelper.CastOrThrow(queryTranslator, "async enumerable") + .GetEnumerable(queryParameters, session); + } + } } diff --git a/src/NHibernate/IQuery.cs b/src/NHibernate/IQuery.cs index 4a8d062415a..7f168c194d5 100644 --- a/src/NHibernate/IQuery.cs +++ b/src/NHibernate/IQuery.cs @@ -3,6 +3,10 @@ using NHibernate.Transform; using NHibernate.Type; using System.Collections.Generic; +using NHibernate.Impl; +using System.Threading.Tasks; +using System.Threading; +using NHibernate.Util; namespace NHibernate { @@ -119,6 +123,35 @@ public partial interface IQuery /// IEnumerable Enumerable(); + /// + /// Return the query results as an . If the query contains multiple results + /// per row, the results are returned in an instance of object[]. + /// + /// A cancellation token that can be used to cancel the work + /// + ///

+ /// Entities returned as results are initialized on demand. The first SQL query returns + /// identifiers only. + ///

+ ///

+ /// This is a good strategy to use if you expect a high number of the objects + /// returned to be already loaded in the or in the 2nd level cache. + ///

+ ///
+ // Since v5.3 + [Obsolete("Use AsyncEnumerable extension method instead.")] + Task EnumerableAsync(CancellationToken cancellationToken = default(CancellationToken)); + + /// + /// Strongly-typed version of . + /// + /// A cancellation token that can be used to cancel the work + /// + /// + // Since v5.3 + [Obsolete("Use AsyncEnumerable extension method instead.")] + Task> EnumerableAsync(CancellationToken cancellationToken = default(CancellationToken)); + /// /// Return the query results as an . If the query contains multiple results per row, /// the results are returned in an instance of object[]. @@ -664,4 +697,22 @@ public partial interface IQuery /// IFutureValue FutureValue(); } + + // 6.0 TODO: Move into IQuery + public static class QueryExtensions + { + /// + /// Returns an which can be enumerated asynchronously. + /// + /// + /// This is a good strategy to use if you expect a high number of the objects + /// returned to be already loaded in the or in the 2nd level cache. + /// + /// The element type. + /// The query. + public static IAsyncEnumerable AsyncEnumerable(this IQuery query) + { + return ReflectHelper.CastOrThrow(query, "async enumerable").AsyncEnumerable(); + } + } } diff --git a/src/NHibernate/Impl/AbstractQueryImpl.cs b/src/NHibernate/Impl/AbstractQueryImpl.cs index ba1e1402854..e1cf7ef8559 100644 --- a/src/NHibernate/Impl/AbstractQueryImpl.cs +++ b/src/NHibernate/Impl/AbstractQueryImpl.cs @@ -933,6 +933,19 @@ public IQuery SetIgnoreUknownNamedParameters(bool ignoredUnknownNamedParameters) public abstract int ExecuteUpdate(); public abstract IEnumerable Enumerable(); public abstract IEnumerable Enumerable(); + // Since v5.3 + [Obsolete("This method has no more usages and will be removed in a future version")] + public abstract Task EnumerableAsync(CancellationToken cancellationToken = default(CancellationToken)); + // Since v5.3 + [Obsolete("This method has no more usages and will be removed in a future version")] + public abstract Task> EnumerableAsync(CancellationToken cancellationToken = default(CancellationToken)); + + // 6.0 TODO: make abstract + public virtual IAsyncEnumerable AsyncEnumerable() + { + throw new NotImplementedException(); + } + public abstract IList List(); public abstract void List(IList results); public abstract IList List(); diff --git a/src/NHibernate/Impl/AbstractQueryImpl2.cs b/src/NHibernate/Impl/AbstractQueryImpl2.cs index b3c5c28be07..451cf89fa12 100644 --- a/src/NHibernate/Impl/AbstractQueryImpl2.cs +++ b/src/NHibernate/Impl/AbstractQueryImpl2.cs @@ -71,6 +71,21 @@ public override IEnumerable Enumerable() } } + public override IAsyncEnumerable AsyncEnumerable() + { + VerifyParameters(); + var namedParams = NamedParams; + Before(); + try + { + return Session.AsyncEnumerable(ExpandParameters(namedParams), GetQueryParameters(namedParams)); + } + finally + { + After(); + } + } + public override IList List() { VerifyParameters(); diff --git a/src/NHibernate/Impl/AbstractSessionImpl.cs b/src/NHibernate/Impl/AbstractSessionImpl.cs index 173a8cba7bd..34936e14096 100644 --- a/src/NHibernate/Impl/AbstractSessionImpl.cs +++ b/src/NHibernate/Impl/AbstractSessionImpl.cs @@ -4,6 +4,8 @@ using System.Data; using System.Data.Common; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using NHibernate.AdoNet; using NHibernate.Cache; using NHibernate.Collection; @@ -186,6 +188,19 @@ public IList ListFilter(object collection, IQueryExpression queryExpression, Que public abstract IList ListFilter(object collection, string filter, QueryParameters parameters); public abstract IEnumerable EnumerableFilter(object collection, string filter, QueryParameters parameters); public abstract IEnumerable EnumerableFilter(object collection, string filter, QueryParameters parameters); + // Since v5.3 + [Obsolete("This method has no more usages and will be removed in a future version")] + public abstract Task EnumerableFilterAsync(object collection, string filter, QueryParameters parameters, CancellationToken cancellationToken); + // Since v5.3 + [Obsolete("This method has no more usages and will be removed in a future version")] + public abstract Task> EnumerableFilterAsync(object collection, string filter, QueryParameters parameters, CancellationToken cancellationToken); + + //6.0 TODO: Make abstract + public virtual IAsyncEnumerable AsyncEnumerableFilter(object collection, string filter, QueryParameters parameters) + { + throw new NotImplementedException(); + } + public abstract IEntityPersister GetEntityPersister(string entityName, object obj); public abstract void AfterTransactionBegin(ITransaction tx); public abstract void BeforeTransactionCompletion(ITransaction tx); @@ -638,6 +653,20 @@ internal IOuterJoinLoadable GetOuterJoinLoadable(string entityName) public abstract IEnumerable Enumerable(IQueryExpression queryExpression, QueryParameters queryParameters); + // Since v5.3 + [Obsolete("This method has no more usages and will be removed in a future version")] + public abstract Task EnumerableAsync(IQueryExpression queryExpression, QueryParameters queryParameters, CancellationToken cancellationToken); + + // Since v5.3 + [Obsolete("This method has no more usages and will be removed in a future version")] + public abstract Task> EnumerableAsync(IQueryExpression queryExpression, QueryParameters queryParameters, CancellationToken cancellationToken); + + // 6.0 TODO: make abstract + public virtual IAsyncEnumerable AsyncEnumerable(IQueryExpression queryExpression, QueryParameters queryParameters) + { + throw new NotImplementedException(); + } + public abstract int ExecuteUpdate(IQueryExpression queryExpression, QueryParameters queryParameters); /// diff --git a/src/NHibernate/Impl/AsyncEnumerableImpl.cs b/src/NHibernate/Impl/AsyncEnumerableImpl.cs new file mode 100644 index 00000000000..2acc588e033 --- /dev/null +++ b/src/NHibernate/Impl/AsyncEnumerableImpl.cs @@ -0,0 +1,326 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data.Common; +using System.Threading; +using System.Threading.Tasks; +using NHibernate.Engine; +using NHibernate.Exceptions; +using NHibernate.SqlCommand; +using NHibernate.Transform; +using NHibernate.Type; + +namespace NHibernate.Impl +{ + internal struct InitializeEnumerableResult + { + public InitializeEnumerableResult(DbCommand command, DbDataReader dataReader) + { + Command = command; + DataReader = dataReader; + } + + public DbCommand Command { get; } + + public DbDataReader DataReader { get; } + } + + internal delegate Task InitializeEnumerableAsync(CancellationToken cancellationToken); + + internal delegate InitializeEnumerableResult InitializeEnumerable(); + + internal partial class AsyncEnumerableImpl : IAsyncEnumerable, IEnumerable + { + private readonly InitializeEnumerableAsync _initializeAsync; + private readonly InitializeEnumerable _initialize; + private readonly bool _readOnly; + private readonly IType[] _types; + private readonly string[][] _columnNames; + private readonly RowSelection _selection; + private readonly IResultTransformer _resultTransformer; + private readonly string[] _returnAliases; + private readonly ISessionImplementor _session; + + public AsyncEnumerableImpl( + InitializeEnumerable initialize, + InitializeEnumerableAsync initializeAsync, + bool readOnly, + IType[] types, + string[][] columnNames, + RowSelection selection, + IResultTransformer resultTransformer, + string[] returnAliases, + ISessionImplementor session) + { + _initialize = initialize; + _initializeAsync = initializeAsync; + _readOnly = readOnly; + _types = types; + _columnNames = columnNames; + _selection = selection; + _resultTransformer = resultTransformer; + _returnAliases = returnAliases; + _session = session; + } + + public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + return new AsyncEnumeratorImpl( + _initialize, + _initializeAsync, + _readOnly, + _types, + _columnNames, + _selection, + _resultTransformer, + _returnAliases, + _session, + cancellationToken); + } + + public IEnumerator GetEnumerator() + { + return new AsyncEnumeratorImpl( + _initialize, + _initializeAsync, + _readOnly, + _types, + _columnNames, + _selection, + _resultTransformer, + _returnAliases, + _session, + default); + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + internal sealed partial class AsyncEnumeratorImpl : IAsyncEnumerator, IEnumerator + { + private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(AsyncEnumeratorImpl)); + + private readonly InitializeEnumerableAsync _initializeAsync; + private readonly InitializeEnumerable _initialize; + private readonly bool _readOnly; + private readonly IType[] _types; + private readonly string[][] _columnNames; + private readonly RowSelection _selection; + private readonly IResultTransformer _resultTransformer; + private readonly string[] _returnAliases; + private readonly ISessionImplementor _session; + private readonly CancellationToken _cancellationToken; + private readonly bool _single; + + private DbDataReader _reader; + private DbCommand _command; + private bool _hasNext; + private bool _startedReading; // True if at least one MoveNext call was made. + // when we start enumerating through the DataReader we are positioned + // before the first record we need + private int _currentRow = -1; + private bool _isAlreadyDisposed; + + public AsyncEnumeratorImpl( + InitializeEnumerable initialize, + InitializeEnumerableAsync initializeAsync, + bool readOnly, + IType[] types, + string[][] columnNames, + RowSelection selection, + IResultTransformer resultTransformer, + string[] returnAliases, + ISessionImplementor session, + CancellationToken cancellationToken) + { + _initialize = initialize; + _initializeAsync = initializeAsync; + _readOnly = readOnly; + _types = types; + _single = types.Length == 1; + _columnNames = columnNames; + _selection = selection; + _resultTransformer = resultTransformer; + _returnAliases = returnAliases; + _session = session; + _cancellationToken = cancellationToken; + } + + public T Current { get; private set; } + + object IEnumerator.Current => Current; + + public async ValueTask MoveNextAsync() + { + if (_reader == null) + { + await InitializeAsync().ConfigureAwait(false); + } + + await ReadAsync(_cancellationToken).ConfigureAwait(false); + + return _hasNext; + } + + public bool MoveNext() + { + if (_reader == null) + { + Initialize(); + } + + Read(); + + return _hasNext; + } + + private void Read() + { + try + { + _hasNext = _reader.Read(); + _startedReading = true; + _currentRow++; + } + catch (DbException e) + { + throw ADOExceptionHelper.Convert(_session.Factory.SQLExceptionConverter, e, "Error executing Enumerable() query", + new SqlString(_command.CommandText)); + } + + PostRead(); + } + + private void PostRead() + { + if (_selection != null && _selection.MaxRows != RowSelection.NoValue) + { + _hasNext = _hasNext && (_currentRow < _selection.MaxRows); + } + + bool sessionDefaultReadOnlyOrig = _session.PersistenceContext.DefaultReadOnly; + _session.PersistenceContext.DefaultReadOnly = _readOnly; + try + { + MaterializeAndSetCurrent(); + } + finally + { + _session.PersistenceContext.DefaultReadOnly = sessionDefaultReadOnlyOrig; + } + } + + public void Reset() + { + Dispose(); + _isAlreadyDisposed = false; + } + + /// + /// Takes care of freeing the managed and unmanaged resources that this class is responsible for. + /// + /// + /// The command is closed and the reader is disposed. This allows other ADO.NET + /// related actions to occur without needing to move all the way through the + /// AsyncEnumeratorImpl. + /// + public void Dispose() + { + Log.Debug($"running {nameof(AsyncEnumeratorImpl)}.{nameof(AsyncEnumeratorImpl.Dispose)}()"); + + if (_isAlreadyDisposed) + { + // don't dispose of multiple times. + return; + } + + // if there is still a possibility of moving next then we need to clean up + // the resources - otherwise the cleanup has already been done by the + // PostMoveNext method. + if (_hasNext || !_startedReading) + { + Current = default; + _session.Batcher.CloseCommand(_command, _reader); + } + + // Reset all fields + _reader = null; + _command = null; + _hasNext = false; + _startedReading = false; + _currentRow = -1; + _isAlreadyDisposed = true; + } + + public ValueTask DisposeAsync() + { + Dispose(); + return default; + } + + private void MaterializeAndSetCurrent() + { + if (!_hasNext) + { + // there are no more records in the DataReader so clean up + Log.Debug("exhausted results"); + Current = default; + _session.Batcher.CloseCommand(_command, _reader); + return; + } + + Log.Debug("retrieving next results"); + if (_single && _resultTransformer == null) + { + SetCurrent(_types[0].NullSafeGet(_reader, _columnNames[0], _session, null)); + return; + } + + var currentResults = new object[_types.Length]; + // move through each of the ITypes contained in the DbDataReader and convert them + // to their objects. + for (int i = 0; i < _types.Length; i++) + { + // The IType knows how to extract its value out of the DbDataReader. If the IType + // is a value type then the value will simply be pulled out of the DbDataReader. If + // the IType is an Entity type then the IType will extract the id from the DbDataReader + // and use the ISession to load an instance of the object. + currentResults[i] = _types[i].NullSafeGet(_reader, _columnNames[i], _session, null); + } + + SetCurrent(_resultTransformer != null + ? _resultTransformer.TransformTuple(currentResults, _returnAliases) + : currentResults); + } + + private void SetCurrent(object value) + { + switch (value) + { + case T element: + Current = element; + break; + case null: + Current = default; + break; + default: + throw new InvalidOperationException( + $"An element of type {value.GetType()} was retrieved for an enumerable containing elements of type {typeof(T)}"); + } + } + + private async Task InitializeAsync() + { + var result = await _initializeAsync(_cancellationToken).ConfigureAwait(false); + _command = result.Command; + _reader = result.DataReader; + } + + private void Initialize() + { + var result = _initialize(); + _command = result.Command; + _reader = result.DataReader; + } + } + } +} diff --git a/src/NHibernate/Impl/CollectionFilterImpl.cs b/src/NHibernate/Impl/CollectionFilterImpl.cs index cb5f4410dbe..476037c8c1d 100644 --- a/src/NHibernate/Impl/CollectionFilterImpl.cs +++ b/src/NHibernate/Impl/CollectionFilterImpl.cs @@ -36,6 +36,13 @@ public override IEnumerable Enumerable() return Session.EnumerableFilter(collection, ExpandParameterLists(namedParams), GetQueryParameters(namedParams)); } + public override IAsyncEnumerable AsyncEnumerable() + { + VerifyParameters(); + IDictionary namedParams = NamedParams; + return Session.AsyncEnumerableFilter(collection, ExpandParameterLists(namedParams), GetQueryParameters(namedParams)); + } + protected internal override IEnumerable GetTranslators(ISessionImplementor session, QueryParameters queryParameters) { // NOTE: updates queryParameters.NamedParameters as (desired) side effect diff --git a/src/NHibernate/Impl/EnumerableImpl.cs b/src/NHibernate/Impl/EnumerableImpl.cs index d48adaeeb4c..f571e91ddf6 100644 --- a/src/NHibernate/Impl/EnumerableImpl.cs +++ b/src/NHibernate/Impl/EnumerableImpl.cs @@ -22,6 +22,8 @@ namespace NHibernate.Impl /// will cause it to be disposed, probably unexpectedly for the developer. (https://stackoverflow.com/a/11179175/1178314) /// "Fortunately", it does not currently support multiple iterations anyway. /// + // Since v5.3 + [Obsolete("This class has no more usage in NHibernate and will be removed in a future version.")] public class EnumerableImpl : IEnumerable, IEnumerator, IDisposable { private static readonly INHibernateLogger log = NHibernateLogger.For(typeof(EnumerableImpl)); diff --git a/src/NHibernate/Impl/SessionImpl.cs b/src/NHibernate/Impl/SessionImpl.cs index 95bf4473137..36ff4dd2bf6 100644 --- a/src/NHibernate/Impl/SessionImpl.cs +++ b/src/NHibernate/Impl/SessionImpl.cs @@ -585,12 +585,21 @@ public override IEnumerable Enumerable(IQueryExpression queryExpression, Q { queryParameters.ValidateParameters(); var plan = GetHQLQueryPlan(queryExpression, true); - AutoFlushIfRequired(plan.QuerySpaces); - using (SuspendAutoFlush()) //stops flush being called multiple times if this method is recursively called - { - return plan.PerformIterate(queryParameters, this); - } + // AutoFlushIfRequired will be called when iterating through the enumerable + return plan.PerformIterate(queryParameters, this); + } + } + + public override IAsyncEnumerable AsyncEnumerable(IQueryExpression queryExpression, QueryParameters queryParameters) + { + using (BeginProcess()) + { + queryParameters.ValidateParameters(); + var plan = GetHQLQueryPlan(queryExpression, true); + + // AutoFlushIfRequired will be called when iterating through the enumerable + return plan.PerformAsyncIterate(queryParameters, this); } } @@ -600,12 +609,9 @@ public override IEnumerable Enumerable(IQueryExpression queryExpression, QueryPa { queryParameters.ValidateParameters(); var plan = GetHQLQueryPlan(queryExpression, true); - AutoFlushIfRequired(plan.QuerySpaces); - using (SuspendAutoFlush()) //stops flush being called multiple times if this method is recursively called - { - return plan.PerformIterate(queryParameters, this); - } + // AutoFlushIfRequired will be called when iterating through the enumerable + return plan.PerformIterate(queryParameters, this); } } @@ -1622,6 +1628,15 @@ public override IEnumerable EnumerableFilter(object collection, string fil } } + public override IAsyncEnumerable AsyncEnumerableFilter(object collection, string filter, QueryParameters queryParameters) + { + using (BeginProcess()) + { + var plan = GetFilterQueryPlan(collection, filter, queryParameters, true); + return plan.PerformAsyncIterate(queryParameters, this); + } + } + public ICriteria CreateCriteria() where T : class { return CreateCriteria(typeof(T)); diff --git a/src/NHibernate/Linq/DefaultQueryProvider.cs b/src/NHibernate/Linq/DefaultQueryProvider.cs index 3442c3c7d7c..2fdf0f9b347 100644 --- a/src/NHibernate/Linq/DefaultQueryProvider.cs +++ b/src/NHibernate/Linq/DefaultQueryProvider.cs @@ -28,6 +28,22 @@ public partial interface INhQueryProvider : IQueryProvider Task ExecuteAsync(Expression expression, CancellationToken cancellationToken); } + // 6.0 TODO: Move into INhQueryProvider + internal static class NhQueryProviderExtensions + { + /// + /// Return the query results as an . + /// + /// The element type. + /// The query provider. + /// The query expression. + public static IAsyncEnumerable GetAsyncEnumerable(this INhQueryProvider nhQueryProvider, Expression expression) + { + return ReflectHelper.CastOrThrow(nhQueryProvider, "async enumerable") + .GetAsyncEnumerable(expression); + } + } + // 6.0 TODO: merge into INhQueryProvider. public interface ISupportFutureBatchNhQueryProvider { @@ -299,6 +315,17 @@ public int ExecuteDml(QueryMode queryMode, Expression expression) return query.ExecuteUpdate(); } + public IAsyncEnumerable GetAsyncEnumerable(Expression expression) + { + var nhLinqExpression = PrepareQuery(expression, out var query); + if (nhLinqExpression.ExpressionToHqlTranslationResults?.PostExecuteTransformer != null) + { + throw new NotSupportedException("AsyncEnumerable is not supported when PostExecuteTransformer is set."); + } + + return query.AsyncEnumerable(); + } + public IQuery GetPreparedQuery(Expression expression, out NhLinqExpression nhExpression) { nhExpression = PrepareQuery(expression, out var query); diff --git a/src/NHibernate/Linq/LinqExtensionMethods.cs b/src/NHibernate/Linq/LinqExtensionMethods.cs index 4d70b7e40e1..f1b5135b01a 100644 --- a/src/NHibernate/Linq/LinqExtensionMethods.cs +++ b/src/NHibernate/Linq/LinqExtensionMethods.cs @@ -2396,6 +2396,16 @@ async Task> InternalToListAsync() #endregion + /// + /// Returns an which can be enumerated asynchronously. + /// + /// The type of the elements of . + /// An to enumerate. + public static IAsyncEnumerable AsAsyncEnumerable(this IQueryable source) + { + return source.GetNhProvider().GetAsyncEnumerable(source.Expression); + } + /// /// Wraps the query in a deferred which enumeration will trigger a batch of all pending future queries. /// diff --git a/src/NHibernate/Loader/Hql/QueryLoader.cs b/src/NHibernate/Loader/Hql/QueryLoader.cs index ad95fd5362e..c65caa6e4ec 100644 --- a/src/NHibernate/Loader/Hql/QueryLoader.cs +++ b/src/NHibernate/Loader/Hql/QueryLoader.cs @@ -4,6 +4,8 @@ using System.Data.Common; using System.Diagnostics; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using NHibernate.Engine; using NHibernate.Event; using NHibernate.Hql.Ast.ANTLR; @@ -466,33 +468,44 @@ protected override bool[] IncludeInResultRow [Obsolete("Please use ResultTypes instead")] public IType[] ReturnTypes => ResultTypes; - internal IEnumerable GetEnumerable(QueryParameters queryParameters, IEventSource session) + internal AsyncEnumerableImpl GetAsyncEnumerable(QueryParameters queryParameters, ISessionImplementor session) { CheckQuery(queryParameters); - Stopwatch stopWatch = null; - if (session.Factory.Statistics.IsStatisticsEnabled) - { - stopWatch = Stopwatch.StartNew(); - } - var cmd = PrepareQueryCommand(queryParameters, false, session); + return new AsyncEnumerableImpl( + () => InitializeEnumerable(queryParameters, session), + cancellationToken => InitializeEnumerableAsync(queryParameters, session, cancellationToken), + queryParameters.IsReadOnly(session), + _queryTranslator.ReturnTypes, + _queryTranslator.GetColumnNames(), + queryParameters.RowSelection, + _selectNewTransformer ?? queryParameters.ResultTransformer, + _queryReturnAliases, + session); + } - // This DbDataReader is disposed of in EnumerableImpl.Dispose - var rs = GetResultSet(cmd, queryParameters, session, null); + internal InitializeEnumerableResult InitializeEnumerable(QueryParameters queryParameters, ISessionImplementor session) + { + session.AutoFlushIfRequired(_queryTranslator.QuerySpaces); + using (session.SuspendAutoFlush()) + { + Stopwatch stopWatch = null; + if (session.Factory.Statistics.IsStatisticsEnabled) + { + stopWatch = Stopwatch.StartNew(); + } - var resultTransformer = _selectNewTransformer ?? queryParameters.ResultTransformer; - IEnumerable result = - new EnumerableImpl(rs, cmd, session, queryParameters.IsReadOnly(session), _queryTranslator.ReturnTypes, _queryTranslator.GetColumnNames(), queryParameters.RowSelection, resultTransformer, _queryReturnAliases); + var command = PrepareQueryCommand(queryParameters, false, session); + var dataReader = GetResultSet(command, queryParameters, session, null); + if (stopWatch != null) + { + stopWatch.Stop(); + session.Factory.StatisticsImplementor.QueryExecuted("HQL: " + _queryTranslator.QueryString, 0, stopWatch.Elapsed); + session.Factory.StatisticsImplementor.QueryExecuted(QueryIdentifier, 0, stopWatch.Elapsed); + } - if (stopWatch != null) - { - stopWatch.Stop(); - session.Factory.StatisticsImplementor.QueryExecuted("HQL: " + _queryTranslator.QueryString, 0, stopWatch.Elapsed); - // NH: Different behavior (H3.2 use QueryLoader in AST parser) we need statistic for orginal query too. - // probably we have a bug some where else for statistic RowCount - session.Factory.StatisticsImplementor.QueryExecuted(QueryIdentifier, 0, stopWatch.Elapsed); + return new InitializeEnumerableResult(command, dataReader); } - return result; } protected override void ResetEffectiveExpectedType(IEnumerable parameterSpecs, QueryParameters queryParameters) diff --git a/src/NHibernate/NHibernate.csproj b/src/NHibernate/NHibernate.csproj index 2ef674c8484..643101b44a8 100644 --- a/src/NHibernate/NHibernate.csproj +++ b/src/NHibernate/NHibernate.csproj @@ -38,6 +38,7 @@ + diff --git a/src/NHibernate/NHibernateUtil.cs b/src/NHibernate/NHibernateUtil.cs index e781db52bef..c26fc9f731d 100644 --- a/src/NHibernate/NHibernateUtil.cs +++ b/src/NHibernate/NHibernateUtil.cs @@ -532,11 +532,12 @@ public static Clob CreateClob(TextReader reader, int length) { /// public static void Close(IEnumerator enumerator) { - EnumerableImpl hibernateEnumerator = enumerator as EnumerableImpl; + var hibernateEnumerator = enumerator as IDisposable; if (hibernateEnumerator == null) { throw new ArgumentException("Not a NHibernate enumerator", nameof(enumerator)); } + hibernateEnumerator.Dispose(); } @@ -544,6 +545,8 @@ public static void Close(IEnumerator enumerator) /// Close an returned by NHibernate immediately, /// instead of waiting until the session is closed or disconnected. /// + // Since v5.3 + [Obsolete("This method has no more usage in NHibernate and will be removed in a future version.")] public static void Close(IEnumerable enumerable) { EnumerableImpl hibernateEnumerable = enumerable as EnumerableImpl; diff --git a/src/NHibernate/Util/JoinedAsyncEnumerable.cs b/src/NHibernate/Util/JoinedAsyncEnumerable.cs new file mode 100644 index 00000000000..dd11f8b9119 --- /dev/null +++ b/src/NHibernate/Util/JoinedAsyncEnumerable.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace NHibernate.Util +{ + // 6.0 TODO: Remove it in favor of await foreach, also AsyncJoinedEnumerableFixture needs to be removed + internal sealed class JoinedAsyncEnumerable : IAsyncEnumerable + { + private readonly IEnumerable> _asyncEnumerables; + + public JoinedAsyncEnumerable(IEnumerable> asyncEnumerables) + { + _asyncEnumerables = asyncEnumerables; + } + + public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + return new JoinedAsyncEnumerator(_asyncEnumerables, cancellationToken); + } + + private class JoinedAsyncEnumerator : IAsyncEnumerator + { + private readonly IEnumerable> _asyncEnumerables; + private readonly CancellationToken _cancellationToken; + private IEnumerator> _currentEnumerator; + private IAsyncEnumerator _currentAsyncEnumerator; + private bool _isAlreadyDisposed; + + public JoinedAsyncEnumerator(IEnumerable> asyncEnumerables, CancellationToken cancellationToken) + { + _asyncEnumerables = asyncEnumerables; + _cancellationToken = cancellationToken; + } + + public T Current { get; private set; } + + public async ValueTask MoveNextAsync() + { + _cancellationToken.ThrowIfCancellationRequested(); + if (_isAlreadyDisposed) + { + throw new InvalidOperationException("The enumerator was disposed."); + } + + if (_currentEnumerator == null) + { + _currentEnumerator = _asyncEnumerables.GetEnumerator(); + if (!MoveNext()) + { + return false; + } + } + + if (_currentAsyncEnumerator == null) + { + return false; // MoveNextAsync called after we reached the end of the enumeration + } + + while (true) + { + if (await _currentAsyncEnumerator.MoveNextAsync().ConfigureAwait(false)) + { + Current = _currentAsyncEnumerator.Current; + return true; + } + + // there are no items left to iterate over in the current + // async enumerator so go ahead and dispose of it. + await _currentAsyncEnumerator.DisposeAsync().ConfigureAwait(false); + if (!MoveNext()) + { + return false; + } + } + } + + private bool MoveNext() + { + if (!_currentEnumerator.MoveNext()) + { + _currentAsyncEnumerator = null; + return false; + } + + _currentAsyncEnumerator = _currentEnumerator.Current.GetAsyncEnumerator(_cancellationToken); + return true; + } + + public async ValueTask DisposeAsync() + { + if (_isAlreadyDisposed) + { + // don't dispose of multiple times. + return; + } + + // Dispose only the current async enumerator when DisposeAsync is called before the enumeration ended + if (_currentAsyncEnumerator != null) + { + await _currentAsyncEnumerator.DisposeAsync().ConfigureAwait(false); + _currentAsyncEnumerator = null; + } + + Current = default; + _currentEnumerator.Dispose(); + _currentEnumerator = null; + _isAlreadyDisposed = true; + } + } + } +} diff --git a/src/NHibernate/Util/JoinedEnumerable.cs b/src/NHibernate/Util/JoinedEnumerable.cs index df7c268efed..e8ebb8968eb 100644 --- a/src/NHibernate/Util/JoinedEnumerable.cs +++ b/src/NHibernate/Util/JoinedEnumerable.cs @@ -8,6 +8,8 @@ namespace NHibernate.Util /// /// Concatenates multiple objects implementing into one. /// + //Since 5.3 + [Obsolete("This class has no more usages in NHibernate and will be removed in a future version.")] public class JoinedEnumerable : IEnumerable { private static readonly INHibernateLogger log = NHibernateLogger.For(typeof(JoinedEnumerable)); diff --git a/src/NHibernate/Util/SafetyEnumerable.cs b/src/NHibernate/Util/SafetyEnumerable.cs index c6656d58e7a..7e2e3c12bff 100644 --- a/src/NHibernate/Util/SafetyEnumerable.cs +++ b/src/NHibernate/Util/SafetyEnumerable.cs @@ -7,6 +7,7 @@ namespace NHibernate.Util /// Used to ensure a collection filtering a given IEnumerable by a certain type. /// /// The type used like filter. + // 6.0 TODO: Remove public class SafetyEnumerable : IEnumerable { /*