diff --git a/releasenotes.txt b/releasenotes.txt index 1a7f3e97521..a612526094e 100644 --- a/releasenotes.txt +++ b/releasenotes.txt @@ -1,4 +1,40 @@ -Build 5.4.3 +Build 5.4.5 +============================= + +Release notes - NHibernate - Version 5.4.5 + +2 issues were resolved in this release. + +** Task + + * #3408 Release 5.4.4 + * #3407 Release Merge 5.3.19 in 5.4.x + + +Build 5.4.4 +============================= + +Release notes - NHibernate - Version 5.4.4 + +6 issues were resolved in this release. + +** Bug + + * #3359 2nd level cache GetMany ineffective for collections + * #3354 Invalid program generated by FieldInterceptorProxyBuilder for indexer property getter + * #3352 Fetch throws "could not resolve property" error for a property that is not mapped + +** Improvement + + * #3368 Allow internal entity classes/interfaces in .NET Standard 2.0 for field interceptor + +** Task + + * #3386 Release 5.4.4 + * #3367 Update readme with actual dev build information for 5.4 + + +Build 5.4.3 ============================= Release notes - NHibernate - Version 5.4.3 @@ -259,6 +295,22 @@ Release notes - NHibernate - Version 5.4.0 * #2242 Test case for NH-3972 - SQL error when selecting a column of a subclass when sibling classes have a column of the same name +Build 5.3.19 +============================= + +Release notes - NHibernate - Version 5.3.19 + +2 issues were resolved in this release. + +** Bug + + * #3397 GenerateSchemaCreationScript creates many identical dialect instances + +** Task + + * #3405 Release 5.3.19 + + Build 5.3.18 ============================= diff --git a/src/NHibernate.Test/Async/CacheTest/BatchableCacheFixture.cs b/src/NHibernate.Test/Async/CacheTest/BatchableCacheFixture.cs index c3c2195688a..94ebca5e031 100644 --- a/src/NHibernate.Test/Async/CacheTest/BatchableCacheFixture.cs +++ b/src/NHibernate.Test/Async/CacheTest/BatchableCacheFixture.cs @@ -1565,8 +1565,79 @@ public async Task QueryFetchEntityBatchCacheTestAsync(bool clearEntityCacheAfter Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(future ? 2 : 1), "Unexpected cache hit count"); } + [Test] + public async Task CollectionLazyInitializationFromCacheIsBatchedAsync() + { + using (var s = OpenSession()) + { + var readOnly = await (s.GetAsync(await (s.Query().Select(x => x.Id).FirstAsync()))); + Assert.That(readOnly.Items.Count, Is.EqualTo(6)); + } + + var itemPersister = Sfi.GetEntityPersister(typeof(ReadOnlyItem).FullName); + var itemCache = (BatchableCache) itemPersister.Cache.Cache; + itemCache.ClearStatistics(); + + using (var s = OpenSession()) + { + var readOnly = await (s.GetAsync(await (s.Query().Select(x => x.Id).FirstAsync()))); + Assert.That(readOnly.Items.Count, Is.EqualTo(6)); + } + + // 6 items with batch-size = 4 so 2 GetMany calls are expected 1st call: 4 items + 2nd call: 2 items + Assert.That(itemCache.GetMultipleCalls.Count, Is.EqualTo(2)); + } + + [Test] + public async Task CollectionLazyInitializationFromCacheIsBatched_FillCacheByQueryCacheAsync() + { + var itemPersister = Sfi.GetEntityPersister(typeof(ReadOnlyItem).FullName); + var itemCache = (BatchableCache) itemPersister.Cache.Cache; + itemCache.ClearStatistics(); + int id; + using (var s = OpenSession()) + { + id = await (s.Query().Select(x => x.Id).FirstAsync()); + var readOnly = (await (s.Query().Fetch(x => x.Items) + .Where(x => x.Id == id) + .WithOptions(x => x.SetCacheable(true)) + .ToListAsync())) + .First(); + Assert.That(itemCache.PutMultipleCalls.Count, Is.EqualTo(1)); + Assert.That(itemCache.GetMultipleCalls.Count, Is.EqualTo(0)); + Assert.That(NHibernateUtil.IsInitialized(readOnly.Items)); + Assert.That(readOnly.Items.Count, Is.EqualTo(6)); + } + + itemCache.ClearStatistics(); + using (var s = OpenSession()) + { + var readOnly = (await (s.Query().Fetch(x => x.Items) + .Where(x => x.Id == id) + .WithOptions(x => x.SetCacheable(true)) + .ToListAsync())) + .First(); + Assert.That(itemCache.PutMultipleCalls.Count, Is.EqualTo(0)); + Assert.That(itemCache.GetMultipleCalls.Count, Is.EqualTo(1)); + Assert.That(NHibernateUtil.IsInitialized(readOnly.Items)); + Assert.That(readOnly.Items.Count, Is.EqualTo(6)); + } + + itemCache.ClearStatistics(); + + + using (var s = OpenSession()) + { + var readOnly = await (s.GetAsync(id)); + Assert.That(readOnly.Items.Count, Is.EqualTo(6)); + } + + // 6 items with batch-size = 4 so 2 GetMany calls are expected 1st call: 4 items + 2nd call: 2 items + Assert.That(itemCache.GetMultipleCalls.Count, Is.EqualTo(2)); + } + private async Task AssertMultipleCacheCallsAsync(IEnumerable loadIds, IReadOnlyList getIds, int idIndex, - int[][] fetchedIdIndexes, int[] putIdIndexes, Func cacheBeforeLoadFn = null, CancellationToken cancellationToken = default(CancellationToken)) + int[][] fetchedIdIndexes, int[] putIdIndexes, Func cacheBeforeLoadFn = null, CancellationToken cancellationToken = default(CancellationToken)) where TEntity : CacheEntity { var persister = Sfi.GetEntityPersister(typeof(TEntity).FullName); diff --git a/src/NHibernate.Test/Async/FetchLazyProperties/FetchLazyPropertiesFixture.cs b/src/NHibernate.Test/Async/FetchLazyProperties/FetchLazyPropertiesFixture.cs index 6a812cd5888..18df844bd71 100644 --- a/src/NHibernate.Test/Async/FetchLazyProperties/FetchLazyPropertiesFixture.cs +++ b/src/NHibernate.Test/Async/FetchLazyProperties/FetchLazyPropertiesFixture.cs @@ -186,6 +186,18 @@ public async Task TestLinqFetchPropertyAsync() AssertFetchProperty(person); } + [Test] + public async Task TestLinqFetchPropertyAfterSelectAsync() + { + using var s = OpenSession(); + var owner = await (s.Query() + .Select(a => a.Owner) + .Fetch(o => o.Image) + .FirstOrDefaultAsync(o => o.Id == 1)); + + AssertFetchProperty(owner); + } + private static void AssertFetchProperty(Person person) { Assert.That(person, Is.Not.Null); diff --git a/src/NHibernate.Test/Async/LazyProperty/LazyPropertyFixture.cs b/src/NHibernate.Test/Async/LazyProperty/LazyPropertyFixture.cs index f99b79e5420..78ff4a50a16 100644 --- a/src/NHibernate.Test/Async/LazyProperty/LazyPropertyFixture.cs +++ b/src/NHibernate.Test/Async/LazyProperty/LazyPropertyFixture.cs @@ -230,6 +230,9 @@ public async Task CanGetValueForNonLazyPropertyAsync() Assert.That(book.Name, Is.EqualTo("some name")); Assert.That(book.FieldInterceptor, Is.EqualTo("Why not that name?")); Assert.That(NHibernateUtil.IsPropertyInitialized(book, "ALotOfText"), Is.False); + //GH-3354 Exception accessing indexer property + Assert.That(book[0], Is.EqualTo(0)); + Assert.DoesNotThrow(() => book[0] = 0); } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH1994/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH1994/ManyToManyFilteredFixture.cs similarity index 83% rename from src/NHibernate.Test/Async/NHSpecificTest/GH1994/Fixture.cs rename to src/NHibernate.Test/Async/NHSpecificTest/GH1994/ManyToManyFilteredFixture.cs index ff47bd2ad14..0c930f421f3 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH1994/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH1994/ManyToManyFilteredFixture.cs @@ -10,7 +10,6 @@ using System.Linq; using NHibernate.Criterion; -using NHibernate.Dialect; using NHibernate.Linq; using NHibernate.SqlCommand; using NHibernate.Transform; @@ -20,7 +19,7 @@ namespace NHibernate.Test.NHSpecificTest.GH1994 { using System.Threading.Tasks; [TestFixture] - public class FixtureAsync : BugTestCase + public class ManyToManyFilteredFixtureAsync : BugTestCase { protected override void OnSetUp() { @@ -41,14 +40,7 @@ protected override void OnTearDown() using (var session = OpenSession()) using (var transaction = session.BeginTransaction()) { - // The HQL delete does all the job inside the database without loading the entities, but it does - // not handle delete order for avoiding violating constraints if any. Use - // session.Delete("from System.Object"); - // instead if in need of having NHibernate ordering the deletes, but this will cause - // loading the entities in the session. - session.Delete("from System.Object"); - transaction.Commit(); } } @@ -70,9 +62,6 @@ public async Task TestUnfilteredLinqQueryAsync() [Test] public async Task TestFilteredByWhereCollectionLinqQueryAsync() { - if(Dialect is PostgreSQLDialect) - Assert.Ignore("Dialect doesn't support 0/1 to bool implicit cast"); - using (var s = OpenSession()) { var query = await (s.Query() @@ -150,5 +139,31 @@ public async Task TestQueryOverRestrictionWithClauseAsync() Assert.That(query[0].Documents.Count, Is.EqualTo(1), "filtered asset documents"); } } + + [Test] + public async Task LazyLoadAsync() + { + using (var s = OpenSession()) + { + var asset = await (s.Query().FirstAsync()); + Assert.That(asset.Documents.Count, Is.EqualTo(2)); + Assert.That(asset.DocumentsBag.Count, Is.EqualTo(2)); + Assert.That(asset.DocumentsFiltered.Count, Is.EqualTo(1)); + } + } + + [Test] + public async Task LazyLoadFilteredAsync() + { + using (var s = OpenSession()) + { + s.EnableFilter("deletedFilter").SetParameter("deletedParam", false); + + var asset = await (s.Query().FirstAsync()); + Assert.That(asset.Documents.Count, Is.EqualTo(1)); + Assert.That(asset.DocumentsBag.Count, Is.EqualTo(1)); + Assert.That(asset.DocumentsFiltered.Count, Is.EqualTo(1)); + } + } } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3311SqlQueryParam/SqlQueryParamTypeFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3311SqlQueryParam/SqlQueryParamTypeFixture.cs new file mode 100644 index 00000000000..4ddde3d2a41 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3311SqlQueryParam/SqlQueryParamTypeFixture.cs @@ -0,0 +1,61 @@ +//------------------------------------------------------------------------------ +// +// 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.Data; +using NHibernate.Dialect; +using NHibernate.SqlTypes; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH3311SqlQueryParam +{ + using System.Threading.Tasks; + [TestFixture] + public class SqlQueryParamTypeFixtureAsync : BugTestCase + { + protected override void OnSetUp() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + var e1 = new Entity {Name = "Bob"}; + session.Save(e1); + + var e2 = new Entity {Name = "Sally"}; + session.Save(e2); + + transaction.Commit(); + } + + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return + //Dialects like SQL Server CE, Firebird don't distinguish AnsiString from String + (Dialect.GetTypeName(new SqlType(DbType.AnsiString)) != Dialect.GetTypeName(new SqlType(DbType.String)) + || Dialect is SQLiteDialect); + } + + protected override void OnTearDown() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + + [Test] + public async Task AppliesParameterTypeFromQueryParamAsync() + { + using var log = new SqlLogSpy(); + using var s = OpenSession(); + await (s.GetNamedQuery("entityIdByName").SetParameter("name", "Bob").UniqueResultAsync()); + Assert.That(log.GetWholeLog(), Does.Contain("Type: AnsiString")); + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3327/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3327/Fixture.cs index b34a71f1317..dd487c61382 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/GH3327/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3327/Fixture.cs @@ -59,5 +59,21 @@ WHERE NOT ( )"); Assert.That((await (q.ListAsync()))[0], Is.EqualTo(0)); } + + [Test] + public async Task NotNotExistsNegatedAsync() + { + using var log = new SqlLogSpy(); + using var session = OpenSession(); + var results = await (session.CreateQuery( + @"SELECT COUNT(ROOT.Id) + FROM Entity AS ROOT + WHERE NOT ( + NOT EXISTS (FROM ChildEntity AS CHILD WHERE CHILD.Parent = ROOT) + AND NOT ROOT.Name = 'Parent' + )").ListAsync()); + Assert.That(log.GetWholeLog(), Does.Not.Contains(" NOT ").IgnoreCase); + Assert.That(results.Count, Is.EqualTo(1)); + } } } diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3352/FetchFromNotMappedBaseClassFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3352/FetchFromNotMappedBaseClassFixture.cs new file mode 100644 index 00000000000..07b1a219f83 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3352/FetchFromNotMappedBaseClassFixture.cs @@ -0,0 +1,174 @@ +//------------------------------------------------------------------------------ +// +// 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.Linq; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Linq; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH3352 +{ + using System.Threading.Tasks; + [TestFixture] + public class FetchFromNotMappedBaseClassFixtureAsync : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name, m => m.Lazy(true)); + }); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.ManyToOne(x => x.Parent, m => m.ForeignKey("none")); + }); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Component(x => x.Component); + }); + mapper.Component(rc => + { + rc.Property(x => x.Field); + rc.ManyToOne(x => x.Entity, m => m.ForeignKey("none")); + rc.Lazy(true); + }); + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + var np = new EntityComponentMapped { Component = new Component { Field = "x" } }; + session.Save(np); + var e = new EntityParentMapped { Parent = np }; + session.Save(e); + var nameMapped = new EntityNameMapped { Name = "lazy" }; + session.Save(nameMapped); + np.Component.Entity = nameMapped; + + transaction.Commit(); + } + + protected override void OnTearDown() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + + [Test] + public async Task CanFetchLazyComponentFromNotMappedBaseClassAsync() + { + using var session = OpenSession(); + var list = await (session.Query().Fetch(x => x.Component).ToListAsync()); + + Assert.That(list, Has.Count.EqualTo(1)); + var result = list[0]; + Assert.That(NHibernateUtil.IsPropertyInitialized(result, nameof(result.Component))); + Assert.That(result.Component.Field, Is.EqualTo("x")); + } + + [Test] + public async Task CanFetchLazyComponentThenEntityFromNotMappedBaseClassAsync() + { + using var session = OpenSession(); + var list = await (session.Query() + .Fetch(x => x.Component) + .ThenFetch(x => x.Entity) + .ThenFetch(x => x.Name) + .ToListAsync()); + + Assert.That(list, Has.Count.EqualTo(1)); + var result = list[0]; + Assert.That(NHibernateUtil.IsPropertyInitialized(result, nameof(result.Component))); + Assert.That(result.Component.Field, Is.EqualTo("x")); + Assert.That(result.Component.Entity, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(result.Component.Entity), Is.True); + Assert.That(NHibernateUtil.IsPropertyInitialized(result.Component.Entity, nameof(result.Name)), Is.True); + Assert.That(result.Component.Entity.Name, Is.EqualTo("lazy")); + } + + [Test] + public async Task CanFetchLazyPropertyFromNotMappedBaseClassAsync() + { + using var session = OpenSession(); + var list = await (session.Query().Fetch(x => x.Name).ToListAsync()); + + Assert.That(list, Has.Count.EqualTo(1)); + var result = list[0]; + Assert.That(NHibernateUtil.IsPropertyInitialized(result, nameof(result.Name))); + Assert.That(result.Name, Is.EqualTo("lazy")); + } + + [Test] + public async Task CanThenFetchLazyComponentFromNotMappedBaseClassAsync() + { + using var session = OpenSession(); + var list = await (session.Query().Fetch(x => x.Parent).ThenFetch(x => x.Component).ToListAsync()); + + Assert.That(list, Has.Count.EqualTo(1)); + var result = list[0].Parent; + Assert.That(NHibernateUtil.IsInitialized(result), Is.True); + Assert.That(NHibernateUtil.IsPropertyInitialized(result, nameof(result.Component))); + Assert.That(result.Component.Field, Is.EqualTo("x")); + } + + [KnownBug("GH-3356")] + [Test(Description = "GH-3356" )] + public async Task FetchAfterSelectAsync() + { + using var log = new SqlLogSpy(); + + using var s = OpenSession(); + var list = await (s.Query() + .Select(x => x.Parent) + .Fetch(x => x.Component) + .ThenFetch(x => x.Entity) + .ThenFetch(x => x.Name) + .ToListAsync()); + Assert.That(list, Has.Count.EqualTo(1)); + var result = list[0]; + Assert.That(NHibernateUtil.IsPropertyInitialized(result, nameof(result.Component))); + Assert.That(result.Component.Field, Is.EqualTo("x")); + Assert.That(result.Component.Entity, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(result.Component.Entity), Is.True); + Assert.That(NHibernateUtil.IsPropertyInitialized(result.Component.Entity, nameof(result.Name)), Is.True); + Assert.That(result.Component.Entity.Name, Is.EqualTo("lazy")); + } + + [Test] + public async Task CanFetchEntityFromNotMappedBaseClassAsync() + { + using var session = OpenSession(); + var list = await (session.Query().Fetch(x => x.Parent).ToListAsync()); + + Assert.That(list, Has.Count.EqualTo(1)); + Assert.That(list[0].Parent, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(list[0].Parent)); + } + + [Test] + public void FetchNotMappedAssociationThrowsAsync() + { + using var session = OpenSession(); + var query = session.Query().Fetch(x => x.Parent); + + Assert.ThrowsAsync(() => query.ToListAsync()); + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3403OneToOne/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3403OneToOne/Fixture.cs new file mode 100644 index 00000000000..85c119e905c --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3403OneToOne/Fixture.cs @@ -0,0 +1,74 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH3403OneToOne +{ + using System.Threading.Tasks; + [TestFixture] + public class FixtureAsync : BugTestCase + { + private Guid _id; + + protected override void OnSetUp() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + var entity = new Entity1 + { + Child = new Entity2() + }; + + entity.Child.Parent = entity; + + session.Save(entity); + transaction.Commit(); + _id = entity.Id; + } + + protected override void OnTearDown() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + + [Test] + public async Task OrphanDeleteForDetachedOneToOneAsync() + { + Guid childId; + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var entity = await (session.GetAsync(_id)); + childId = entity.Child.Id; + await (session.EvictAsync(entity.Child)); + entity.Child = null; + + await (session.FlushAsync()); + await (transaction.CommitAsync()); + } + + using (var session = OpenSession()) + { + var entity = await (session.GetAsync(_id)); + Assert.That(entity, Is.Not.Null); + Assert.That(entity.Child, Is.Null, "Unexpected child on parent"); + + var child = await (session.GetAsync(childId)); + Assert.That(child , Is.Null, "Child is still in database"); + } + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH750/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH750/Fixture.cs deleted file mode 100644 index 07df8065629..00000000000 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH750/Fixture.cs +++ /dev/null @@ -1,124 +0,0 @@ -//------------------------------------------------------------------------------ -// -// 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 NHibernate.Cfg; -using NUnit.Framework; - -namespace NHibernate.Test.NHSpecificTest.NH750 -{ - using System.Threading.Tasks; - [TestFixture] - public class FixtureAsync : BugTestCase - { - protected override void OnTearDown() - { - using (ISession s = Sfi.OpenSession()) - { - s.Delete("from Device"); - s.Delete("from Drive"); - s.Flush(); - } - } - - protected override void Configure(Configuration configuration) - { - configuration.SetProperty(Cfg.Environment.UseSecondLevelCache, "false"); - base.Configure(configuration); - } - - [Test] - public async Task DeviceOfDriveAsync() - { - int[] dvSavedId = new int[2]; - Drive dr1 = new Drive("Drive 1"); - Drive dr2 = new Drive("Drive 2"); - Drive dr3 = new Drive("Drive 3"); - Device dv1 = new Device("Device 1"); - Device dv2 = new Device("Device 2"); - using (ISession s = Sfi.OpenSession()) - { - await (s.SaveAsync(dr1)); - await (s.SaveAsync(dr2)); - await (s.SaveAsync(dr3)); - dvSavedId[0] = (int) await (s.SaveAsync(dv1)); - dvSavedId[1] = (int) await (s.SaveAsync(dv2)); - await (s.FlushAsync()); - } - - dv1.Drives.Add(dr1); - dv1.Drives.Add(dr2); - dv2.Drives.Add(dr1); - dv2.Drives.Add(dr3); - using (ISession s = Sfi.OpenSession()) - { - dvSavedId[0] = (int) await (s.SaveAsync(dv1)); - dvSavedId[1] = (int) await (s.SaveAsync(dv2)); - await (s.FlushAsync()); - } - dv1 = null; - dv2 = null; - using (ISession s = Sfi.OpenSession()) - { - await (s.DeleteAsync(dr3)); - await (s.FlushAsync()); - dv1 = (Device) await (s.LoadAsync(typeof(Device), dvSavedId[0])); - dv2 = (Device) await (s.LoadAsync(typeof(Device), dvSavedId[1])); - } - Assert.AreEqual(2, dv1.Drives.Count); - // Verify one is missing - Assert.AreEqual(1, dv2.Drives.Count); - // Verify dv1 unchanged - Assert.IsTrue(dv1.Drives.Contains(dr1)); - Assert.IsTrue(dv1.Drives.Contains(dr2)); - - // Verify dv2 - Assert.IsTrue(dv2.Drives.Contains(dr1)); - Assert.IsFalse(dv2.Drives.Contains(dr3)); - - //Make sure that flush didn't touch not-found="ignore" records for not modified collection - using (var s = Sfi.OpenSession()) - using (var t = s.BeginTransaction()) - { - dv2 = await (s.GetAsync(dv2.Id)); - await (s.FlushAsync()); - await (t.CommitAsync()); - } - - await (VerifyResultAsync(expectedInCollection: 1, expectedInDb: 2, msg: "not modified collection")); - - //Many-to-many clears collection and recreates it so not-found ignore records are lost - using (var s = Sfi.OpenSession()) - using (var t = s.BeginTransaction()) - { - dv2 = await (s.GetAsync(dv2.Id)); - dv2.Drives.Add(dr2); - await (t.CommitAsync()); - } - - await (VerifyResultAsync(2, 2, msg: "modified collection")); - - async Task VerifyResultAsync(int expectedInCollection, int expectedInDb, string msg) - { - using (var s = Sfi.OpenSession()) - { - var realCound = Convert.ToInt32( - await (s.CreateSQLQuery("select count(*) from DriveOfDevice where DeviceId = :id ") - .SetParameter("id", dv2.Id) - .UniqueResultAsync())); - dv2 = await (s.GetAsync(dv2.Id)); - - Assert.That(dv2.Drives.Count, Is.EqualTo(expectedInCollection), msg); - Assert.That(realCound, Is.EqualTo(expectedInDb), msg); - } - } - } - } -} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH750/ManyToManyFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH750/ManyToManyFixture.cs new file mode 100644 index 00000000000..362bd7a6f71 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH750/ManyToManyFixture.cs @@ -0,0 +1,155 @@ +//------------------------------------------------------------------------------ +// +// 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 NHibernate.Criterion; +using NHibernate.Transform; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH750 +{ + using System.Threading.Tasks; + [TestFixture(0)] + [TestFixture(1)] + [TestFixture(2)] + public class ManyToManyFixtureAsync : BugTestCase + { + private int id2; + private readonly int _drivesCount; + private int _withTemplateId; + private int ValidDrivesCount => _drivesCount; + + public ManyToManyFixtureAsync(int drivesCount) + { + _drivesCount = drivesCount; + } + + protected override void OnSetUp() + { + Drive dr1 = new Drive("Drive 1"); + Drive dr2 = new Drive("Drive 2"); + Drive dr3 = new Drive("Drive 3"); + Device dv1 = new Device("Device 1"); + Device dv2 = new Device("Device 2"); + var withTemplate = new Device("Device With Device 2 template") { Template = dv2 }; + using var s = Sfi.OpenSession(); + using var t = s.BeginTransaction(); + s.Save(dr1); + s.Save(dr2); + s.Save(dr3); + AddDrive(dv1, dr2); + AddDrive(dv1, dr1); + AddDrive(dv2, dr3); + AddDrive(dv2, dr1); + + s.Save(dv1); + id2 = (int) s.Save(dv2); + _withTemplateId = (int)s.Save(withTemplate); + t.Commit(); + } + + private void AddDrive(Device dv, Drive drive) + { + if(dv.DrivesNotIgnored.Count >= _drivesCount) + return; + dv.DrivesNotIgnored.Add(drive); + } + + protected override void OnTearDown() + { + using var s = Sfi.OpenSession(); + using var t = s.BeginTransaction(); + + s.CreateSQLQuery("delete from DriveOfDevice").ExecuteUpdate(); + s.Delete("from Device"); + s.Delete("from Drive"); + t.Commit(); + } + + [Test] + public async Task QueryOverFetchAsync() + { + using var log = new SqlLogSpy(); + using var s = OpenSession(); + var dv2 = await (s.QueryOver() + .Fetch(SelectMode.Fetch, x => x.DrivesNotIgnored) + .Where(Restrictions.IdEq(id2)) + .TransformUsing(Transformers.DistinctRootEntity) + .SingleOrDefaultAsync()); + + Assert.That(NHibernateUtil.IsInitialized(dv2.DrivesNotIgnored), Is.True); + Assert.That(dv2.DrivesNotIgnored, Has.Count.EqualTo(ValidDrivesCount).And.None.Null); + Assert.That(log.Appender.GetEvents().Length, Is.EqualTo(1)); + } + + [Test] + public async Task QueryOverFetch2Async() + { + using var log = new SqlLogSpy(); + using var s = OpenSession(); + var withTemplate = await (s.QueryOver() + .Fetch(SelectMode.Fetch, x => x.Template, x => x.Template.DrivesNotIgnored) + .Where(Restrictions.IdEq(_withTemplateId)) + .TransformUsing(Transformers.DistinctRootEntity) + .SingleOrDefaultAsync()); + + Assert.That(NHibernateUtil.IsInitialized(withTemplate.Template), Is.True); + Assert.That(NHibernateUtil.IsInitialized(withTemplate.Template.DrivesNotIgnored), Is.True); + Assert.That(withTemplate.Template.DrivesNotIgnored, Has.Count.EqualTo(ValidDrivesCount).And.None.Null); + Assert.That(log.Appender.GetEvents().Length, Is.EqualTo(1)); + } + + [Test] + public async Task HqlFetchAsync() + { + using var log = new SqlLogSpy(); + using var s = OpenSession(); + var dv2 = await (s.CreateQuery("from Device d left join fetch d.DrivesNotIgnored where d.id = :id") + .SetResultTransformer(Transformers.DistinctRootEntity) + .SetParameter("id", id2) + .UniqueResultAsync()); + + Assert.That(NHibernateUtil.IsInitialized(dv2.DrivesNotIgnored), Is.True); + Assert.That(dv2.DrivesNotIgnored, Has.Count.EqualTo(ValidDrivesCount).And.None.Null); + Assert.That(log.Appender.GetEvents().Length, Is.EqualTo(1)); + } + + [Test] + public async Task HqlFetch2Async() + { + using var log = new SqlLogSpy(); + using var s = OpenSession(); + var withTemplate = await (s.CreateQuery("from Device t left join fetch t.Template d left join fetch d.DrivesNotIgnored where d.id = :id") + .SetResultTransformer(Transformers.DistinctRootEntity) + .SetParameter("id", id2) + .UniqueResultAsync()); + + Assert.That(NHibernateUtil.IsInitialized(withTemplate.Template), Is.True); + Assert.That(NHibernateUtil.IsInitialized(withTemplate.Template.DrivesNotIgnored), Is.True); + Assert.That(withTemplate.Template.DrivesNotIgnored, Has.Count.EqualTo(ValidDrivesCount).And.None.Null); + Assert.That(log.Appender.GetEvents().Length, Is.EqualTo(1)); + } + + [Test] + public async Task LazyLoadAsync() + { + using var log = new SqlLogSpy(); + using var s = OpenSession(); + + var dv2 = await (s.GetAsync(id2)); + + await (NHibernateUtil.InitializeAsync(dv2.DrivesNotIgnored)); + Assert.That(NHibernateUtil.IsInitialized(dv2.DrivesNotIgnored), Is.True); + Assert.That(dv2.DrivesNotIgnored, Has.Count.EqualTo(ValidDrivesCount).And.None.Null); + // First query for Device, second for Drives collection + Assert.That(log.Appender.GetEvents().Length, Is.EqualTo(2)); + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH750/ManyToManyNotFoundIgnoreFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH750/ManyToManyNotFoundIgnoreFixture.cs new file mode 100644 index 00000000000..2889ad0ea64 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH750/ManyToManyNotFoundIgnoreFixture.cs @@ -0,0 +1,219 @@ +//------------------------------------------------------------------------------ +// +// 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 NHibernate.Criterion; +using NHibernate.Transform; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH750 +{ + using System.Threading.Tasks; + [TestFixture(0)] + [TestFixture(1)] + [TestFixture(2)] + public class ManyToManyNotFoundIgnoreFixtureAsync : BugTestCase + { + private int id1; + private int id2; + private int _drive2Id; + private int _withTemplateId; + private readonly int _drivesCount; + private int ValidDrivesCount => _drivesCount == 0 ? 0 : _drivesCount - 1; + + public ManyToManyNotFoundIgnoreFixtureAsync(int drivesCount) + { + _drivesCount = drivesCount; + } + + protected override void OnSetUp() + { + Drive dr1 = new Drive("Drive 1"); + Drive dr2 = new Drive("Drive 2"); + Drive dr3 = new Drive("Drive 3"); + Device dv1 = new Device("Device 1"); + Device dv2 = new Device("Device 2"); + var withTemplate = new Device("Device With Device 2 template") { Template = dv2 }; + + using var s = Sfi.OpenSession(); + using var t = s.BeginTransaction(); + s.Save(dr1); + _drive2Id = (int)s.Save(dr2); + s.Save(dr3); + AddDrive(dv1, dr2); + AddDrive(dv1, dr1); + AddDrive(dv2, dr3); + AddDrive(dv2, dr1); + + id1 = (int) s.Save(dv1); + id2 = (int) s.Save(dv2); + _withTemplateId = (int)s.Save(withTemplate); + s.Flush(); + + s.Clear(); + s.Delete(dr3); + t.Commit(); + } + + private void AddDrive(Device dv, Drive drive) + { + if(dv.Drives.Count >= _drivesCount) + return; + dv.Drives.Add(drive); + } + + protected override void OnTearDown() + { + using var s = Sfi.OpenSession(); + using var t = s.BeginTransaction(); + + s.CreateSQLQuery("delete from DriveOfDevice").ExecuteUpdate(); + s.Delete("from Device"); + s.Delete("from Drive"); + t.Commit(); + } + + [Test] + public async Task DeviceOfDriveAsync() + { + Device dv1; + Device dv2; + using (ISession s = Sfi.OpenSession()) + { + dv1 = (Device) await (s.LoadAsync(typeof(Device), id1)); + dv2 = (Device) await (s.LoadAsync(typeof(Device), id2)); + await (NHibernateUtil.InitializeAsync(dv1.Drives)); + await (NHibernateUtil.InitializeAsync(dv2.Drives)); + } + + Assert.That(dv1.Drives, Has.Count.EqualTo(_drivesCount).And.None.Null); + // Verify one is missing + Assert.That(dv2.Drives, Has.Count.EqualTo(ValidDrivesCount).And.None.Null); + + //Make sure that flush didn't touch not-found="ignore" records for not modified collection + using (var s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) + { + dv2 = await (s.GetAsync(dv2.Id)); + await (s.FlushAsync()); + await (t.CommitAsync()); + } + + await (VerifyResultAsync(expectedInCollection: ValidDrivesCount, expectedInDb: _drivesCount, msg: "not modified collection")); + + // Many-to-many clears collection and recreates it so not-found ignore records are lost + // Note: It's not the case when no valid records are present, so loaded Drives collection is empty + // Just skip this check in this case: + if (_drivesCount < 2) + return; + + using (var s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) + { + dv2 = await (s.GetAsync(dv2.Id)); + dv2.Drives.Add(await (s.LoadAsync(_drive2Id))); + await (t.CommitAsync()); + } + + await (VerifyResultAsync(_drivesCount, _drivesCount, msg: "modified collection")); + + async Task VerifyResultAsync(int expectedInCollection, int expectedInDb, string msg) + { + using (var s = Sfi.OpenSession()) + { + var realCound = Convert.ToInt32( + await (s.CreateSQLQuery("select count(*) from DriveOfDevice where DeviceId = :id ") + .SetParameter("id", dv2.Id) + .UniqueResultAsync())); + dv2 = await (s.GetAsync(dv2.Id)); + + Assert.That(dv2.Drives.Count, Is.EqualTo(expectedInCollection), msg); + Assert.That(realCound, Is.EqualTo(expectedInDb), msg); + } + } + } + + [Test] + public async Task QueryOverFetchAsync() + { + using var log = new SqlLogSpy(); + using var s = OpenSession(); + var dv2 = await (s.QueryOver() + .Fetch(SelectMode.Fetch, x => x.Drives) + .Where(Restrictions.IdEq(id2)) + .TransformUsing(Transformers.DistinctRootEntity) + .SingleOrDefaultAsync()); + + Assert.That(NHibernateUtil.IsInitialized(dv2.Drives), Is.True); + Assert.That(dv2.Drives, Has.Count.EqualTo(ValidDrivesCount).And.None.Null); + Assert.That(log.Appender.GetEvents().Length, Is.EqualTo(1)); + } + + [Test] + public async Task QueryOverFetch2Async() + { + using var log = new SqlLogSpy(); + using var s = OpenSession(); + var withTemplate = await (s.QueryOver() + .Fetch(SelectMode.Fetch, x => x.Template, x => x.Template.Drives) + .Where(Restrictions.IdEq(_withTemplateId)) + .TransformUsing(Transformers.DistinctRootEntity) + .SingleOrDefaultAsync()); + + Assert.That(NHibernateUtil.IsInitialized(withTemplate.Template), Is.True); + Assert.That(NHibernateUtil.IsInitialized(withTemplate.Template.Drives), Is.True); + Assert.That(withTemplate.Template.Drives, Has.Count.EqualTo(ValidDrivesCount).And.None.Null); + Assert.That(log.Appender.GetEvents().Length, Is.EqualTo(1)); + } + + [Test] + public async Task HqlFetchAsync() + { + using var log = new SqlLogSpy(); + using var s = OpenSession(); + var dv2 = await (s.CreateQuery("from Device d left join fetch d.Drives where d.id = :id") + .SetResultTransformer(Transformers.DistinctRootEntity) + .SetParameter("id", id2) + .UniqueResultAsync()); + + Assert.That(NHibernateUtil.IsInitialized(dv2.Drives), Is.True); + Assert.That(dv2.Drives, Has.Count.EqualTo(ValidDrivesCount).And.None.Null); + Assert.That(log.Appender.GetEvents().Length, Is.EqualTo(1)); + } + + [Test] + public async Task HqlFetch2Async() + { + using var log = new SqlLogSpy(); + using var s = OpenSession(); + var withTemplate = await (s.CreateQuery("from Device t left join fetch t.Template d left join fetch d.Drives where d.id = :id") + .SetResultTransformer(Transformers.DistinctRootEntity) + .SetParameter("id", id2) + .UniqueResultAsync()); + + Assert.That(NHibernateUtil.IsInitialized(withTemplate.Template), Is.True); + Assert.That(NHibernateUtil.IsInitialized(withTemplate.Template.Drives), Is.True); + Assert.That(withTemplate.Template.Drives, Has.Count.EqualTo(ValidDrivesCount).And.None.Null); + Assert.That(log.Appender.GetEvents().Length, Is.EqualTo(1)); + } + + [Test] + public async Task LazyLoadAsync() + { + using var s = OpenSession(); + + var dv2 = await (s.GetAsync(id2)); + using var log = new SqlLogSpy(); + + Assert.That(dv2.Drives, Has.Count.EqualTo(ValidDrivesCount).And.None.Null); + Assert.That(log.Appender.GetEvents().Length, Is.EqualTo(1)); + } + } +} diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH750/ManyToManyThrowsForNotFoundFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH750/ManyToManyThrowsForNotFoundFixture.cs new file mode 100644 index 00000000000..78848ea3659 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH750/ManyToManyThrowsForNotFoundFixture.cs @@ -0,0 +1,109 @@ +//------------------------------------------------------------------------------ +// +// 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.Linq; +using NHibernate.Criterion; +using NHibernate.Linq; +using NHibernate.Transform; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH750 +{ + using System.Threading.Tasks; + [TestFixture] + public class ManyToManyThrowsForNotFoundFixtureAsync : BugTestCase + { + private int _id; + private int _withTemplateId; + + protected override void OnSetUp() + { + using var s = Sfi.OpenSession(); + using var t = s.BeginTransaction(); + Device dv = new Device("Device"); + Drive dr = new Drive("Drive"); + var withTemplate = new Device("Device With Device 2 template") { Template = dv }; + s.Save(dr); + dv.DrivesNotIgnored.Add(dr); + + _id = (int) s.Save(dv); + _withTemplateId = (int)s.Save(withTemplate); + s.Flush(); + + s.Clear(); + s.Delete(dr); + t.Commit(); + } + + protected override void OnTearDown() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Delete("from Device"); + s.Delete("from Drive"); + t.Commit(); + } + } + + [Test] + public async Task LazyLoadAsync() + { + using var s = OpenSession(); + var device = await (s.GetAsync(_id)); + Assert.ThrowsAsync(() => NHibernateUtil.InitializeAsync(device.DrivesNotIgnored)); + } + + [Test] + public void QueryOverFetchAsync() + { + using var s = OpenSession(); + var queryOver = s.QueryOver() + .Fetch(SelectMode.Fetch, x => x.DrivesNotIgnored) + .Where(Restrictions.IdEq(_id)) + .TransformUsing(Transformers.DistinctRootEntity); + Assert.ThrowsAsync(() => queryOver.SingleOrDefaultAsync()); + } + + [Test] + public void QueryOverFetch2Async() + { + using var s = OpenSession(); + var queryOver = s.QueryOver() + .Fetch(SelectMode.Fetch, x=> x.Template, x => x.Template.DrivesNotIgnored) + .Where(Restrictions.IdEq(_withTemplateId)) + .TransformUsing(Transformers.DistinctRootEntity); + Assert.ThrowsAsync(() => queryOver.SingleOrDefaultAsync()); + } + + [Test] + public void LinqFetchAsync() + { + using var s = OpenSession(); + var query = s.Query() + + .Fetch(x => x.DrivesNotIgnored) + .Where(x => x.Id == _id); + Assert.ThrowsAsync(async () => await (NHibernateUtil.InitializeAsync(await (query.SingleOrDefaultAsync())))); + } + + [Test] + public void LinqFetch2Async() + { + using var s = OpenSession(); + var query = s.Query() + + .Fetch(x => x.Template) + .ThenFetchMany(x => x.DrivesNotIgnored) + .Where(x => x.Id == _withTemplateId); + Assert.ThrowsAsync(async () => await (NHibernateUtil.InitializeAsync(await (query.SingleOrDefaultAsync())))); + } + } +} diff --git a/src/NHibernate.Test/Async/PropertyRef/ManyToManyPropertyRefFixture.cs b/src/NHibernate.Test/Async/PropertyRef/ManyToManyPropertyRefFixture.cs index 1bb2716733f..435758ac99e 100644 --- a/src/NHibernate.Test/Async/PropertyRef/ManyToManyPropertyRefFixture.cs +++ b/src/NHibernate.Test/Async/PropertyRef/ManyToManyPropertyRefFixture.cs @@ -8,20 +8,22 @@ //------------------------------------------------------------------------------ +using System.Linq; using NHibernate.Criterion; +using NHibernate.Linq; using NUnit.Framework; namespace NHibernate.Test.PropertyRef { using System.Threading.Tasks; - [TestFixture] + [TestFixture(Description = "NH-2180 (GH-1214)")] public class ManyToManyPropertyRefFixtureAsync : TestCase { protected override string[] Mappings => new[] { "PropertyRef.ManyToManyWithPropertyRef.hbm.xml" }; protected override string MappingsAssembly => "NHibernate.Test"; - private object _manyAId; + private long _manyAId; protected override void OnSetUp() { @@ -34,7 +36,7 @@ protected override void OnSetUp() var manyB2 = new ManyB { Number = 8, Value = "a value of b2" }; var manyB3 = new ManyB { Number = 12, Value = "a value of b3" }; - _manyAId = session.Save(manyA); + _manyAId = (long) session.Save(manyA); session.Save(manyB1); session.Save(manyB2); session.Save(manyB3); @@ -144,5 +146,20 @@ bei NHibernate.Type.EntityType.LoadByUniqueKey(String entityName, String uniqueK Assert.That(loadedManyA.ManyBs, Has.Count.EqualTo(3).And.None.Null); } + + [Test] + public async Task LinqFetchAsync() + { + using (var session = OpenSession()) + { + var manyA = (await (session + .Query() + .Where(a => a.Id == _manyAId) + .FetchMany(a => a.ManyBs) + .ToListAsync())) + .First(); + Assert.That(manyA.ManyBs, Has.Count.EqualTo(3).And.None.Null); + } + } } } diff --git a/src/NHibernate.Test/CacheTest/BatchableCacheFixture.cs b/src/NHibernate.Test/CacheTest/BatchableCacheFixture.cs index 150369306b2..d3f06568cd6 100644 --- a/src/NHibernate.Test/CacheTest/BatchableCacheFixture.cs +++ b/src/NHibernate.Test/CacheTest/BatchableCacheFixture.cs @@ -1553,8 +1553,79 @@ public void QueryFetchEntityBatchCacheTest(bool clearEntityCacheAfterQuery, bool Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(future ? 2 : 1), "Unexpected cache hit count"); } + [Test] + public void CollectionLazyInitializationFromCacheIsBatched() + { + using (var s = OpenSession()) + { + var readOnly = s.Get(s.Query().Select(x => x.Id).First()); + Assert.That(readOnly.Items.Count, Is.EqualTo(6)); + } + + var itemPersister = Sfi.GetEntityPersister(typeof(ReadOnlyItem).FullName); + var itemCache = (BatchableCache) itemPersister.Cache.Cache; + itemCache.ClearStatistics(); + + using (var s = OpenSession()) + { + var readOnly = s.Get(s.Query().Select(x => x.Id).First()); + Assert.That(readOnly.Items.Count, Is.EqualTo(6)); + } + + // 6 items with batch-size = 4 so 2 GetMany calls are expected 1st call: 4 items + 2nd call: 2 items + Assert.That(itemCache.GetMultipleCalls.Count, Is.EqualTo(2)); + } + + [Test] + public void CollectionLazyInitializationFromCacheIsBatched_FillCacheByQueryCache() + { + var itemPersister = Sfi.GetEntityPersister(typeof(ReadOnlyItem).FullName); + var itemCache = (BatchableCache) itemPersister.Cache.Cache; + itemCache.ClearStatistics(); + int id; + using (var s = OpenSession()) + { + id = s.Query().Select(x => x.Id).First(); + var readOnly = s.Query().Fetch(x => x.Items) + .Where(x => x.Id == id) + .WithOptions(x => x.SetCacheable(true)) + .ToList() + .First(); + Assert.That(itemCache.PutMultipleCalls.Count, Is.EqualTo(1)); + Assert.That(itemCache.GetMultipleCalls.Count, Is.EqualTo(0)); + Assert.That(NHibernateUtil.IsInitialized(readOnly.Items)); + Assert.That(readOnly.Items.Count, Is.EqualTo(6)); + } + + itemCache.ClearStatistics(); + using (var s = OpenSession()) + { + var readOnly = s.Query().Fetch(x => x.Items) + .Where(x => x.Id == id) + .WithOptions(x => x.SetCacheable(true)) + .ToList() + .First(); + Assert.That(itemCache.PutMultipleCalls.Count, Is.EqualTo(0)); + Assert.That(itemCache.GetMultipleCalls.Count, Is.EqualTo(1)); + Assert.That(NHibernateUtil.IsInitialized(readOnly.Items)); + Assert.That(readOnly.Items.Count, Is.EqualTo(6)); + } + + itemCache.ClearStatistics(); + + + using (var s = OpenSession()) + { + var readOnly = s.Get(id); + Assert.That(readOnly.Items.Count, Is.EqualTo(6)); + } + + // 6 items with batch-size = 4 so 2 GetMany calls are expected 1st call: 4 items + 2nd call: 2 items + Assert.That(itemCache.GetMultipleCalls.Count, Is.EqualTo(2)); + } + private void AssertMultipleCacheCalls(IEnumerable loadIds, IReadOnlyList getIds, int idIndex, - int[][] fetchedIdIndexes, int[] putIdIndexes, Func cacheBeforeLoadFn = null) + int[][] fetchedIdIndexes, int[] putIdIndexes, Func cacheBeforeLoadFn = null) where TEntity : CacheEntity { var persister = Sfi.GetEntityPersister(typeof(TEntity).FullName); diff --git a/src/NHibernate.Test/DialectTest/MsSql2008DialectFixture.cs b/src/NHibernate.Test/DialectTest/MsSql2008DialectFixture.cs index e02b69dfed2..3115bf26328 100644 --- a/src/NHibernate.Test/DialectTest/MsSql2008DialectFixture.cs +++ b/src/NHibernate.Test/DialectTest/MsSql2008DialectFixture.cs @@ -148,16 +148,7 @@ public void ScaleTypes() Assert.That(dialect.GetTypeName(SqlTypeFactory.GetTime(max + 1)), Is.EqualTo("time").IgnoreCase, "Over max time"); } - private static readonly FieldInfo _mappingField = - typeof(Configuration).GetField("mapping", BindingFlags.Instance | BindingFlags.NonPublic); - - private static IMapping GetMapping(Configuration cfg) - { - Assert.That(_mappingField, Is.Not.Null, "Unable to find field mapping"); - var mapping = _mappingField.GetValue(cfg) as IMapping; - Assert.That(mapping, Is.Not.Null, "Unable to find mapping object"); - return mapping; - } + private static IMapping GetMapping(Configuration cfg) => (IMapping) cfg.BuildSessionFactory(); private static void AssertSqlType(IType type, SqlType sqlType, IMapping mapping) { diff --git a/src/NHibernate.Test/FetchLazyProperties/FetchLazyPropertiesFixture.cs b/src/NHibernate.Test/FetchLazyProperties/FetchLazyPropertiesFixture.cs index 7a89e998c36..f05c462e5c8 100644 --- a/src/NHibernate.Test/FetchLazyProperties/FetchLazyPropertiesFixture.cs +++ b/src/NHibernate.Test/FetchLazyProperties/FetchLazyPropertiesFixture.cs @@ -175,6 +175,18 @@ public void TestLinqFetchProperty() AssertFetchProperty(person); } + [Test] + public void TestLinqFetchPropertyAfterSelect() + { + using var s = OpenSession(); + var owner = s.Query() + .Select(a => a.Owner) + .Fetch(o => o.Image) + .FirstOrDefault(o => o.Id == 1); + + AssertFetchProperty(owner); + } + private static void AssertFetchProperty(Person person) { Assert.That(person, Is.Not.Null); diff --git a/src/NHibernate.Test/Hql/Ast/ParsingFixture.cs b/src/NHibernate.Test/Hql/Ast/ParsingFixture.cs index 45be4789f1c..3af770191d5 100644 --- a/src/NHibernate.Test/Hql/Ast/ParsingFixture.cs +++ b/src/NHibernate.Test/Hql/Ast/ParsingFixture.cs @@ -1,176 +1,137 @@ using System; using System.Collections.Generic; -using log4net.Config; -using NHibernate.Cfg; -using NHibernate.Engine; +using System.IO; +using System.Xml.Linq; using NHibernate.Hql.Ast.ANTLR; -using NHibernate.Tool.hbm2ddl; using NUnit.Framework; namespace NHibernate.Test.Hql.Ast { - // This test need the new NUnit - //[TestFixture] - //public class ParsingFixture - //{ - // /// - // /// Key test for HQL strings -> HQL AST's - takes the query and returns a string - // /// representation of the resultant tree - // /// - // /// - // /// - // [Test] - // [TestCaseSource(typeof(QueryFactoryClass), "TestCases")] - // public string HqlParse(string query) - // { - // // This test need the new NUnit - // var p = new HqlParseEngine(query, false, null); - // p.Parse(); - - // return " " + p.Ast.ToStringTree(); - // } - - // /// - // /// Used to test individual queries "by hand", since td.net doesn't let me run a - // /// single test out of a data set - // /// - // [Test] - // public void ManualTest() - // { - // var p = new HqlParseEngine(@"select all s, s.Other from s in class Simple where s = :s", false, null); - - // p.Parse(); - - // Console.WriteLine(p.Ast.ToStringTree()); - // } - - // /// - // /// Helper "test" to display queries that are ignored - // /// - // [Test] - // public void ShowIgnoredQueries() - // { - // foreach (string query in QueryFactoryClass.GetIgnores) - // { - // Console.WriteLine(query); - // } - // } - - // /// - // /// Helper "test" to display queries that don't parse in H3 - // /// - // [Test] - // public void ShowExceptionQueries() - // { - // foreach (string query in QueryFactoryClass.GetExceptions) - // { - // Console.WriteLine(query); - // } - // } - - // /// - // /// Goes all the way to the DB and back. Just here until there's a better place to put it... - // /// - // [Test] - // public void BasicQuery() - // { - // string input = "select o.id, li.id from NHibernate.Test.CompositeId.Order o join o.LineItems li";// join o.LineItems li"; - - // ISessionFactoryImplementor sfi = SetupSFI(); - - // ISession session = sfi.OpenSession(); - // session.CreateQuery(input).List(); - // /* - // foreach (Animal o in session.CreateQuery(input).Enumerable()) - // { - // Console.WriteLine(o.Description); - // }*/ - // } - - // ISessionFactoryImplementor SetupSFI() - // { - // Configuration cfg = new Configuration(); - // cfg.AddAssembly(this.GetType().Assembly); - // new SchemaExport(cfg).Create(false, true); - // return (ISessionFactoryImplementor)cfg.BuildSessionFactory(); - // } - - // /// - // /// Class used by Nunit 2.5 to drive the data into the HqlParse test - // /// - // public class QueryFactoryClass - // { - // public static IEnumerable TestCases - // { - // get - // { - // // TODO - need to handle Ignore better (it won't show in results...) - // return EnumerateTests(td => !td.Ignore && !td.Result.StartsWith("Exception"), - // td => new TestCaseData(td.Query) - // .Returns(td.Result) - // .SetCategory(td.Category) - // .SetName(td.Name) - // .SetDescription(td.Description)); - // } - // } - - // public static IEnumerable GetIgnores - // { - // get - // { - // return EnumerateTests(td => td.Ignore, - // td => td.Query); - // } - // } - - // public static IEnumerable GetExceptions - // { - // get - // { - // return EnumerateTests(td => td.Result.StartsWith("Exception"), - // td => td.Query); - // } - // } - - // static IEnumerable EnumerateTests(Func predicate, Func projection) - // { - // XDocument doc = XDocument.Load(@"HQL Parsing\TestQueriesWithResults.xml"); - - // foreach (XElement testGroup in doc.Element("Tests").Elements("TestGroup")) - // { - // string category = testGroup.Attribute("Name").Value; - - // foreach (XElement test in testGroup.Elements("Test")) - // { - // QueryTestData testData = new QueryTestData(category, test); - - // if (predicate(testData)) - // { - // yield return projection(testData); - // } - // } - // } - // } - - // class QueryTestData - // { - // internal QueryTestData(string category, XElement xml) - // { - // Category = category; - // Query = xml.Element("Query").Value; - // Result = xml.Element("Result") != null ? xml.Element("Result").Value : "barf"; - // Name = xml.Element("Name") != null ? xml.Element("Name").Value : null; - // Description = xml.Element("Description") != null ? xml.Element("Description").Value : null; - // Ignore = xml.Attribute("Ignore") != null ? bool.Parse(xml.Attribute("Ignore").Value) : false; - // } - - // internal string Category; - // internal string Query; - // internal string Result; - // internal string Name; - // internal string Description; - // internal bool Ignore; - // } - // } - //} + [TestFixture] + public class ParsingFixture + { + /// + /// Key test for HQL strings -> HQL AST's - takes the query and returns a string + /// representation of the resultant tree + /// + /// + /// + [Test] + [TestCaseSource(typeof(QueryFactoryClass), nameof(QueryFactoryClass.TestCases))] + public string HqlParse(string query) + { + var p = new HqlParseEngine(query, false, null); + var result = p.Parse().ToStringTree(); + + return " " + result; + } + + /// + /// Used to test individual queries "by hand", since td.net doesn't let me run a + /// single test out of a data set + /// + [Test, Explicit] + public void ManualTest() + { + var p = new HqlParseEngine(@"select all s, s.Other from s in class Simple where s = :s", false, null); + + var result = p.Parse().ToStringTree(); + + Console.WriteLine(result); + } + + /// + /// Helper "test" to display queries that are ignored + /// + [Test, Explicit] + public void ShowIgnoredQueries() + { + foreach (string query in QueryFactoryClass.GetIgnores) + { + Console.WriteLine(query); + } + } + + /// + /// Helper "test" to display queries that don't parse in H3 + /// + [Test, Explicit] + public void ShowExceptionQueries() + { + foreach (string query in QueryFactoryClass.GetExceptions) + { + Console.WriteLine(query); + } + } + + /// + /// Class used by NUnit to drive the data into the HqlParse test. + /// + public class QueryFactoryClass + { + public static IEnumerable TestCases => + // TODO - need to handle Ignore better (it won't show in results...) + EnumerateTests( + td => !td.Ignore && !td.Result.StartsWith("Exception"), + td => new TestCaseData(td.Query) + .Returns(td.Result) + .SetCategory(td.Category) + .SetName(td.Name) + .SetDescription(td.Description)); + + public static IEnumerable GetIgnores => + EnumerateTests( + td => td.Ignore, + td => td.Query); + + public static IEnumerable GetExceptions => + EnumerateTests( + td => td.Result.StartsWith("Exception"), + td => td.Query); + + static IEnumerable EnumerateTests( + Func predicate, + Func projection) + { + + XDocument doc = XDocument.Load( + Path.Combine(Path.GetDirectoryName(typeof(ParsingFixture).Assembly.Location), @"Hql/Ast/TestQueriesWithResults.xml")); + + foreach (XElement testGroup in doc.Element("Tests").Elements("TestGroup")) + { + string category = testGroup.Attribute("Name").Value; + + foreach (XElement test in testGroup.Elements("Test")) + { + QueryTestData testData = new QueryTestData(category, test); + + if (predicate(testData)) + { + yield return projection(testData); + } + } + } + } + + class QueryTestData + { + internal QueryTestData(string category, XElement xml) + { + Category = category; + Query = xml.Element("Query").Value.Trim(); + Result = xml.Element("Result")?.Value; + Name = xml.Element("Name")?.Value; + Description = xml.Element("Description")?.Value.Trim() ?? string.Empty; + Ignore = bool.Parse(xml.Attribute("Ignore")?.Value ?? "false"); + } + + internal string Category; + internal string Query; + internal string Result; + internal string Name; + internal string Description; + internal bool Ignore; + } + } + } } diff --git a/src/NHibernate.Test/Hql/Ast/TestQueries.xml b/src/NHibernate.Test/Hql/Ast/TestQueries.xml deleted file mode 100644 index 5b0488ae037..00000000000 --- a/src/NHibernate.Test/Hql/Ast/TestQueries.xml +++ /dev/null @@ -1,3006 +0,0 @@ - - - - - all baz.IntArray.indices]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0]]> - - - 0]]> - - - current_date]]> - - - 100]]> - - - 10000]]> - - - - 100]]> - - - 10000]]> - - - - - - - - - - - - - all elements(p.scores)]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 100 - order by count(kitten) asc, sum(kitten.weight) desc - ]]> - - - - - - - = all ( - select cat.effectiveDate from Catalog as cat where cat.effectiveDate < sysdate) group by ord - having sum(price.amount) > :minAmount order by sum(price.amount) desc - ]]> - - - - :minAmount order by sum(price.amount) desc - ]]> - - - - PaymentStatus.AWAITING_APPROVAL or ( - statusChange.timeStamp = ( select max(change.timeStamp) - from PaymentStatusChange change where change.payment = payment - ) and statusChange.user <> :currentUser ) - group by status.name, status.sortOrder order by status.sortOrder - ]]> - - - PaymentStatus.AWAITING_APPROVAL - or payment.statusChanges[ maxIndex(payment.statusChanges) ].user <> :currentUser - group by status.name, status.sortOrder order by status.sortOrder - ]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ( select avg(cat.weight) from eg.DomesticCat cat)]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 3.1415e3]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - parse( - "SELECT DISTINCT bar FROM eg.mypackage.Cat qat left join com.multijoin.JoinORama as bar, com.toadstool.Foo f join net.sf.blurb.Blurb"); - - - - - - - - - - - - - 5]]> - - - 5]]> - - - - - - - - - - - - 10]]> - - - 10 and an.bodyWeight < 100) or an.bodyWeight is null - ]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Added quote quote is an escape - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0]]> - - - 'a' or foo2.id <'a' - ]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ? or bar.short = 1 or bar.string = 'ff ? bb']]> - - - - - - - - - - - - - - - - - - '0' order by s.foo]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 'a']]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - TODO: "count" is reserved - - - - TODO: "count" is reserved - - - - TODO: "count" is reserved - - - - - - - - - - 0]]> - - - 0]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 10]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - :count]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0]]> - - - - - - 'a']]> - - - - - - - - - - - - - - - - - - - - - 'a']]> - - - 'a']]> - - - - - - - - - - - - - - - -1 and s.name is null]]> - - - - - - -1 and s.name is null]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - ]]> - - - 0 and m.extraProp is not null]]> - - - 0 and m.name is not null]]> - - - - - - - - - 0.0]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Added '?' as a valid expression. - - - - - - - - - Added collectionExpr as a valid 'in' clause. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Cover NOT optimization in HqlParser - - - - - 1 )]]> - - - = 1 )]]> - - - - - - - - - - - - - - - double "NOT" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - from h3.2.6 I'm not sure about these... [jsd] - - - - from h3.2.6 I'm not sure about these... [jsd] - - - - from h3.2.6 I'm not sure about these... [jsd] - -1 and this.name is null]]> - - - from h3.2.6 I'm not sure about these... [jsd] - - - - from h3.2.6 I'm not sure about these... [jsd] - - - - - - - - - - - - - - - no member of - - - - - - - - - - - - - - - - The keyword 'order' used as a property name. - - - - The keyword 'order' and 'count' used as a property name. - 3]]> - - - - The keywords 'where', 'order' and 'count' used as a property name. - 3]]> - - - - - - - - - - - - - Super evil badness... a legitimate keyword! - - - - - - - - - - - - - - - - - - - - - - - - - - Okay, now this is getting silly. - - - - - - - - - - - - - - - 'where' as a package name - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - TODO support .NET identifier NH-685; won't fix ? - - - - - - - TODO Some SQLs have function names with package qualifiers. - - - - - - - - - - - - - - - - - won't fix - - :dateenow ]]> - - - - - - - - - - - - - - 1]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - [Test, Ignore("Not supported")] - - - TODO InnerClass but for NET - the problem with inner class is that in .NET the "separator" is '+' and - Path+Identifier is a valid MathAddExpression - because this is a special case and the use of entity-name is a valid workaroud we can wait - to support it or we can choose another character to query inner-classes (the same of JAVA '$' is a valid choice) - - - - - - - - - - - TODO Support Unicode in StringLiteral - - - - - - - - from NHibernate tests - - - - - - - - 1]]> - - - - - - 0]]> - - - - - - 0]]> - - - - - - 0]]> - - - - - - 0]]> - - - - from NHibernate tests - - - - - - - - - - - - - - - - - - - - - - - - from NHibernate tests - - - - - - - - - - from NHibernate tests - - - - - - - - - - - - - - - - - - - - - - - - - - - from NHibernate tests - - - - - - - - - from NHibernate tests - - - - - - - - - from NHibernate tests - - - - - - - - - - from NHibernate tests - - - - - - - - - from NHibernate tests - - - - - 0]]> - - - 0]]> - - - - from NHibernate tests - - - - - - - - - - - - - - from NHibernate tests - - - - - 10]]> - - - - - from NHibernate tests - - - - - - - - - - - - from NHibernate tests - - - - - - - - - - - - - - from NHibernate tests - - - - - - - - - - - - - - 0]]> - - - 0]]> - - - - - - - - - - - - - - - 0]]> - - - 0]]> - - - 0]]> - - - - - - - - - - - from NHibernate tests - - - - - - - from NHibernate tests - - - - - 0]]> - - - - - from NHibernate tests - - - - - - - - - - from NHibernate tests - - - - - - - - from NHibernate tests - - - - - - - from NHibernate tests - - - - - - - - - - from NHibernate tests - - - - - - - - - - from NHibernate tests - - 0]]> - - - 3]]> - - - 0]]> - - - - \ No newline at end of file diff --git a/src/NHibernate.Test/Hql/Ast/TestQueriesWithResults.xml b/src/NHibernate.Test/Hql/Ast/TestQueriesWithResults.xml index c7d8efb5a52..03fa3aa7a5e 100644 --- a/src/NHibernate.Test/Hql/Ast/TestQueriesWithResults.xml +++ b/src/NHibernate.Test/Hql/Ast/TestQueriesWithResults.xml @@ -269,14 +269,14 @@ select cat.color, sum(cat.weight), count(cat) from eg.Cat cat group by cat.color having cat.color in (eg.Color.TABBY, eg.Color.BLACK) ]]> - + 100 order by count(kitten) asc, sum(kitten.weight) desc ]]> - ( avg ( . kitten weight ) ) 100 ) ) ) ( order ( count kitten ) asc ( sum ( . kitten weight ) ) desc ) )]]> + ( avg ( . kitten weight ) ) 100 ) ) ( order ( count kitten ) asc ( sum ( . kitten weight ) ) desc ) )]]> @@ -290,7 +290,7 @@ select cat.effectiveDate from Catalog as cat where cat.effectiveDate < sysdate) group by ord having sum(price.amount) > :minAmount order by sum(price.amount) desc ]]> - = ( . catalog effectiveDate ) ( all ( query ( SELECT_FROM ( from ( RANGE Catalog cat ) ) ( select ( . cat effectiveDate ) ) ) ( where ( < ( . cat effectiveDate ) sysdate ) ) ) ) ) ) ) ( group ord ( having ( > ( sum ( . price amount ) ) ( : minAmount ) ) ) ) ( order ( sum ( . price amount ) ) desc ) )]]> + = ( . catalog effectiveDate ) ( all ( query ( SELECT_FROM ( from ( RANGE Catalog cat ) ) ( select ( . cat effectiveDate ) ) ) ( where ( < ( . cat effectiveDate ) sysdate ) ) ) ) ) ) ) ( group ord ) ( having ( > ( sum ( . price amount ) ) ( : minAmount ) ) ) ( order ( sum ( . price amount ) ) desc ) )]]> :minAmount order by sum(price.amount) desc ]]> - ( sum ( . price amount ) ) ( : minAmount ) ) ) ) ( order ( sum ( . price amount ) ) desc ) )]]> + ( sum ( . price amount ) ) ( : minAmount ) ) ) ( order ( sum ( . price amount ) ) desc ) )]]> 10]]> - ( sum ( . s count ) ) 10 ) ) ) )]]> + ( sum ( . s count ) ) 10 ) ) )]]> - + @@ -3510,7 +3510,7 @@ 0]]> - ( ( abs ( exprList ( * ( . a BodyWeight ) ( - 1 ) ) ) ) 0 ) ) ) )]]> + ( ( abs ( exprList ( * ( . a BodyWeight ) ( - 1 ) ) ) ) 0 ) ) )]]> @@ -3551,7 +3551,7 @@ - + @@ -3568,7 +3568,7 @@ - + @@ -3615,15 +3615,15 @@ 0]]> - ( ( cast ( exprList ( . a BodyWeight ) Double ) ) 0 ) ) ) )]]> + ( ( cast ( exprList ( . a BodyWeight ) Double ) ) 0 ) ) )]]> 0]]> - ( ( cast ( exprList ( - ( + 7 123.3 ) ( * 1 ( . a BodyWeight ) ) ) int ) ) 0 ) ) ) )]]> + ( ( cast ( exprList ( - ( + 7 123.3 ) ( * 1 ( . a BodyWeight ) ) ) int ) ) 0 ) ) )]]> 0]]> - ( ( cast ( exprList ( + ( : aParam ) ( . a BodyWeight ) ) int ) ) 0 ) ) ) )]]> + ( ( cast ( exprList ( + ( : aParam ) ( . a BodyWeight ) ) int ) ) 0 ) ) )]]> @@ -3718,7 +3718,7 @@ 0]]> - ( ( cast ( exprList ( + ( : aParam ) ( . a BodyWeight ) ) Double ) ) 0 ) ) ) )]]> + ( ( cast ( exprList ( + ( : aParam ) ( . a BodyWeight ) ) Double ) ) 0 ) ) )]]> diff --git a/src/NHibernate.Test/Hql/Parser/HqlParserFixture.cs b/src/NHibernate.Test/Hql/Parser/HqlParserFixture.cs new file mode 100644 index 00000000000..9eb0095ea37 --- /dev/null +++ b/src/NHibernate.Test/Hql/Parser/HqlParserFixture.cs @@ -0,0 +1,30 @@ +using Antlr.Runtime; +using NHibernate.Hql.Ast.ANTLR; +using NHibernate.Hql.Ast.ANTLR.Tree; +using NUnit.Framework; + +namespace NHibernate.Test.Hql.Parser +{ + [TestFixture] + public class HqlParserFixture + { + [Test] + public void HandlesPathWithReservedWords() + { + Assert.DoesNotThrow(() => Parse("delete from System.Object")); + Assert.DoesNotThrow(() => Parse("delete from Object.Object.Object.Object")); + } + + private static void Parse(string hql) + { + var lex = new HqlLexer(new CaseInsensitiveStringStream(hql)); + var tokens = new CommonTokenStream(lex); + + var parser = new HqlParser(tokens) + { + TreeAdaptor = new ASTTreeAdaptor(), + ParseErrorHandler = new WarningAsErrorReporter() + }.statement(); + } + } +} diff --git a/src/NHibernate.Test/Hql/Parser/WarningAsErrorReporter.cs b/src/NHibernate.Test/Hql/Parser/WarningAsErrorReporter.cs new file mode 100644 index 00000000000..5897d670698 --- /dev/null +++ b/src/NHibernate.Test/Hql/Parser/WarningAsErrorReporter.cs @@ -0,0 +1,29 @@ +using Antlr.Runtime; +using NHibernate.Hql.Ast.ANTLR; + +namespace NHibernate.Test.Hql.Parser +{ + public class WarningAsErrorReporter : IParseErrorHandler + { + public void ReportError(RecognitionException e) + { + throw e; + } + + public void ReportError(string s) + { + throw new QueryException(s); + } + + public void ReportWarning(string s) + { + throw new QueryException(s); + } + + public int GetErrorCount() => 0; + + public void ThrowQueryException() + { + } + } +} diff --git a/src/NHibernate.Test/LazyProperty/Book.cs b/src/NHibernate.Test/LazyProperty/Book.cs index 3dcfe73c567..10eb7d241f2 100644 --- a/src/NHibernate.Test/LazyProperty/Book.cs +++ b/src/NHibernate.Test/LazyProperty/Book.cs @@ -28,5 +28,11 @@ public virtual byte[] NoSetterImage public virtual string FieldInterceptor { get; set; } public virtual IList Words { get; set; } + + public virtual int this[int i] + { + get { return i;} + set { } + } } } diff --git a/src/NHibernate.Test/LazyProperty/LazyPropertyFixture.cs b/src/NHibernate.Test/LazyProperty/LazyPropertyFixture.cs index 302271f41a3..1b588dee440 100644 --- a/src/NHibernate.Test/LazyProperty/LazyPropertyFixture.cs +++ b/src/NHibernate.Test/LazyProperty/LazyPropertyFixture.cs @@ -225,6 +225,9 @@ public void CanGetValueForNonLazyProperty() Assert.That(book.Name, Is.EqualTo("some name")); Assert.That(book.FieldInterceptor, Is.EqualTo("Why not that name?")); Assert.That(NHibernateUtil.IsPropertyInitialized(book, "ALotOfText"), Is.False); + //GH-3354 Exception accessing indexer property + Assert.That(book[0], Is.EqualTo(0)); + Assert.DoesNotThrow(() => book[0] = 0); } } diff --git a/src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH1994/ManyToManyFilteredFixture.cs similarity index 82% rename from src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs rename to src/NHibernate.Test/NHSpecificTest/GH1994/ManyToManyFilteredFixture.cs index 5dbe043ebfe..4edb09d5cb1 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH1994/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH1994/ManyToManyFilteredFixture.cs @@ -1,6 +1,5 @@ using System.Linq; using NHibernate.Criterion; -using NHibernate.Dialect; using NHibernate.Linq; using NHibernate.SqlCommand; using NHibernate.Transform; @@ -9,7 +8,7 @@ namespace NHibernate.Test.NHSpecificTest.GH1994 { [TestFixture] - public class Fixture : BugTestCase + public class ManyToManyFilteredFixture : BugTestCase { protected override void OnSetUp() { @@ -30,14 +29,7 @@ protected override void OnTearDown() using (var session = OpenSession()) using (var transaction = session.BeginTransaction()) { - // The HQL delete does all the job inside the database without loading the entities, but it does - // not handle delete order for avoiding violating constraints if any. Use - // session.Delete("from System.Object"); - // instead if in need of having NHibernate ordering the deletes, but this will cause - // loading the entities in the session. - session.Delete("from System.Object"); - transaction.Commit(); } } @@ -59,9 +51,6 @@ public void TestUnfilteredLinqQuery() [Test] public void TestFilteredByWhereCollectionLinqQuery() { - if(Dialect is PostgreSQLDialect) - Assert.Ignore("Dialect doesn't support 0/1 to bool implicit cast"); - using (var s = OpenSession()) { var query = s.Query() @@ -139,5 +128,31 @@ public void TestQueryOverRestrictionWithClause() Assert.That(query[0].Documents.Count, Is.EqualTo(1), "filtered asset documents"); } } + + [Test] + public void LazyLoad() + { + using (var s = OpenSession()) + { + var asset = s.Query().First(); + Assert.That(asset.Documents.Count, Is.EqualTo(2)); + Assert.That(asset.DocumentsBag.Count, Is.EqualTo(2)); + Assert.That(asset.DocumentsFiltered.Count, Is.EqualTo(1)); + } + } + + [Test] + public void LazyLoadFiltered() + { + using (var s = OpenSession()) + { + s.EnableFilter("deletedFilter").SetParameter("deletedParam", false); + + var asset = s.Query().First(); + Assert.That(asset.Documents.Count, Is.EqualTo(1)); + Assert.That(asset.DocumentsBag.Count, Is.EqualTo(1)); + Assert.That(asset.DocumentsFiltered.Count, Is.EqualTo(1)); + } + } } } diff --git a/src/NHibernate.Test/NHSpecificTest/GH1994/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH1994/Mappings.hbm.xml index 449a4b8bc97..dba718ccde7 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH1994/Mappings.hbm.xml +++ b/src/NHibernate.Test/NHSpecificTest/GH1994/Mappings.hbm.xml @@ -5,7 +5,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -39,7 +39,7 @@ - + diff --git a/src/NHibernate.Test/NHSpecificTest/GH3311SqlQueryParam/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH3311SqlQueryParam/Entity.cs new file mode 100644 index 00000000000..3dba8b9c90b --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3311SqlQueryParam/Entity.cs @@ -0,0 +1,10 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH3311SqlQueryParam +{ + class Entity + { + public virtual long Id { get; set; } + public virtual string Name { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH3311SqlQueryParam/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH3311SqlQueryParam/Mappings.hbm.xml new file mode 100644 index 00000000000..028a77a8a61 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3311SqlQueryParam/Mappings.hbm.xml @@ -0,0 +1,14 @@ + + + + + + + + + + select s.Id from Entity s where s.Name = :name + + + diff --git a/src/NHibernate.Test/NHSpecificTest/GH3311SqlQueryParam/SqlQueryParamTypeFixture.cs b/src/NHibernate.Test/NHSpecificTest/GH3311SqlQueryParam/SqlQueryParamTypeFixture.cs new file mode 100644 index 00000000000..c7c5f1967f0 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3311SqlQueryParam/SqlQueryParamTypeFixture.cs @@ -0,0 +1,50 @@ +using System.Data; +using NHibernate.Dialect; +using NHibernate.SqlTypes; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH3311SqlQueryParam +{ + [TestFixture] + public class SqlQueryParamTypeFixture : BugTestCase + { + protected override void OnSetUp() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + var e1 = new Entity {Name = "Bob"}; + session.Save(e1); + + var e2 = new Entity {Name = "Sally"}; + session.Save(e2); + + transaction.Commit(); + } + + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return + //Dialects like SQL Server CE, Firebird don't distinguish AnsiString from String + (Dialect.GetTypeName(new SqlType(DbType.AnsiString)) != Dialect.GetTypeName(new SqlType(DbType.String)) + || Dialect is SQLiteDialect); + } + + protected override void OnTearDown() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + + [Test] + public void AppliesParameterTypeFromQueryParam() + { + using var log = new SqlLogSpy(); + using var s = OpenSession(); + s.GetNamedQuery("entityIdByName").SetParameter("name", "Bob").UniqueResult(); + Assert.That(log.GetWholeLog(), Does.Contain("Type: AnsiString")); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH3327/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH3327/Fixture.cs index aef1a40243e..31efc796ed1 100644 --- a/src/NHibernate.Test/NHSpecificTest/GH3327/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/GH3327/Fixture.cs @@ -48,5 +48,21 @@ WHERE NOT ( )"); Assert.That(q.List()[0], Is.EqualTo(0)); } + + [Test] + public void NotNotExistsNegated() + { + using var log = new SqlLogSpy(); + using var session = OpenSession(); + var results = session.CreateQuery( + @"SELECT COUNT(ROOT.Id) + FROM Entity AS ROOT + WHERE NOT ( + NOT EXISTS (FROM ChildEntity AS CHILD WHERE CHILD.Parent = ROOT) + AND NOT ROOT.Name = 'Parent' + )").List(); + Assert.That(log.GetWholeLog(), Does.Not.Contains(" NOT ").IgnoreCase); + Assert.That(results.Count, Is.EqualTo(1)); + } } } diff --git a/src/NHibernate.Test/NHSpecificTest/GH3352/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH3352/Entity.cs new file mode 100644 index 00000000000..dc7800cc977 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3352/Entity.cs @@ -0,0 +1,31 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH3352 +{ + public class Entity + { + public virtual Guid Id { get; set; } + public virtual string Name { get; set; } + public virtual EntityComponentMapped Parent { get; set; } + public virtual Component Component { get; set; } + } + + public class EntityNameMapped : Entity + { + } + + public class EntityParentMapped : Entity + { + } + + public class EntityComponentMapped : Entity + { + } + + public class Component + { + public string Field { get; set; } + + public EntityNameMapped Entity { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH3352/FetchFromNotMappedBaseClassFixture.cs b/src/NHibernate.Test/NHSpecificTest/GH3352/FetchFromNotMappedBaseClassFixture.cs new file mode 100644 index 00000000000..5b66027c963 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3352/FetchFromNotMappedBaseClassFixture.cs @@ -0,0 +1,163 @@ +using System.Linq; +using NHibernate.Cfg.MappingSchema; +using NHibernate.Linq; +using NHibernate.Mapping.ByCode; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH3352 +{ + [TestFixture] + public class FetchFromNotMappedBaseClassFixture : TestCaseMappingByCode + { + protected override HbmMapping GetMappings() + { + var mapper = new ModelMapper(); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Property(x => x.Name, m => m.Lazy(true)); + }); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.ManyToOne(x => x.Parent, m => m.ForeignKey("none")); + }); + mapper.Class(rc => + { + rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb)); + rc.Component(x => x.Component); + }); + mapper.Component(rc => + { + rc.Property(x => x.Field); + rc.ManyToOne(x => x.Entity, m => m.ForeignKey("none")); + rc.Lazy(true); + }); + return mapper.CompileMappingForAllExplicitlyAddedEntities(); + } + + protected override void OnSetUp() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + var np = new EntityComponentMapped { Component = new Component { Field = "x" } }; + session.Save(np); + var e = new EntityParentMapped { Parent = np }; + session.Save(e); + var nameMapped = new EntityNameMapped { Name = "lazy" }; + session.Save(nameMapped); + np.Component.Entity = nameMapped; + + transaction.Commit(); + } + + protected override void OnTearDown() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + + [Test] + public void CanFetchLazyComponentFromNotMappedBaseClass() + { + using var session = OpenSession(); + var list = session.Query().Fetch(x => x.Component).ToList(); + + Assert.That(list, Has.Count.EqualTo(1)); + var result = list[0]; + Assert.That(NHibernateUtil.IsPropertyInitialized(result, nameof(result.Component))); + Assert.That(result.Component.Field, Is.EqualTo("x")); + } + + [Test] + public void CanFetchLazyComponentThenEntityFromNotMappedBaseClass() + { + using var session = OpenSession(); + var list = session.Query() + .Fetch(x => x.Component) + .ThenFetch(x => x.Entity) + .ThenFetch(x => x.Name) + .ToList(); + + Assert.That(list, Has.Count.EqualTo(1)); + var result = list[0]; + Assert.That(NHibernateUtil.IsPropertyInitialized(result, nameof(result.Component))); + Assert.That(result.Component.Field, Is.EqualTo("x")); + Assert.That(result.Component.Entity, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(result.Component.Entity), Is.True); + Assert.That(NHibernateUtil.IsPropertyInitialized(result.Component.Entity, nameof(result.Name)), Is.True); + Assert.That(result.Component.Entity.Name, Is.EqualTo("lazy")); + } + + [Test] + public void CanFetchLazyPropertyFromNotMappedBaseClass() + { + using var session = OpenSession(); + var list = session.Query().Fetch(x => x.Name).ToList(); + + Assert.That(list, Has.Count.EqualTo(1)); + var result = list[0]; + Assert.That(NHibernateUtil.IsPropertyInitialized(result, nameof(result.Name))); + Assert.That(result.Name, Is.EqualTo("lazy")); + } + + [Test] + public void CanThenFetchLazyComponentFromNotMappedBaseClass() + { + using var session = OpenSession(); + var list = session.Query().Fetch(x => x.Parent).ThenFetch(x => x.Component).ToList(); + + Assert.That(list, Has.Count.EqualTo(1)); + var result = list[0].Parent; + Assert.That(NHibernateUtil.IsInitialized(result), Is.True); + Assert.That(NHibernateUtil.IsPropertyInitialized(result, nameof(result.Component))); + Assert.That(result.Component.Field, Is.EqualTo("x")); + } + + [KnownBug("GH-3356")] + [Test(Description = "GH-3356" )] + public void FetchAfterSelect() + { + using var log = new SqlLogSpy(); + + using var s = OpenSession(); + var list = s.Query() + .Select(x => x.Parent) + .Fetch(x => x.Component) + .ThenFetch(x => x.Entity) + .ThenFetch(x => x.Name) + .ToList(); + Assert.That(list, Has.Count.EqualTo(1)); + var result = list[0]; + Assert.That(NHibernateUtil.IsPropertyInitialized(result, nameof(result.Component))); + Assert.That(result.Component.Field, Is.EqualTo("x")); + Assert.That(result.Component.Entity, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(result.Component.Entity), Is.True); + Assert.That(NHibernateUtil.IsPropertyInitialized(result.Component.Entity, nameof(result.Name)), Is.True); + Assert.That(result.Component.Entity.Name, Is.EqualTo("lazy")); + } + + [Test] + public void CanFetchEntityFromNotMappedBaseClass() + { + using var session = OpenSession(); + var list = session.Query().Fetch(x => x.Parent).ToList(); + + Assert.That(list, Has.Count.EqualTo(1)); + Assert.That(list[0].Parent, Is.Not.Null); + Assert.That(NHibernateUtil.IsInitialized(list[0].Parent)); + } + + [Test] + public void FetchNotMappedAssociationThrows() + { + using var session = OpenSession(); + var query = session.Query().Fetch(x => x.Parent); + + Assert.Throws(() => query.ToList()); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH3403OneToOne/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH3403OneToOne/Entity.cs new file mode 100644 index 00000000000..0bde7420b92 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3403OneToOne/Entity.cs @@ -0,0 +1,17 @@ +using System; + +namespace NHibernate.Test.NHSpecificTest.GH3403OneToOne +{ + public class Entity1 + { + public virtual Guid Id { get; set; } + + public virtual Entity2 Child { get; set; } + } + public class Entity2 + { + public virtual Guid Id { get; set; } + + public virtual Entity1 Parent { get; set; } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH3403OneToOne/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH3403OneToOne/Fixture.cs new file mode 100644 index 00000000000..08f965eef1d --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3403OneToOne/Fixture.cs @@ -0,0 +1,63 @@ +using System; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.GH3403OneToOne +{ + [TestFixture] + public class Fixture : BugTestCase + { + private Guid _id; + + protected override void OnSetUp() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + var entity = new Entity1 + { + Child = new Entity2() + }; + + entity.Child.Parent = entity; + + session.Save(entity); + transaction.Commit(); + _id = entity.Id; + } + + protected override void OnTearDown() + { + using var session = OpenSession(); + using var transaction = session.BeginTransaction(); + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + + transaction.Commit(); + } + + [Test] + public void OrphanDeleteForDetachedOneToOne() + { + Guid childId; + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + var entity = session.Get(_id); + childId = entity.Child.Id; + session.Evict(entity.Child); + entity.Child = null; + + session.Flush(); + transaction.Commit(); + } + + using (var session = OpenSession()) + { + var entity = session.Get(_id); + Assert.That(entity, Is.Not.Null); + Assert.That(entity.Child, Is.Null, "Unexpected child on parent"); + + var child = session.Get(childId); + Assert.That(child , Is.Null, "Child is still in database"); + } + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/GH3403OneToOne/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH3403OneToOne/Mappings.hbm.xml new file mode 100644 index 00000000000..175d587c0ce --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/GH3403OneToOne/Mappings.hbm.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + Parent + + + + + diff --git a/src/NHibernate.Test/NHSpecificTest/NH750/Device.cs b/src/NHibernate.Test/NHSpecificTest/NH750/Device.cs index 7a77e8b2e04..b4496828089 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH750/Device.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH750/Device.cs @@ -6,6 +6,11 @@ namespace NHibernate.Test.NHSpecificTest.NH750 { public class Device { + private int _id; + private Device _template; + private IList _drives = new List(); + private IList _drivesNotIgnored = new List(); + public Device() : base() { } @@ -16,9 +21,13 @@ public Device(string manifacturer) _manifacturer = manifacturer; } - private int _id; + public virtual Device Template + { + get => _template; + set => _template = value; + } - public int Id + public virtual int Id { get { return _id; } set { _id = value; } @@ -26,18 +35,23 @@ public int Id private string _manifacturer; - public string Manifacturer + public virtual string Manifacturer { get { return _manifacturer; } set { _manifacturer = value; } } - private IList _drives = new List(); - public IList Drives + public virtual IList Drives { get { return _drives; } set { _drives = value; } } + + public virtual IList DrivesNotIgnored + { + get => _drivesNotIgnored; + set => _drivesNotIgnored = value; + } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH750/Drive.cs b/src/NHibernate.Test/NHSpecificTest/NH750/Drive.cs index b449dfb27ea..0e473bdf875 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH750/Drive.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH750/Drive.cs @@ -16,7 +16,7 @@ public Drive(string classFullName) private int _id; - public int Id + public virtual int Id { get { return _id; } set { _id = value; } @@ -24,7 +24,7 @@ public int Id private string _classFullName; - public string ClassFullName + public virtual string ClassFullName { get { return _classFullName; } set { _classFullName = value; } @@ -44,4 +44,4 @@ public override int GetHashCode() return _classFullName.GetHashCode(); } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH750/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH750/Fixture.cs deleted file mode 100644 index b766cd8b87e..00000000000 --- a/src/NHibernate.Test/NHSpecificTest/NH750/Fixture.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System; -using NHibernate.Cfg; -using NUnit.Framework; - -namespace NHibernate.Test.NHSpecificTest.NH750 -{ - [TestFixture] - public class Fixture : BugTestCase - { - protected override void OnTearDown() - { - using (ISession s = Sfi.OpenSession()) - { - s.Delete("from Device"); - s.Delete("from Drive"); - s.Flush(); - } - } - - protected override void Configure(Configuration configuration) - { - configuration.SetProperty(Cfg.Environment.UseSecondLevelCache, "false"); - base.Configure(configuration); - } - - [Test] - public void DeviceOfDrive() - { - int[] dvSavedId = new int[2]; - Drive dr1 = new Drive("Drive 1"); - Drive dr2 = new Drive("Drive 2"); - Drive dr3 = new Drive("Drive 3"); - Device dv1 = new Device("Device 1"); - Device dv2 = new Device("Device 2"); - using (ISession s = Sfi.OpenSession()) - { - s.Save(dr1); - s.Save(dr2); - s.Save(dr3); - dvSavedId[0] = (int) s.Save(dv1); - dvSavedId[1] = (int) s.Save(dv2); - s.Flush(); - } - - dv1.Drives.Add(dr1); - dv1.Drives.Add(dr2); - dv2.Drives.Add(dr1); - dv2.Drives.Add(dr3); - using (ISession s = Sfi.OpenSession()) - { - dvSavedId[0] = (int) s.Save(dv1); - dvSavedId[1] = (int) s.Save(dv2); - s.Flush(); - } - dv1 = null; - dv2 = null; - using (ISession s = Sfi.OpenSession()) - { - s.Delete(dr3); - s.Flush(); - dv1 = (Device) s.Load(typeof(Device), dvSavedId[0]); - dv2 = (Device) s.Load(typeof(Device), dvSavedId[1]); - } - Assert.AreEqual(2, dv1.Drives.Count); - // Verify one is missing - Assert.AreEqual(1, dv2.Drives.Count); - // Verify dv1 unchanged - Assert.IsTrue(dv1.Drives.Contains(dr1)); - Assert.IsTrue(dv1.Drives.Contains(dr2)); - - // Verify dv2 - Assert.IsTrue(dv2.Drives.Contains(dr1)); - Assert.IsFalse(dv2.Drives.Contains(dr3)); - - //Make sure that flush didn't touch not-found="ignore" records for not modified collection - using (var s = Sfi.OpenSession()) - using (var t = s.BeginTransaction()) - { - dv2 = s.Get(dv2.Id); - s.Flush(); - t.Commit(); - } - - VerifyResult(expectedInCollection: 1, expectedInDb: 2, msg: "not modified collection"); - - //Many-to-many clears collection and recreates it so not-found ignore records are lost - using (var s = Sfi.OpenSession()) - using (var t = s.BeginTransaction()) - { - dv2 = s.Get(dv2.Id); - dv2.Drives.Add(dr2); - t.Commit(); - } - - VerifyResult(2, 2, msg: "modified collection"); - - void VerifyResult(int expectedInCollection, int expectedInDb, string msg) - { - using (var s = Sfi.OpenSession()) - { - var realCound = Convert.ToInt32( - s.CreateSQLQuery("select count(*) from DriveOfDevice where DeviceId = :id ") - .SetParameter("id", dv2.Id) - .UniqueResult()); - dv2 = s.Get(dv2.Id); - - Assert.That(dv2.Drives.Count, Is.EqualTo(expectedInCollection), msg); - Assert.That(realCound, Is.EqualTo(expectedInDb), msg); - } - } - } - } -} diff --git a/src/NHibernate.Test/NHSpecificTest/NH750/ManyToManyFixture.cs b/src/NHibernate.Test/NHSpecificTest/NH750/ManyToManyFixture.cs new file mode 100644 index 00000000000..dd923c1a1eb --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH750/ManyToManyFixture.cs @@ -0,0 +1,144 @@ +using System; +using NHibernate.Criterion; +using NHibernate.Transform; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH750 +{ + [TestFixture(0)] + [TestFixture(1)] + [TestFixture(2)] + public class ManyToManyFixture : BugTestCase + { + private int id2; + private readonly int _drivesCount; + private int _withTemplateId; + private int ValidDrivesCount => _drivesCount; + + public ManyToManyFixture(int drivesCount) + { + _drivesCount = drivesCount; + } + + protected override void OnSetUp() + { + Drive dr1 = new Drive("Drive 1"); + Drive dr2 = new Drive("Drive 2"); + Drive dr3 = new Drive("Drive 3"); + Device dv1 = new Device("Device 1"); + Device dv2 = new Device("Device 2"); + var withTemplate = new Device("Device With Device 2 template") { Template = dv2 }; + using var s = Sfi.OpenSession(); + using var t = s.BeginTransaction(); + s.Save(dr1); + s.Save(dr2); + s.Save(dr3); + AddDrive(dv1, dr2); + AddDrive(dv1, dr1); + AddDrive(dv2, dr3); + AddDrive(dv2, dr1); + + s.Save(dv1); + id2 = (int) s.Save(dv2); + _withTemplateId = (int)s.Save(withTemplate); + t.Commit(); + } + + private void AddDrive(Device dv, Drive drive) + { + if(dv.DrivesNotIgnored.Count >= _drivesCount) + return; + dv.DrivesNotIgnored.Add(drive); + } + + protected override void OnTearDown() + { + using var s = Sfi.OpenSession(); + using var t = s.BeginTransaction(); + + s.CreateSQLQuery("delete from DriveOfDevice").ExecuteUpdate(); + s.Delete("from Device"); + s.Delete("from Drive"); + t.Commit(); + } + + [Test] + public void QueryOverFetch() + { + using var log = new SqlLogSpy(); + using var s = OpenSession(); + var dv2 = s.QueryOver() + .Fetch(SelectMode.Fetch, x => x.DrivesNotIgnored) + .Where(Restrictions.IdEq(id2)) + .TransformUsing(Transformers.DistinctRootEntity) + .SingleOrDefault(); + + Assert.That(NHibernateUtil.IsInitialized(dv2.DrivesNotIgnored), Is.True); + Assert.That(dv2.DrivesNotIgnored, Has.Count.EqualTo(ValidDrivesCount).And.None.Null); + Assert.That(log.Appender.GetEvents().Length, Is.EqualTo(1)); + } + + [Test] + public void QueryOverFetch2() + { + using var log = new SqlLogSpy(); + using var s = OpenSession(); + var withTemplate = s.QueryOver() + .Fetch(SelectMode.Fetch, x => x.Template, x => x.Template.DrivesNotIgnored) + .Where(Restrictions.IdEq(_withTemplateId)) + .TransformUsing(Transformers.DistinctRootEntity) + .SingleOrDefault(); + + Assert.That(NHibernateUtil.IsInitialized(withTemplate.Template), Is.True); + Assert.That(NHibernateUtil.IsInitialized(withTemplate.Template.DrivesNotIgnored), Is.True); + Assert.That(withTemplate.Template.DrivesNotIgnored, Has.Count.EqualTo(ValidDrivesCount).And.None.Null); + Assert.That(log.Appender.GetEvents().Length, Is.EqualTo(1)); + } + + [Test] + public void HqlFetch() + { + using var log = new SqlLogSpy(); + using var s = OpenSession(); + var dv2 = s.CreateQuery("from Device d left join fetch d.DrivesNotIgnored where d.id = :id") + .SetResultTransformer(Transformers.DistinctRootEntity) + .SetParameter("id", id2) + .UniqueResult(); + + Assert.That(NHibernateUtil.IsInitialized(dv2.DrivesNotIgnored), Is.True); + Assert.That(dv2.DrivesNotIgnored, Has.Count.EqualTo(ValidDrivesCount).And.None.Null); + Assert.That(log.Appender.GetEvents().Length, Is.EqualTo(1)); + } + + [Test] + public void HqlFetch2() + { + using var log = new SqlLogSpy(); + using var s = OpenSession(); + var withTemplate = s.CreateQuery("from Device t left join fetch t.Template d left join fetch d.DrivesNotIgnored where d.id = :id") + .SetResultTransformer(Transformers.DistinctRootEntity) + .SetParameter("id", id2) + .UniqueResult(); + + Assert.That(NHibernateUtil.IsInitialized(withTemplate.Template), Is.True); + Assert.That(NHibernateUtil.IsInitialized(withTemplate.Template.DrivesNotIgnored), Is.True); + Assert.That(withTemplate.Template.DrivesNotIgnored, Has.Count.EqualTo(ValidDrivesCount).And.None.Null); + Assert.That(log.Appender.GetEvents().Length, Is.EqualTo(1)); + } + + [Test] + public void LazyLoad() + { + using var log = new SqlLogSpy(); + using var s = OpenSession(); + + var dv2 = s.Get(id2); + + NHibernateUtil.Initialize(dv2.DrivesNotIgnored); + Assert.That(NHibernateUtil.IsInitialized(dv2.DrivesNotIgnored), Is.True); + Assert.That(dv2.DrivesNotIgnored, Has.Count.EqualTo(ValidDrivesCount).And.None.Null); + // First query for Device, second for Drives collection + Assert.That(log.Appender.GetEvents().Length, Is.EqualTo(2)); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH750/ManyToManyNotFoundIgnoreFixture.cs b/src/NHibernate.Test/NHSpecificTest/NH750/ManyToManyNotFoundIgnoreFixture.cs new file mode 100644 index 00000000000..3762342e932 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH750/ManyToManyNotFoundIgnoreFixture.cs @@ -0,0 +1,208 @@ +using System; +using NHibernate.Criterion; +using NHibernate.Transform; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH750 +{ + [TestFixture(0)] + [TestFixture(1)] + [TestFixture(2)] + public class ManyToManyNotFoundIgnoreFixture : BugTestCase + { + private int id1; + private int id2; + private int _drive2Id; + private int _withTemplateId; + private readonly int _drivesCount; + private int ValidDrivesCount => _drivesCount == 0 ? 0 : _drivesCount - 1; + + public ManyToManyNotFoundIgnoreFixture(int drivesCount) + { + _drivesCount = drivesCount; + } + + protected override void OnSetUp() + { + Drive dr1 = new Drive("Drive 1"); + Drive dr2 = new Drive("Drive 2"); + Drive dr3 = new Drive("Drive 3"); + Device dv1 = new Device("Device 1"); + Device dv2 = new Device("Device 2"); + var withTemplate = new Device("Device With Device 2 template") { Template = dv2 }; + + using var s = Sfi.OpenSession(); + using var t = s.BeginTransaction(); + s.Save(dr1); + _drive2Id = (int)s.Save(dr2); + s.Save(dr3); + AddDrive(dv1, dr2); + AddDrive(dv1, dr1); + AddDrive(dv2, dr3); + AddDrive(dv2, dr1); + + id1 = (int) s.Save(dv1); + id2 = (int) s.Save(dv2); + _withTemplateId = (int)s.Save(withTemplate); + s.Flush(); + + s.Clear(); + s.Delete(dr3); + t.Commit(); + } + + private void AddDrive(Device dv, Drive drive) + { + if(dv.Drives.Count >= _drivesCount) + return; + dv.Drives.Add(drive); + } + + protected override void OnTearDown() + { + using var s = Sfi.OpenSession(); + using var t = s.BeginTransaction(); + + s.CreateSQLQuery("delete from DriveOfDevice").ExecuteUpdate(); + s.Delete("from Device"); + s.Delete("from Drive"); + t.Commit(); + } + + [Test] + public void DeviceOfDrive() + { + Device dv1; + Device dv2; + using (ISession s = Sfi.OpenSession()) + { + dv1 = (Device) s.Load(typeof(Device), id1); + dv2 = (Device) s.Load(typeof(Device), id2); + NHibernateUtil.Initialize(dv1.Drives); + NHibernateUtil.Initialize(dv2.Drives); + } + + Assert.That(dv1.Drives, Has.Count.EqualTo(_drivesCount).And.None.Null); + // Verify one is missing + Assert.That(dv2.Drives, Has.Count.EqualTo(ValidDrivesCount).And.None.Null); + + //Make sure that flush didn't touch not-found="ignore" records for not modified collection + using (var s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) + { + dv2 = s.Get(dv2.Id); + s.Flush(); + t.Commit(); + } + + VerifyResult(expectedInCollection: ValidDrivesCount, expectedInDb: _drivesCount, msg: "not modified collection"); + + // Many-to-many clears collection and recreates it so not-found ignore records are lost + // Note: It's not the case when no valid records are present, so loaded Drives collection is empty + // Just skip this check in this case: + if (_drivesCount < 2) + return; + + using (var s = Sfi.OpenSession()) + using (var t = s.BeginTransaction()) + { + dv2 = s.Get(dv2.Id); + dv2.Drives.Add(s.Load(_drive2Id)); + t.Commit(); + } + + VerifyResult(_drivesCount, _drivesCount, msg: "modified collection"); + + void VerifyResult(int expectedInCollection, int expectedInDb, string msg) + { + using (var s = Sfi.OpenSession()) + { + var realCound = Convert.ToInt32( + s.CreateSQLQuery("select count(*) from DriveOfDevice where DeviceId = :id ") + .SetParameter("id", dv2.Id) + .UniqueResult()); + dv2 = s.Get(dv2.Id); + + Assert.That(dv2.Drives.Count, Is.EqualTo(expectedInCollection), msg); + Assert.That(realCound, Is.EqualTo(expectedInDb), msg); + } + } + } + + [Test] + public void QueryOverFetch() + { + using var log = new SqlLogSpy(); + using var s = OpenSession(); + var dv2 = s.QueryOver() + .Fetch(SelectMode.Fetch, x => x.Drives) + .Where(Restrictions.IdEq(id2)) + .TransformUsing(Transformers.DistinctRootEntity) + .SingleOrDefault(); + + Assert.That(NHibernateUtil.IsInitialized(dv2.Drives), Is.True); + Assert.That(dv2.Drives, Has.Count.EqualTo(ValidDrivesCount).And.None.Null); + Assert.That(log.Appender.GetEvents().Length, Is.EqualTo(1)); + } + + [Test] + public void QueryOverFetch2() + { + using var log = new SqlLogSpy(); + using var s = OpenSession(); + var withTemplate = s.QueryOver() + .Fetch(SelectMode.Fetch, x => x.Template, x => x.Template.Drives) + .Where(Restrictions.IdEq(_withTemplateId)) + .TransformUsing(Transformers.DistinctRootEntity) + .SingleOrDefault(); + + Assert.That(NHibernateUtil.IsInitialized(withTemplate.Template), Is.True); + Assert.That(NHibernateUtil.IsInitialized(withTemplate.Template.Drives), Is.True); + Assert.That(withTemplate.Template.Drives, Has.Count.EqualTo(ValidDrivesCount).And.None.Null); + Assert.That(log.Appender.GetEvents().Length, Is.EqualTo(1)); + } + + [Test] + public void HqlFetch() + { + using var log = new SqlLogSpy(); + using var s = OpenSession(); + var dv2 = s.CreateQuery("from Device d left join fetch d.Drives where d.id = :id") + .SetResultTransformer(Transformers.DistinctRootEntity) + .SetParameter("id", id2) + .UniqueResult(); + + Assert.That(NHibernateUtil.IsInitialized(dv2.Drives), Is.True); + Assert.That(dv2.Drives, Has.Count.EqualTo(ValidDrivesCount).And.None.Null); + Assert.That(log.Appender.GetEvents().Length, Is.EqualTo(1)); + } + + [Test] + public void HqlFetch2() + { + using var log = new SqlLogSpy(); + using var s = OpenSession(); + var withTemplate = s.CreateQuery("from Device t left join fetch t.Template d left join fetch d.Drives where d.id = :id") + .SetResultTransformer(Transformers.DistinctRootEntity) + .SetParameter("id", id2) + .UniqueResult(); + + Assert.That(NHibernateUtil.IsInitialized(withTemplate.Template), Is.True); + Assert.That(NHibernateUtil.IsInitialized(withTemplate.Template.Drives), Is.True); + Assert.That(withTemplate.Template.Drives, Has.Count.EqualTo(ValidDrivesCount).And.None.Null); + Assert.That(log.Appender.GetEvents().Length, Is.EqualTo(1)); + } + + [Test] + public void LazyLoad() + { + using var s = OpenSession(); + + var dv2 = s.Get(id2); + using var log = new SqlLogSpy(); + + Assert.That(dv2.Drives, Has.Count.EqualTo(ValidDrivesCount).And.None.Null); + Assert.That(log.Appender.GetEvents().Length, Is.EqualTo(1)); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH750/ManyToManyThrowsForNotFoundFixture.cs b/src/NHibernate.Test/NHSpecificTest/NH750/ManyToManyThrowsForNotFoundFixture.cs new file mode 100644 index 00000000000..27ce2d3aaaf --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH750/ManyToManyThrowsForNotFoundFixture.cs @@ -0,0 +1,98 @@ +using System.Linq; +using NHibernate.Criterion; +using NHibernate.Linq; +using NHibernate.Transform; +using NUnit.Framework; + +namespace NHibernate.Test.NHSpecificTest.NH750 +{ + [TestFixture] + public class ManyToManyThrowsForNotFoundFixture : BugTestCase + { + private int _id; + private int _withTemplateId; + + protected override void OnSetUp() + { + using var s = Sfi.OpenSession(); + using var t = s.BeginTransaction(); + Device dv = new Device("Device"); + Drive dr = new Drive("Drive"); + var withTemplate = new Device("Device With Device 2 template") { Template = dv }; + s.Save(dr); + dv.DrivesNotIgnored.Add(dr); + + _id = (int) s.Save(dv); + _withTemplateId = (int)s.Save(withTemplate); + s.Flush(); + + s.Clear(); + s.Delete(dr); + t.Commit(); + } + + protected override void OnTearDown() + { + using (var s = OpenSession()) + using (var t = s.BeginTransaction()) + { + s.Delete("from Device"); + s.Delete("from Drive"); + t.Commit(); + } + } + + [Test] + public void LazyLoad() + { + using var s = OpenSession(); + var device = s.Get(_id); + Assert.Throws(() => NHibernateUtil.Initialize(device.DrivesNotIgnored)); + } + + [Test] + public void QueryOverFetch() + { + using var s = OpenSession(); + var queryOver = s.QueryOver() + .Fetch(SelectMode.Fetch, x => x.DrivesNotIgnored) + .Where(Restrictions.IdEq(_id)) + .TransformUsing(Transformers.DistinctRootEntity); + Assert.Throws(() => queryOver.SingleOrDefault()); + } + + [Test] + public void QueryOverFetch2() + { + using var s = OpenSession(); + var queryOver = s.QueryOver() + .Fetch(SelectMode.Fetch, x=> x.Template, x => x.Template.DrivesNotIgnored) + .Where(Restrictions.IdEq(_withTemplateId)) + .TransformUsing(Transformers.DistinctRootEntity); + Assert.Throws(() => queryOver.SingleOrDefault()); + } + + [Test] + public void LinqFetch() + { + using var s = OpenSession(); + var query = s.Query() + + .Fetch(x => x.DrivesNotIgnored) + .Where(x => x.Id == _id); + Assert.Throws(() => NHibernateUtil.Initialize(query.SingleOrDefault())); + } + + [Test] + public void LinqFetch2() + { + using var s = OpenSession(); + var query = s.Query() + + .Fetch(x => x.Template) + .ThenFetchMany(x => x.DrivesNotIgnored) + .Where(x => x.Id == _withTemplateId); + Assert.Throws(() => NHibernateUtil.Initialize(query.SingleOrDefault())); + } + } +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH750/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/NH750/Mappings.hbm.xml index 6a100d7926d..e2b3987d60e 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH750/Mappings.hbm.xml +++ b/src/NHibernate.Test/NHSpecificTest/NH750/Mappings.hbm.xml @@ -2,17 +2,21 @@ + default-access="field.camelcase-underscore"> - + + + + + + diff --git a/src/NHibernate.Test/NHibernate.Test.csproj b/src/NHibernate.Test/NHibernate.Test.csproj index 663fdc58461..d722ec6b8a9 100644 --- a/src/NHibernate.Test/NHibernate.Test.csproj +++ b/src/NHibernate.Test/NHibernate.Test.csproj @@ -34,6 +34,9 @@ Always + + PreserveNewest + @@ -46,6 +49,9 @@ + + Hql\Parser\CaseInsensitiveStringStream.cs + UtilityTest\AsyncReaderWriterLock.cs @@ -56,10 +62,10 @@ - + - + diff --git a/src/NHibernate.Test/PropertyRef/ManyToManyPropertyRefFixture.cs b/src/NHibernate.Test/PropertyRef/ManyToManyPropertyRefFixture.cs index 054cba44957..9ecb5dd4377 100644 --- a/src/NHibernate.Test/PropertyRef/ManyToManyPropertyRefFixture.cs +++ b/src/NHibernate.Test/PropertyRef/ManyToManyPropertyRefFixture.cs @@ -1,16 +1,18 @@ +using System.Linq; using NHibernate.Criterion; +using NHibernate.Linq; using NUnit.Framework; namespace NHibernate.Test.PropertyRef { - [TestFixture] + [TestFixture(Description = "NH-2180 (GH-1214)")] public class ManyToManyPropertyRefFixture : TestCase { protected override string[] Mappings => new[] { "PropertyRef.ManyToManyWithPropertyRef.hbm.xml" }; protected override string MappingsAssembly => "NHibernate.Test"; - private object _manyAId; + private long _manyAId; protected override void OnSetUp() { @@ -23,7 +25,7 @@ protected override void OnSetUp() var manyB2 = new ManyB { Number = 8, Value = "a value of b2" }; var manyB3 = new ManyB { Number = 12, Value = "a value of b3" }; - _manyAId = session.Save(manyA); + _manyAId = (long) session.Save(manyA); session.Save(manyB1); session.Save(manyB2); session.Save(manyB3); @@ -133,5 +135,20 @@ bei NHibernate.Type.EntityType.LoadByUniqueKey(String entityName, String uniqueK Assert.That(loadedManyA.ManyBs, Has.Count.EqualTo(3).And.None.Null); } + + [Test] + public void LinqFetch() + { + using (var session = OpenSession()) + { + var manyA = session + .Query() + .Where(a => a.Id == _manyAId) + .FetchMany(a => a.ManyBs) + .ToList() + .First(); + Assert.That(manyA.ManyBs, Has.Count.EqualTo(3).And.None.Null); + } + } } } diff --git a/src/NHibernate/Async/Collection/Generic/PersistentGenericBag.cs b/src/NHibernate/Async/Collection/Generic/PersistentGenericBag.cs index 3b229ac54b8..687700d44fd 100644 --- a/src/NHibernate/Async/Collection/Generic/PersistentGenericBag.cs +++ b/src/NHibernate/Async/Collection/Generic/PersistentGenericBag.cs @@ -109,9 +109,13 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist var array = (object[]) disassembled; var size = array.Length; BeforeInitialize(persister, size); + + var elementType = persister.ElementType; + await (BeforeAssembleAsync(elementType, array, cancellationToken)).ConfigureAwait(false); + for (var i = 0; i < size; i++) { - var element = await (persister.ElementType.AssembleAsync(array[i], Session, owner, cancellationToken)).ConfigureAwait(false); + var element = await (elementType.AssembleAsync(array[i], Session, owner, cancellationToken)).ConfigureAwait(false); if (element != null) { _gbag.Add((T) element); @@ -119,6 +123,18 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist } } + private async Task BeforeAssembleAsync(IType elementType, object[] array, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + if (Session.PersistenceContext.BatchFetchQueue.QueryCacheQueue != null) + return; + + for (int i = 0; i < array.Length; i++) + { + await (elementType.BeforeAssembleAsync(array[i], Session, cancellationToken)).ConfigureAwait(false); + } + } + public override Task NeedsInsertingAsync(object entry, int i, IType elemType, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) diff --git a/src/NHibernate/Async/Collection/Generic/PersistentGenericIdentifierBag.cs b/src/NHibernate/Async/Collection/Generic/PersistentGenericIdentifierBag.cs index 6e3ea111d01..cb5c7bcc41b 100644 --- a/src/NHibernate/Async/Collection/Generic/PersistentGenericIdentifierBag.cs +++ b/src/NHibernate/Async/Collection/Generic/PersistentGenericIdentifierBag.cs @@ -44,10 +44,28 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist object[] array = (object[])disassembled; int size = array.Length; BeforeInitialize(persister, size); + + var identifierType = persister.IdentifierType; + var elementType = persister.ElementType; + await (BeforeAssembleAsync(identifierType, elementType, array, cancellationToken)).ConfigureAwait(false); + for (int i = 0; i < size; i += 2) { - _identifiers[i / 2] = await (persister.IdentifierType.AssembleAsync(array[i], Session, owner, cancellationToken)).ConfigureAwait(false); - _values.Add((T) await (persister.ElementType.AssembleAsync(array[i + 1], Session, owner, cancellationToken)).ConfigureAwait(false)); + _identifiers[i / 2] = await (identifierType.AssembleAsync(array[i], Session, owner, cancellationToken)).ConfigureAwait(false); + _values.Add((T) await (elementType.AssembleAsync(array[i + 1], Session, owner, cancellationToken)).ConfigureAwait(false)); + } + } + + private async Task BeforeAssembleAsync(IType identifierType, IType elementType, object[] array, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + if (Session.PersistenceContext.BatchFetchQueue.QueryCacheQueue != null) + return; + + for (int i = 0; i < array.Length; i += 2) + { + await (identifierType.BeforeAssembleAsync(array[i], Session, cancellationToken)).ConfigureAwait(false); + await (elementType.BeforeAssembleAsync(array[i + 1], Session, cancellationToken)).ConfigureAwait(false); } } diff --git a/src/NHibernate/Async/Collection/Generic/PersistentGenericList.cs b/src/NHibernate/Async/Collection/Generic/PersistentGenericList.cs index 1417c98e361..626fe414d3c 100644 --- a/src/NHibernate/Async/Collection/Generic/PersistentGenericList.cs +++ b/src/NHibernate/Async/Collection/Generic/PersistentGenericList.cs @@ -98,13 +98,29 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist object[] array = (object[])disassembled; int size = array.Length; BeforeInitialize(persister, size); + + var elementType = persister.ElementType; + await (BeforeAssembleAsync(elementType, array, cancellationToken)).ConfigureAwait(false); + for (int i = 0; i < size; i++) { - var element = await (persister.ElementType.AssembleAsync(array[i], Session, owner, cancellationToken)).ConfigureAwait(false); + var element = await (elementType.AssembleAsync(array[i], Session, owner, cancellationToken)).ConfigureAwait(false); WrappedList.Add((T) (element ?? DefaultForType)); } } + private async Task BeforeAssembleAsync(IType elementType, object[] array, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + if (Session.PersistenceContext.BatchFetchQueue.QueryCacheQueue != null) + return; + + for (int i = 0; i < array.Length; i++) + { + await (elementType.BeforeAssembleAsync(array[i], Session, cancellationToken)).ConfigureAwait(false); + } + } + public override async Task DisassembleAsync(ICollectionPersister persister, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/NHibernate/Async/Collection/Generic/PersistentGenericMap.cs b/src/NHibernate/Async/Collection/Generic/PersistentGenericMap.cs index e574577a111..18526b4d09d 100644 --- a/src/NHibernate/Async/Collection/Generic/PersistentGenericMap.cs +++ b/src/NHibernate/Async/Collection/Generic/PersistentGenericMap.cs @@ -94,10 +94,28 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist object[] array = (object[])disassembled; int size = array.Length; BeforeInitialize(persister, size); + + var indexType = persister.IndexType; + var elementType = persister.ElementType; + await (BeforeAssembleAsync(indexType, elementType, array, cancellationToken)).ConfigureAwait(false); + for (int i = 0; i < size; i += 2) { - WrappedMap[(TKey)await (persister.IndexType.AssembleAsync(array[i], Session, owner, cancellationToken)).ConfigureAwait(false)] = - (TValue)await (persister.ElementType.AssembleAsync(array[i + 1], Session, owner, cancellationToken)).ConfigureAwait(false); + WrappedMap[(TKey)await (indexType.AssembleAsync(array[i], Session, owner, cancellationToken)).ConfigureAwait(false)] = + (TValue)await (elementType.AssembleAsync(array[i + 1], Session, owner, cancellationToken)).ConfigureAwait(false); + } + } + + private async Task BeforeAssembleAsync(IType indexType, IType elementType, object[] array, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + if (Session.PersistenceContext.BatchFetchQueue.QueryCacheQueue != null) + return; + + for (int i = 0; i < array.Length; i += 2) + { + await (indexType.BeforeAssembleAsync(array[i], Session, cancellationToken)).ConfigureAwait(false); + await (elementType.BeforeAssembleAsync(array[i + 1], Session, cancellationToken)).ConfigureAwait(false); } } diff --git a/src/NHibernate/Async/Collection/Generic/PersistentGenericSet.cs b/src/NHibernate/Async/Collection/Generic/PersistentGenericSet.cs index 91f2f86f626..848e918ef5e 100644 --- a/src/NHibernate/Async/Collection/Generic/PersistentGenericSet.cs +++ b/src/NHibernate/Async/Collection/Generic/PersistentGenericSet.cs @@ -84,9 +84,13 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist var array = (object[])disassembled; int size = array.Length; BeforeInitialize(persister, size); + + var elementType = persister.ElementType; + await (BeforeAssembleAsync(elementType, array, cancellationToken)).ConfigureAwait(false); + for (int i = 0; i < size; i++) { - var element = await (persister.ElementType.AssembleAsync(array[i], Session, owner, cancellationToken)).ConfigureAwait(false); + var element = await (elementType.AssembleAsync(array[i], Session, owner, cancellationToken)).ConfigureAwait(false); if (element != null) { WrappedSet.Add((T) element); @@ -95,6 +99,18 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist SetInitialized(); } + private async Task BeforeAssembleAsync(IType elementType, object[] array, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + if (Session.PersistenceContext.BatchFetchQueue.QueryCacheQueue != null) + return; + + for (int i = 0; i < array.Length; i++) + { + await (elementType.BeforeAssembleAsync(array[i], Session, cancellationToken)).ConfigureAwait(false); + } + } + public override async Task ReadFromAsync(DbDataReader rs, ICollectionPersister role, ICollectionAliases descriptor, object owner, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/NHibernate/Async/Collection/PersistentArrayHolder.cs b/src/NHibernate/Async/Collection/PersistentArrayHolder.cs index 448309f2f9e..6fb58819c4d 100644 --- a/src/NHibernate/Async/Collection/PersistentArrayHolder.cs +++ b/src/NHibernate/Async/Collection/PersistentArrayHolder.cs @@ -94,9 +94,24 @@ public override async Task InitializeFromCacheAsync(ICollectionPersister persist array = System.Array.CreateInstance(persister.ElementClass, cached.Length); + var elementType = persister.ElementType; + await (BeforeAssembleAsync(elementType, cached, cancellationToken)).ConfigureAwait(false); + + for (int i = 0; i < cached.Length; i++) + { + array.SetValue(await (elementType.AssembleAsync(cached[i], Session, owner, cancellationToken)).ConfigureAwait(false), i); + } + } + + private async Task BeforeAssembleAsync(IType elementType, object[] cached, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + if (Session.PersistenceContext.BatchFetchQueue.QueryCacheQueue != null) + return; + for (int i = 0; i < cached.Length; i++) { - array.SetValue(await (persister.ElementType.AssembleAsync(cached[i], Session, owner, cancellationToken)).ConfigureAwait(false), i); + await (elementType.BeforeAssembleAsync(cached[i], Session, cancellationToken)).ConfigureAwait(false); } } diff --git a/src/NHibernate/Async/Engine/Cascade.cs b/src/NHibernate/Async/Engine/Cascade.cs index c0165e5cf66..46877a36ae1 100644 --- a/src/NHibernate/Async/Engine/Cascade.cs +++ b/src/NHibernate/Async/Engine/Cascade.cs @@ -151,7 +151,7 @@ private async Task CascadePropertyAsync(object parent, object child, IType type, if (loadedValue != null) { - await (eventSource.DeleteAsync(entry.Persister.EntityName, loadedValue, false, null, cancellationToken)).ConfigureAwait(false); + await (eventSource.DeleteAsync(((EntityType) type).GetAssociatedEntityName(), loadedValue, false, null, cancellationToken)).ConfigureAwait(false); } } } diff --git a/src/NHibernate/Cfg/Configuration.cs b/src/NHibernate/Cfg/Configuration.cs index 6b52f6008ba..13e3ab51bb6 100644 --- a/src/NHibernate/Cfg/Configuration.cs +++ b/src/NHibernate/Cfg/Configuration.cs @@ -98,7 +98,6 @@ public Configuration(SerializationInfo info, StreamingContext context) FilterDefinitions = GetSerialedObject>(info, "filterDefinitions"); Imports = GetSerialedObject>(info, "imports"); interceptor = GetSerialedObject(info, "interceptor"); - mapping = GetSerialedObject(info, "mapping"); NamedQueries = GetSerialedObject>(info, "namedQueries"); NamedSQLQueries = GetSerialedObject>(info, "namedSqlQueries"); namingStrategy = GetSerialedObject(info, "namingStrategy"); @@ -124,7 +123,6 @@ public void GetObjectData(SerializationInfo info, StreamingContext context) { ConfigureProxyFactoryFactory(); SecondPassCompile(); - Validate(); info.AddValue("entityNotFoundDelegate", EntityNotFoundDelegate); @@ -139,7 +137,6 @@ public void GetObjectData(SerializationInfo info, StreamingContext context) info.AddValue("filterDefinitions", FilterDefinitions); info.AddValue("imports", Imports); info.AddValue("interceptor", interceptor); - info.AddValue("mapping", mapping); info.AddValue("namedQueries", NamedQueries); info.AddValue("namedSqlQueries", NamedSQLQueries); info.AddValue("namingStrategy", namingStrategy); @@ -246,10 +243,10 @@ private class StaticDialectMappingWrapper : IMapping { private readonly IMapping _mapping; - public StaticDialectMappingWrapper(IMapping mapping) + public StaticDialectMappingWrapper(IMapping mapping, Dialect.Dialect dialect) { _mapping = mapping; - Dialect = mapping.Dialect; + Dialect = dialect; } public IType GetIdentifierType(string className) @@ -275,23 +272,25 @@ public bool HasNonIdentifierPropertyNamedId(string className) public Dialect.Dialect Dialect { get; } } - private IMapping mapping; - protected Configuration(SettingsFactory settingsFactory) { - InitBlock(); this.settingsFactory = settingsFactory; Reset(); } - private void InitBlock() + // Since v5.5 + [Obsolete("Use BuildMapping(Dialect.Dialect) instead.")] + public virtual IMapping BuildMapping() { - mapping = BuildMapping(); + return new Mapping(this); } - public virtual IMapping BuildMapping() + public virtual IMapping BuildMapping(Dialect.Dialect dialect) { - return new Mapping(this); +#pragma warning disable CS0618 + var mapping = BuildMapping(); +#pragma warning restore CS0618 + return new StaticDialectMappingWrapper(mapping, dialect); } /// @@ -548,9 +547,8 @@ private void AddValidatedDocument(NamedXmlDocument doc) public void AddDeserializedMapping(HbmMapping mappingDocument, string documentFileName) { if (mappingDocument == null) - { - throw new ArgumentNullException("mappingDocument"); - } + throw new ArgumentNullException(nameof(mappingDocument)); + try { var dialect = new Lazy(() => Dialect.Dialect.GetDialect(properties)); @@ -745,9 +743,8 @@ public Configuration AddResource(string path, Assembly assembly) public Configuration AddResources(IEnumerable paths, Assembly assembly) { if (paths == null) - { - throw new ArgumentNullException("paths"); - } + throw new ArgumentNullException(nameof(paths)); + foreach (var path in paths) { AddResource(path, assembly); @@ -934,6 +931,7 @@ public static bool IncludeAction(SchemaAction actionsSource, SchemaAction includ /// public string[] GenerateSchemaCreationScript(Dialect.Dialect dialect) { + var mapping = BuildMapping(dialect); SecondPassCompile(); var defaultCatalog = GetQuotedDefaultCatalog(dialect); @@ -1001,11 +999,11 @@ public string[] GenerateSchemaCreationScript(Dialect.Dialect dialect) return script.ToArray(); } - private void Validate() + private void Validate(IMapping mapping) { - ValidateEntities(); + ValidateEntities(mapping); - ValidateCollections(); + ValidateCollections(mapping); ValidateFilterDefs(); } @@ -1059,7 +1057,7 @@ private void ValidateFilterDefs() } } - private void ValidateCollections() + private void ValidateCollections(IMapping mapping) { foreach (var col in collections.Values) { @@ -1067,7 +1065,7 @@ private void ValidateCollections() } } - private void ValidateEntities() + private void ValidateEntities(IMapping mapping) { bool validateProxy = PropertiesHelper.GetBoolean(Environment.UseProxyValidator, properties, true); HashSet allProxyErrors = null; @@ -1281,8 +1279,7 @@ protected virtual void ConfigureProxyFactoryFactory() //http://nhibernate.jira.com/browse/NH-975 var ipff = Environment.BytecodeProvider as IInjectableProxyFactoryFactory; - string pffClassName; - properties.TryGetValue(Environment.ProxyFactoryFactoryClass, out pffClassName); + properties.TryGetValue(Environment.ProxyFactoryFactoryClass, out var pffClassName); if (ipff != null && !string.IsNullOrEmpty(pffClassName)) { ipff.SetProxyFactoryFactory(pffClassName); @@ -1299,33 +1296,21 @@ protected virtual void ConfigureProxyFactoryFactory() /// An instance. public ISessionFactory BuildSessionFactory() { - var dynamicDialectMapping = mapping; // Use a mapping which does store the dialect instead of instantiating a new one // at each access. The dialect does not change while building a session factory. // It furthermore allows some hack on NHibernate.Spatial side to go on working, // See nhibernate/NHibernate.Spatial#104 - mapping = new StaticDialectMappingWrapper(mapping); - try - { - ConfigureProxyFactoryFactory(); - SecondPassCompile(); - Validate(); - Environment.VerifyProperties(properties); - Settings settings = BuildSettings(); + var settings = BuildSettings(); + var mapping = BuildMapping(settings.Dialect); + ConfigureProxyFactoryFactory(); + SecondPassCompile(); + Validate(mapping); + Environment.VerifyProperties(properties); - // Ok, don't need schemas anymore, so free them - Schemas = null; + // Ok, don't need schemas anymore, so free them + Schemas = null; - return new SessionFactoryImpl( - this, - mapping, - settings, - GetInitializedEventListeners()); - } - finally - { - mapping = dynamicDialectMapping; - } + return new SessionFactoryImpl(this, mapping, settings, GetInitializedEventListeners()); } /// @@ -2358,6 +2343,7 @@ private static T[] AppendListeners(T[] existing, T[] listenersToAdd) /// public string[] GenerateSchemaUpdateScript(Dialect.Dialect dialect, IDatabaseMetadata databaseMetadata) { + var mapping = BuildMapping(dialect); SecondPassCompile(); var defaultCatalog = GetQuotedDefaultCatalog(dialect); @@ -2438,6 +2424,7 @@ public string[] GenerateSchemaUpdateScript(Dialect.Dialect dialect, IDatabaseMet public void ValidateSchema(Dialect.Dialect dialect, IDatabaseMetadata databaseMetadata) { + var mapping = BuildMapping(dialect); SecondPassCompile(); var defaultCatalog = GetQuotedDefaultCatalog(dialect); diff --git a/src/NHibernate/Cfg/XmlHbmBinding/NamedSQLQueryBinder.cs b/src/NHibernate/Cfg/XmlHbmBinding/NamedSQLQueryBinder.cs index e9bb3f89081..d802bb534c2 100644 --- a/src/NHibernate/Cfg/XmlHbmBinding/NamedSQLQueryBinder.cs +++ b/src/NHibernate/Cfg/XmlHbmBinding/NamedSQLQueryBinder.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using NHibernate.Cfg.MappingSchema; using NHibernate.Engine; using NHibernate.Util; @@ -33,7 +34,11 @@ public void AddSqlQuery(HbmSqlQuery querySchema) ? querySchema.cachemode.ToCacheMode() : null; - var parameterTypes = new LinkedHashMap(); + var parameterTypes = + querySchema.Items.EmptyIfNull().OfType() + .Where(x => !string.IsNullOrEmpty(x.type)) + .ToDictionary(x => x.name, x => x.type); + var synchronizedTables = GetSynchronizedTables(querySchema); NamedSQLQueryDefinition namedQuery; diff --git a/src/NHibernate/Collection/Generic/PersistentGenericBag.cs b/src/NHibernate/Collection/Generic/PersistentGenericBag.cs index 98c916717c3..74740304845 100644 --- a/src/NHibernate/Collection/Generic/PersistentGenericBag.cs +++ b/src/NHibernate/Collection/Generic/PersistentGenericBag.cs @@ -400,9 +400,13 @@ public override void InitializeFromCache(ICollectionPersister persister, object var array = (object[]) disassembled; var size = array.Length; BeforeInitialize(persister, size); + + var elementType = persister.ElementType; + BeforeAssemble(elementType, array); + for (var i = 0; i < size; i++) { - var element = persister.ElementType.Assemble(array[i], Session, owner); + var element = elementType.Assemble(array[i], Session, owner); if (element != null) { _gbag.Add((T) element); @@ -410,6 +414,17 @@ public override void InitializeFromCache(ICollectionPersister persister, object } } + private void BeforeAssemble(IType elementType, object[] array) + { + if (Session.PersistenceContext.BatchFetchQueue.QueryCacheQueue != null) + return; + + for (int i = 0; i < array.Length; i++) + { + elementType.BeforeAssemble(array[i], Session); + } + } + public override bool IsSnapshotEmpty(object snapshot) { return ((ICollection) snapshot).Count == 0; diff --git a/src/NHibernate/Collection/Generic/PersistentGenericIdentifierBag.cs b/src/NHibernate/Collection/Generic/PersistentGenericIdentifierBag.cs index 884d454b76e..f66980aa8b6 100644 --- a/src/NHibernate/Collection/Generic/PersistentGenericIdentifierBag.cs +++ b/src/NHibernate/Collection/Generic/PersistentGenericIdentifierBag.cs @@ -75,10 +75,27 @@ public override void InitializeFromCache(ICollectionPersister persister, object object[] array = (object[])disassembled; int size = array.Length; BeforeInitialize(persister, size); + + var identifierType = persister.IdentifierType; + var elementType = persister.ElementType; + BeforeAssemble(identifierType, elementType, array); + for (int i = 0; i < size; i += 2) { - _identifiers[i / 2] = persister.IdentifierType.Assemble(array[i], Session, owner); - _values.Add((T) persister.ElementType.Assemble(array[i + 1], Session, owner)); + _identifiers[i / 2] = identifierType.Assemble(array[i], Session, owner); + _values.Add((T) elementType.Assemble(array[i + 1], Session, owner)); + } + } + + private void BeforeAssemble(IType identifierType, IType elementType, object[] array) + { + if (Session.PersistenceContext.BatchFetchQueue.QueryCacheQueue != null) + return; + + for (int i = 0; i < array.Length; i += 2) + { + identifierType.BeforeAssemble(array[i], Session); + elementType.BeforeAssemble(array[i + 1], Session); } } diff --git a/src/NHibernate/Collection/Generic/PersistentGenericList.cs b/src/NHibernate/Collection/Generic/PersistentGenericList.cs index 6255616dd0e..c8756cf504b 100644 --- a/src/NHibernate/Collection/Generic/PersistentGenericList.cs +++ b/src/NHibernate/Collection/Generic/PersistentGenericList.cs @@ -160,13 +160,28 @@ public override void InitializeFromCache(ICollectionPersister persister, object object[] array = (object[])disassembled; int size = array.Length; BeforeInitialize(persister, size); + + var elementType = persister.ElementType; + BeforeAssemble(elementType, array); + for (int i = 0; i < size; i++) { - var element = persister.ElementType.Assemble(array[i], Session, owner); + var element = elementType.Assemble(array[i], Session, owner); WrappedList.Add((T) (element ?? DefaultForType)); } } + private void BeforeAssemble(IType elementType, object[] array) + { + if (Session.PersistenceContext.BatchFetchQueue.QueryCacheQueue != null) + return; + + for (int i = 0; i < array.Length; i++) + { + elementType.BeforeAssemble(array[i], Session); + } + } + public override object Disassemble(ICollectionPersister persister) { int length = WrappedList.Count; diff --git a/src/NHibernate/Collection/Generic/PersistentGenericMap.cs b/src/NHibernate/Collection/Generic/PersistentGenericMap.cs index 5fab797d793..da27836713b 100644 --- a/src/NHibernate/Collection/Generic/PersistentGenericMap.cs +++ b/src/NHibernate/Collection/Generic/PersistentGenericMap.cs @@ -163,10 +163,27 @@ public override void InitializeFromCache(ICollectionPersister persister, object object[] array = (object[])disassembled; int size = array.Length; BeforeInitialize(persister, size); + + var indexType = persister.IndexType; + var elementType = persister.ElementType; + BeforeAssemble(indexType, elementType, array); + for (int i = 0; i < size; i += 2) { - WrappedMap[(TKey)persister.IndexType.Assemble(array[i], Session, owner)] = - (TValue)persister.ElementType.Assemble(array[i + 1], Session, owner); + WrappedMap[(TKey)indexType.Assemble(array[i], Session, owner)] = + (TValue)elementType.Assemble(array[i + 1], Session, owner); + } + } + + private void BeforeAssemble(IType indexType, IType elementType, object[] array) + { + if (Session.PersistenceContext.BatchFetchQueue.QueryCacheQueue != null) + return; + + for (int i = 0; i < array.Length; i += 2) + { + indexType.BeforeAssemble(array[i], Session); + elementType.BeforeAssemble(array[i + 1], Session); } } @@ -246,8 +263,9 @@ public void Add(TKey key, TValue value) { if (key == null) { - throw new ArgumentNullException("key"); + throw new ArgumentNullException(nameof(key)); } + if (PutQueueEnabled) { var found = TryReadElementByKey(key, out _, out _); diff --git a/src/NHibernate/Collection/Generic/PersistentGenericSet.cs b/src/NHibernate/Collection/Generic/PersistentGenericSet.cs index 4e138bb22fa..9a686724e41 100644 --- a/src/NHibernate/Collection/Generic/PersistentGenericSet.cs +++ b/src/NHibernate/Collection/Generic/PersistentGenericSet.cs @@ -154,9 +154,13 @@ public override void InitializeFromCache(ICollectionPersister persister, object var array = (object[])disassembled; int size = array.Length; BeforeInitialize(persister, size); + + var elementType = persister.ElementType; + BeforeAssemble(elementType, array); + for (int i = 0; i < size; i++) { - var element = persister.ElementType.Assemble(array[i], Session, owner); + var element = elementType.Assemble(array[i], Session, owner); if (element != null) { WrappedSet.Add((T) element); @@ -165,6 +169,17 @@ public override void InitializeFromCache(ICollectionPersister persister, object SetInitialized(); } + private void BeforeAssemble(IType elementType, object[] array) + { + if (Session.PersistenceContext.BatchFetchQueue.QueryCacheQueue != null) + return; + + for (int i = 0; i < array.Length; i++) + { + elementType.BeforeAssemble(array[i], Session); + } + } + public override bool Empty { get { return WrappedSet.Count == 0; } diff --git a/src/NHibernate/Collection/PersistentArrayHolder.cs b/src/NHibernate/Collection/PersistentArrayHolder.cs index 242d7dac51a..aaa8ab2625d 100644 --- a/src/NHibernate/Collection/PersistentArrayHolder.cs +++ b/src/NHibernate/Collection/PersistentArrayHolder.cs @@ -196,9 +196,23 @@ public override void InitializeFromCache(ICollectionPersister persister, object array = System.Array.CreateInstance(persister.ElementClass, cached.Length); + var elementType = persister.ElementType; + BeforeAssemble(elementType, cached); + + for (int i = 0; i < cached.Length; i++) + { + array.SetValue(elementType.Assemble(cached[i], Session, owner), i); + } + } + + private void BeforeAssemble(IType elementType, object[] cached) + { + if (Session.PersistenceContext.BatchFetchQueue.QueryCacheQueue != null) + return; + for (int i = 0; i < cached.Length; i++) { - array.SetValue(persister.ElementType.Assemble(cached[i], Session, owner), i); + elementType.BeforeAssemble(cached[i], Session); } } diff --git a/src/NHibernate/Engine/Cascade.cs b/src/NHibernate/Engine/Cascade.cs index 2c2c2987a3b..35098331337 100644 --- a/src/NHibernate/Engine/Cascade.cs +++ b/src/NHibernate/Engine/Cascade.cs @@ -203,7 +203,7 @@ private void CascadeProperty(object parent, object child, IType type, CascadeSty if (loadedValue != null) { - eventSource.Delete(entry.Persister.EntityName, loadedValue, false, null); + eventSource.Delete(((EntityType) type).GetAssociatedEntityName(), loadedValue, false, null); } } } diff --git a/src/NHibernate/Engine/JoinSequence.cs b/src/NHibernate/Engine/JoinSequence.cs index 83c8baf0aaa..7fcfc9eb86b 100644 --- a/src/NHibernate/Engine/JoinSequence.cs +++ b/src/NHibernate/Engine/JoinSequence.cs @@ -187,11 +187,11 @@ internal virtual JoinFragment ToJoinFragment( { Join join = joins[i]; - withClauses[i] = GetWithClause(enabledFilters, ref withClauseFragment, join, last); + withClauses[i] = GetWithClause(enabledFilters, join, last, ref withClauseFragment); last = join.Joinable; } - if (rootJoinable == null && !IsThetaStyle && TableGroupJoinHelper.ProcessAsTableGroupJoin(joins, withClauses, includeAllSubclassJoins, joinFragment, alias => IsIncluded(alias), factory)) + if (rootJoinable == null && !IsThetaStyle && TableGroupJoinHelper.ProcessAsTableGroupJoin(joins, withClauses, includeAllSubclassJoins, joinFragment, alias => IsIncluded(alias))) { return joinFragment; } @@ -223,7 +223,7 @@ internal virtual JoinFragment ToJoinFragment( return joinFragment; } - private SqlString GetWithClause(IDictionary enabledFilters, ref SqlString withClauseFragment, Join join, IJoinable last) + private SqlString GetWithClause(IDictionary enabledFilters, Join join, IJoinable last, ref SqlString withClauseFragment) { string on = join.AssociationType.GetOnCondition(join.Alias, factory, enabledFilters); var withConditions = new List(); diff --git a/src/NHibernate/Engine/Query/QueryPlanCache.cs b/src/NHibernate/Engine/Query/QueryPlanCache.cs index c2b8cd9e1f6..33b260ed39e 100644 --- a/src/NHibernate/Engine/Query/QueryPlanCache.cs +++ b/src/NHibernate/Engine/Query/QueryPlanCache.cs @@ -5,6 +5,7 @@ using NHibernate.Engine.Query.Sql; using NHibernate.Hql; using NHibernate.Linq; +using NHibernate.Type; using NHibernate.Util; namespace NHibernate.Engine.Query @@ -40,6 +41,11 @@ public QueryPlanCache(ISessionFactoryImplementor factory) } public ParameterMetadata GetSQLParameterMetadata(string query) + { + return GetSQLParameterMetadata(query, CollectionHelper.EmptyDictionary()); + } + + public ParameterMetadata GetSQLParameterMetadata(string query, IDictionary parameterTypes) { var metadata = (ParameterMetadata)sqlParamMetadataCache[query]; if (metadata == null) @@ -49,7 +55,7 @@ public ParameterMetadata GetSQLParameterMetadata(string query) // retrieval for a native-sql query depends on all of the return // types having been set, which might not be the case up-front when // param metadata would be most useful - metadata = BuildNativeSQLParameterMetadata(query); + metadata = BuildNativeSQLParameterMetadata(query, parameterTypes); sqlParamMetadataCache.Put(query, metadata); } return metadata; @@ -170,14 +176,14 @@ public NativeSQLQueryPlan GetNativeSQLQueryPlan(NativeSQLQuerySpecification spec return plan; } - private ParameterMetadata BuildNativeSQLParameterMetadata(string sqlString) + private ParameterMetadata BuildNativeSQLParameterMetadata(string sqlString, + IDictionary parameterTypes) { ParamLocationRecognizer recognizer = ParamLocationRecognizer.ParseLocations(sqlString); var ordinalDescriptors = new OrdinalParameterDescriptor[recognizer.OrdinalParameterLocationList.Count]; - for (int i = 0; i < recognizer.OrdinalParameterLocationList.Count; i++) + for (int i = 0; i < ordinalDescriptors.Length; i++) { - int position = recognizer.OrdinalParameterLocationList[i]; ordinalDescriptors[i] = new OrdinalParameterDescriptor(i, null); } @@ -187,8 +193,14 @@ private ParameterMetadata BuildNativeSQLParameterMetadata(string sqlString) { string name = entry.Key; ParamLocationRecognizer.NamedParameterDescription description = entry.Value; + IType expectedType = null; + if (parameterTypes.TryGetValue(name, out var type) && !string.IsNullOrEmpty(type)) + { + expectedType = TypeFactory.HeuristicType(type); + } + namedParamDescriptorMap[name] = - new NamedParameterDescriptor(name, null, description.JpaStyle); + new NamedParameterDescriptor(name, expectedType, description.JpaStyle); } return new ParameterMetadata(ordinalDescriptors, namedParamDescriptorMap); diff --git a/src/NHibernate/Engine/TableGroupJoinHelper.cs b/src/NHibernate/Engine/TableGroupJoinHelper.cs index 12ad3be28a2..c47e74cfac3 100644 --- a/src/NHibernate/Engine/TableGroupJoinHelper.cs +++ b/src/NHibernate/Engine/TableGroupJoinHelper.cs @@ -4,6 +4,7 @@ using NHibernate.Persister.Collection; using NHibernate.Persister.Entity; using NHibernate.SqlCommand; +using NHibernate.Type; namespace NHibernate.Engine { @@ -16,7 +17,7 @@ namespace NHibernate.Engine // ) ON person0_.Id = individual1_.PersonID AND individual1_1_.Deleted = @p0 internal class TableGroupJoinHelper { - internal static bool ProcessAsTableGroupJoin(IReadOnlyList tableGroupJoinables, SqlString[] withClauseFragments, bool includeAllSubclassJoins, JoinFragment joinFragment, Func isSubclassIncluded, ISessionFactoryImplementor sessionFactoryImplementor) + internal static bool ProcessAsTableGroupJoin(IReadOnlyList tableGroupJoinables, SqlString[] withClauseFragments, bool includeAllSubclassJoins, JoinFragment joinFragment, Func isSubclassIncluded) { if (!NeedsTableGroupJoin(tableGroupJoinables, withClauseFragments, includeAllSubclassJoins, isSubclassIncluded)) return false; @@ -68,14 +69,17 @@ private static bool NeedsTableGroupJoin(IReadOnlyList joins, SqlString[] foreach (var join in joins) { - var entityPersister = GetEntityPersister(join.Joinable, out var isManyToMany); + var entityPersister = GetEntityPersister(join.Joinable, out var manyToManyType); + if (manyToManyType?.IsNullable == true) + return true; + if (entityPersister?.HasSubclassJoins(includeSubclasses && isSubclassIncluded(join.Alias)) != true) continue; if (hasWithClause) return true; - if (!isManyToMany // many-to-many keys are stored in separate table + if (manyToManyType == null // many-to-many keys are stored in separate table && entityPersister.ColumnsDependOnSubclassJoins(join.RHSColumns)) { return true; @@ -94,14 +98,14 @@ private static SqlString GetTableGroupJoinWithClause(SqlString[] withClauseFragm var isAssociationJoin = lhsColumns.Length > 0; if (isAssociationJoin) { - var entityPersister = GetEntityPersister(first.Joinable, out var isManyToMany); + var entityPersister = GetEntityPersister(first.Joinable, out var manyToManyType); string rhsAlias = first.Alias; string[] rhsColumns = first.RHSColumns; for (int j = 0; j < lhsColumns.Length; j++) { fromFragment.Add(lhsColumns[j]) .Add("=") - .Add((entityPersister == null || isManyToMany) // many-to-many keys are stored in separate table + .Add((entityPersister == null || manyToManyType != null) // many-to-many keys are stored in separate table ? rhsAlias : entityPersister.GenerateTableAliasForColumn(rhsAlias, rhsColumns[j])) .Add(".") @@ -116,15 +120,18 @@ private static SqlString GetTableGroupJoinWithClause(SqlString[] withClauseFragm return fromFragment.ToSqlString(); } - private static AbstractEntityPersister GetEntityPersister(IJoinable joinable, out bool isManyToMany) + private static AbstractEntityPersister GetEntityPersister(IJoinable joinable, out EntityType manyToManyType) { - isManyToMany = false; + manyToManyType = null; if (!joinable.IsCollection) return joinable as AbstractEntityPersister; var collection = (IQueryableCollection) joinable; - isManyToMany = collection.IsManyToMany; - return collection.ElementType.IsEntityType ? collection.ElementPersister as AbstractEntityPersister : null; + if (!collection.ElementType.IsEntityType) + return null; + if (collection.IsManyToMany) + manyToManyType = (EntityType) collection.ElementType; + return collection.ElementPersister as AbstractEntityPersister; } private static void AppendWithClause(SqlStringBuilder fromFragment, bool hasConditions, SqlString[] withClauseFragments) diff --git a/src/NHibernate/Hql/Ast/ANTLR/Hql.g b/src/NHibernate/Hql/Ast/ANTLR/Hql.g index 364c9142711..fa4d5467f72 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Hql.g +++ b/src/NHibernate/Hql/Ast/ANTLR/Hql.g @@ -695,10 +695,9 @@ constant path @init { -// TODO - need to clean up DotIdent - suspect that DotIdent2 supersedes the other one, but need to do the analysis -//HandleDotIdent2(); +HandleDotIdents(); } - : identifier ( DOT^ { WeakKeywords(); } identifier )* + : identifier ( DOT^ identifier )* ; // Wraps the IDENT token from the lexer, in order to provide diff --git a/src/NHibernate/Hql/Ast/ANTLR/HqlParser.cs b/src/NHibernate/Hql/Ast/ANTLR/HqlParser.cs index 6c05fc6c999..c59f2a99a03 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/HqlParser.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/HqlParser.cs @@ -112,6 +112,27 @@ protected override object RecoverFromMismatchedToken(IIntStream input, int ttype throw new MismatchedTokenException(ttype, input); } + public void HandleDotIdents() + { + int i = 2; + + while (input.LA(i) == DOT) + { + var next = input.LT(i + 1); + if (next != null) + next.Type = IDENT; + i += 2; + } + + if (input.LA(1) == IDENT || input.LA(2) != DOT) + return; + + if (IsPossibleId(input.LT(1))) + { + input.LT(1).Type = IDENT; + } + } + public void WeakKeywords() { int t = input.LA(1); @@ -147,8 +168,8 @@ public void WeakKeywords() // Case 2: The current token is after FROM and before '.'. if (t != IDENT && input.LA(-1) == FROM && ((input.LA(2) == DOT) || (input.LA(2) == IDENT) || (input.LA(2) == -1))) { - HqlToken hqlToken = input.LT(1) as HqlToken; - if (hqlToken != null && hqlToken.PossibleId) + var hqlToken = input.LT(1); + if (IsPossibleId(hqlToken)) { hqlToken.Type = IDENT; if (log.IsDebugEnabled()) @@ -192,8 +213,8 @@ public void WeakKeywords2() // Case 2: The current token is after FROM and before '.'. if (t != IDENT && input.LA(-1) == FROM && input.LA(2) == DOT) { - HqlToken hqlToken = (HqlToken)input.LT(1); - if (hqlToken.PossibleId) + var hqlToken = input.LT(1); + if (IsPossibleId(hqlToken)) { hqlToken.Type = IDENT; if (log.IsDebugEnabled()) @@ -279,10 +300,8 @@ public IASTNode NegateNode(IASTNode node) node.Type = BETWEEN; node.Text = "{not}" + node.Text; return node; // (NOT (NOT_BETWEEN a b) ) => (BETWEEN a b) - /* This can never happen because this rule will always eliminate the child NOT. - case NOT: - return x.getFirstChild(); // (NOT (NOT x) ) => (x) - */ + case NOT: + return node.GetChild(0); // (NOT (NOT x) ) => (x) default: IASTNode not = (IASTNode) TreeAdaptor.Create(NOT, "not"); not.AddChild(node); @@ -290,16 +309,8 @@ public IASTNode NegateNode(IASTNode node) } } - public IASTNode ProcessEqualityExpression(object o) + public IASTNode ProcessEqualityExpression(IASTNode x) { - IASTNode x = o as IASTNode; - - if (x == null) - { - log.Warn("processEqualityExpression() : No expression to process!"); - return null; - } - int type = x.Type; if (type == EQ || type == NE) { @@ -336,11 +347,11 @@ public void HandleDotIdent() if (input.LA(1) == DOT && input.LA(2) != IDENT) { // See if the second lookahed token can be an identifier. - HqlToken t = input.LT(2) as HqlToken; - if (t != null && t.PossibleId) + var t = input.LT(2); + if (IsPossibleId(t)) { // Set it! - input.LT(2).Type = IDENT; + t.Type = IDENT; if (log.IsDebugEnabled()) { log.Debug("handleDotIdent() : new LT(2) token - {0}", input.LT(1)); @@ -401,37 +412,38 @@ public IASTNode ProcessMemberOf(IToken n, IASTNode p, IASTNode root) public IASTNode HandleIdentifierError(IToken token, RecognitionException ex) { - if (token is HqlToken) + // ... and the token could be an identifier and the error is + // a mismatched token error ... + if (IsPossibleId(token) && (ex is MismatchedTokenException mte) + // ... and the expected token type was an identifier, then: + && mte.Expecting == IDENT) { - HqlToken hqlToken = (HqlToken)token; - - // ... and the token could be an identifer and the error is - // a mismatched token error ... - if (hqlToken.PossibleId && (ex is MismatchedTokenException)) - { - MismatchedTokenException mte = (MismatchedTokenException)ex; - - // ... and the expected token type was an identifier, then: - if (mte.Expecting == IDENT) - { - // Use the token as an identifier. - _parseErrorHandler.ReportWarning("Keyword '" - + token.Text - + "' is being interpreted as an identifier due to: " + mte.Message); + // Use the token as an identifier. + _parseErrorHandler.ReportWarning("Keyword '" + + token.Text + + "' is being interpreted as an identifier due to: " + mte.Message); - // Add the token to the AST. + // Add the token to the AST. - token.Type = WEIRD_IDENT; + token.Type = WEIRD_IDENT; - input.Consume(); - return (IASTNode) adaptor.Create(token); - } - } + input.Consume(); + return (IASTNode) adaptor.Create(token); } - + // Otherwise, handle the error normally. ReflectHelper.PreserveStackTrace(ex); throw ex; } + + /// + /// Indicates if the token could be an identifier. + /// + /// + public static bool IsPossibleId(IToken token) + { + var type = token.Type; + return type >= 0 && type < possibleIds.Length && possibleIds[type]; + } } } diff --git a/src/NHibernate/Hql/Ast/ANTLR/HqlToken.cs b/src/NHibernate/Hql/Ast/ANTLR/HqlToken.cs index 7c2449b50e7..12d1b634534 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/HqlToken.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/HqlToken.cs @@ -39,10 +39,9 @@ public HqlToken(IToken other) /// /// Indicates if the token could be an identifier. /// - public bool PossibleId - { - get { return HqlParser.possibleIds[Type]; } - } + // Since 5.5 + [Obsolete("Use HqlParser.IsPossibleId method instead.")] + public bool PossibleId => HqlParser.IsPossibleId(this); /// /// Returns the previous token type. @@ -62,7 +61,7 @@ public override string ToString() + Text + "\",<" + Type + "> previously: <" + PreviousType + ">,line=" + Line + ",col=" - + CharPositionInLine + ",possibleID=" + PossibleId + "]"; + + CharPositionInLine + ",possibleID=" + HqlParser.IsPossibleId(this) + "]"; } } } diff --git a/src/NHibernate/Hql/Ast/ANTLR/QueryTranslatorImpl.cs b/src/NHibernate/Hql/Ast/ANTLR/QueryTranslatorImpl.cs index 6bab2557751..a318e9be5b5 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/QueryTranslatorImpl.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/QueryTranslatorImpl.cs @@ -547,10 +547,6 @@ public IASTNode Parse() try { var ast = (IASTNode) parser.statement().Tree; - - var walker = new NodeTraverser(new ConstantConverter(_sfi)); - walker.TraverseDepthFirst(ast); - return ast; } finally @@ -558,53 +554,6 @@ public IASTNode Parse() parser.ParseErrorHandler.ThrowQueryException(); } } - - class ConstantConverter : IVisitationStrategy - { - private IASTNode _dotRoot; - private readonly ISessionFactoryImplementor _sfi; - - public ConstantConverter(ISessionFactoryImplementor sfi) - { - _sfi = sfi; - } - - public void Visit(IASTNode node) - { - if (_dotRoot != null) - { - // we are already processing a dot-structure - if (ASTUtil.IsSubtreeChild(_dotRoot, node)) - { - // ignore it... - return; - } - - // we are now at a new tree level - _dotRoot = null; - } - - if (_dotRoot == null && node.Type == HqlSqlWalker.DOT) - { - _dotRoot = node; - HandleDotStructure(_dotRoot); - } - } - - private void HandleDotStructure(IASTNode dotStructureRoot) - { - var expression = ASTUtil.GetPathText(dotStructureRoot); - - var constant = ReflectHelper.GetConstantValue(expression, _sfi); - - if (constant != null) - { - dotStructureRoot.ClearChildren(); - dotStructureRoot.Type = HqlSqlWalker.JAVA_CONSTANT; - dotStructureRoot.Text = expression; - } - } - } } internal class HqlSqlTranslator diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs index 4b0f127a2ca..686ab1d05ff 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs @@ -18,7 +18,7 @@ namespace NHibernate.Hql.Ast.ANTLR.Tree /// Ported by: Steve Strong /// [CLSCompliant(false)] - public class DotNode : FromReferenceNode + public class DotNode : FromReferenceNode, IExpectedTypeAwareNode { private static readonly INHibernateLogger Log = NHibernateLogger.For(typeof(DotNode)); @@ -72,6 +72,8 @@ public class DotNode : FromReferenceNode /// private JoinType _joinType = JoinType.InnerJoin; + private object _constantValue; + public DotNode(IToken token) : base(token) { } @@ -287,11 +289,14 @@ private IType GetDataType() return DataType; } - public void SetResolvedConstant(string text) + public void SetResolvedConstant(string text) => SetResolvedConstant(text, null); + + public void SetResolvedConstant(string text, object value) { _path = text; _dereferenceType = DerefJavaConstant; IsResolved = true; // Don't resolve the node again. + _constantValue = value; } private static QueryException BuildIllegalCollectionDereferenceException(string propertyName, IASTNode lhs) @@ -772,5 +777,24 @@ public void ResolveSelectExpression() lhs = (FromReferenceNode)lhs.GetChild(0); } } + + public IType ExpectedType + { + get => DataType; + set + { + if (Type != HqlSqlWalker.JAVA_CONSTANT) + return; + + DataType = value; + } + } + + public override SqlString RenderText(ISessionFactoryImplementor sessionFactory) + { + return Type == HqlSqlWalker.JAVA_CONSTANT + ? JavaConstantNode.ResolveToLiteralString(DataType, _constantValue, sessionFactory.Dialect) + : base.RenderText(sessionFactory); + } } } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElementFactory.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElementFactory.cs index e9a95769f1f..f6726bfa8b3 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElementFactory.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/FromElementFactory.cs @@ -424,7 +424,9 @@ private FromElement CreateManyToMany( // Add the second join, the one that ends in the destination table. JoinSequence joinSequence = CreateJoinSequence(roleAlias, joinType, implicitJoin); - joinSequence.AddJoin(sfh.GetElementAssociationType(_collectionType), tableAlias, joinType, secondJoinColumns); + // It's safe to always use inner join for many-to-many not-found ignore mapping as it's processed by table group join + var secondJoinType = type.IsNullable ? JoinType.InnerJoin : joinType; + joinSequence.AddJoin(sfh.GetElementAssociationType(_collectionType), tableAlias, secondJoinType, secondJoinColumns); elem = CreateJoin(associatedEntityName, tableAlias, joinSequence, type, false); elem.UseFromFragment = true; } diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/JavaConstantNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/JavaConstantNode.cs index 74e05746f5f..5c46638f8a1 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Tree/JavaConstantNode.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/JavaConstantNode.cs @@ -38,27 +38,30 @@ public ISessionFactoryImplementor SessionFactory set { _factory = value; } } - public override SqlString RenderText(ISessionFactoryImplementor sessionFactory) - { - ProcessText(); + public override SqlString RenderText(ISessionFactoryImplementor sessionFactory) + { + ProcessText(); IType type = _expectedType ?? _heuristicType; - return new SqlString(ResolveToLiteralString( type )); + return ResolveToLiteralString(type); } - private string ResolveToLiteralString(IType type) - { - try - { - ILiteralType literalType = (ILiteralType)type; - Dialect.Dialect dialect = _factory.Dialect; - return literalType.ObjectToSQLString(_constantValue, dialect); - } - catch (Exception t) - { - throw new QueryException(LiteralProcessor.ErrorCannotFormatLiteral + Text, t); - } - } + private SqlString ResolveToLiteralString(IType type) + { + return ResolveToLiteralString(type, _constantValue, _factory.Dialect); + } + + internal static SqlString ResolveToLiteralString(IType type, object constantValue, Dialect.Dialect dialect) + { + try + { + return new SqlString(((ILiteralType) type).ObjectToSQLString(constantValue, dialect)); + } + catch (Exception t) + { + throw new QueryException(LiteralProcessor.ErrorCannotFormatLiteral + constantValue, t); + } + } private void ProcessText() { diff --git a/src/NHibernate/Hql/Ast/ANTLR/Util/LiteralProcessor.cs b/src/NHibernate/Hql/Ast/ANTLR/Util/LiteralProcessor.cs index dd89906775b..0d82a990fbb 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Util/LiteralProcessor.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Util/LiteralProcessor.cs @@ -70,7 +70,7 @@ public void LookupConstant(DotNode node) } else { - Object value = ReflectHelper.GetConstantValue(text); + var value = ReflectHelper.GetConstantValue(text, _walker.SessionFactoryHelper.Factory); if (value == null) { throw new InvalidPathException("Invalid path: '" + text + "'"); @@ -149,6 +149,7 @@ public void ProcessConstant(SqlNode constant, bool resolveIdent) if (isIdent && queryable != null) { constant.Text = queryable.DiscriminatorSQLValue; + constant.DataType = queryable.DiscriminatorType; } // Otherwise, it's a literal. else @@ -275,74 +276,9 @@ private void SetConstantValue(DotNode node, string text, object value) node.ClearChildren(); // Chop off the rest of the tree. - if (value is string) - { - node.Type = HqlSqlWalker.QUOTED_String; - } - else if (value is char) - { - node.Type = HqlSqlWalker.QUOTED_String; - } - else if (value is byte) - { - node.Type = HqlSqlWalker.NUM_INT; - } - else if (value is short) - { - node.Type = HqlSqlWalker.NUM_INT; - } - else if (value is int) - { - node.Type = HqlSqlWalker.NUM_INT; - } - else if (value is long) - { - node.Type = HqlSqlWalker.NUM_LONG; - } - else if (value is double) - { - node.Type = HqlSqlWalker.NUM_DOUBLE; - } - else if (value is decimal) - { - node.Type = HqlSqlWalker.NUM_DECIMAL; - } - else if (value is float) - { - node.Type = HqlSqlWalker.NUM_FLOAT; - } - else - { - node.Type = HqlSqlWalker.CONSTANT; - } - - IType type; - try - { - type = TypeFactory.HeuristicType(value.GetType().Name); - } - catch (MappingException me) - { - throw new QueryException(me); - } - - if (type == null) - { - throw new QueryException(LiteralProcessor.ErrorCannotDetermineType + node.Text); - } - try - { - ILiteralType literalType = (ILiteralType)type; - NHibernate.Dialect.Dialect dialect = _walker.SessionFactoryHelper.Factory.Dialect; - node.Text = literalType.ObjectToSQLString(value, dialect); - } - catch (Exception e) - { - throw new QueryException(LiteralProcessor.ErrorCannotFormatLiteral + node.Text, e); - } - - node.DataType = type; - node.SetResolvedConstant(text); + node.Type = HqlSqlWalker.JAVA_CONSTANT; + node.DataType = TypeFactory.HeuristicType(value.GetType().Name); + node.SetResolvedConstant(text, value); } interface IDecimalFormatter diff --git a/src/NHibernate/Impl/AbstractSessionImpl.cs b/src/NHibernate/Impl/AbstractSessionImpl.cs index d2fd81b1b72..4fe9c9ae598 100644 --- a/src/NHibernate/Impl/AbstractSessionImpl.cs +++ b/src/NHibernate/Impl/AbstractSessionImpl.cs @@ -299,7 +299,7 @@ public virtual IQuery GetNamedSQLQuery(string name) throw new MappingException("Named SQL query not known: " + name); } var query = new SqlQueryImpl(nsqlqd, this, - _factory.QueryPlanCache.GetSQLParameterMetadata(nsqlqd.QueryString)); + _factory.QueryPlanCache.GetSQLParameterMetadata(nsqlqd.QueryString, nsqlqd.ParameterTypes)); query.SetComment("named native SQL query " + name); InitQuery(query, nsqlqd); return query; @@ -378,7 +378,7 @@ public virtual IQuery GetNamedQuery(string queryName) throw new MappingException("Named query not known: " + queryName); } query = new SqlQueryImpl(nsqlqd, this, - _factory.QueryPlanCache.GetSQLParameterMetadata(nsqlqd.QueryString)); + _factory.QueryPlanCache.GetSQLParameterMetadata(nsqlqd.QueryString, nsqlqd.ParameterTypes)); query.SetComment("named native SQL query " + queryName); nqd = nsqlqd; } diff --git a/src/NHibernate/Linq/IntermediateHqlTree.cs b/src/NHibernate/Linq/IntermediateHqlTree.cs index 17417319e41..4104ff4854d 100644 --- a/src/NHibernate/Linq/IntermediateHqlTree.cs +++ b/src/NHibernate/Linq/IntermediateHqlTree.cs @@ -127,6 +127,17 @@ public void AddFromClause(HqlTreeNode from) _root.NodesPreOrder.OfType().First().AddChild(from); } + internal HqlTreeNode GetFromNodeByAlias(string alias) => + _root.NodesPreOrder + .First(x => x.AstNode.Type == HqlSqlWalker.FROM).Children + .First(x => GetNodeAlias(x) == alias); + + private static string GetNodeAlias(HqlTreeNode fromNode) => + fromNode.Children + .Select(x => x.AstNode) + .First(x => x.Type == HqlSqlWalker.ALIAS) + .Text; + internal HqlRange GetFromRangeClause() { return _root.NodesPreOrder.OfType().First().Children.OfType().FirstOrDefault(); diff --git a/src/NHibernate/Linq/Visitors/JoinBuilder.cs b/src/NHibernate/Linq/Visitors/JoinBuilder.cs index a898389fae5..470b3174135 100644 --- a/src/NHibernate/Linq/Visitors/JoinBuilder.cs +++ b/src/NHibernate/Linq/Visitors/JoinBuilder.cs @@ -25,10 +25,7 @@ internal Joiner(QueryModel queryModel) { _nameGenerator = new NameGenerator(queryModel); _queryModel = queryModel; - AddJoinMethod = AddJoin; } - - internal System.Action AddJoinMethod { get; } public IEnumerable Joins { @@ -42,7 +39,7 @@ public Expression AddJoin(Expression expression, string key) if (!_joins.TryGetValue(key, out join)) { join = new NhJoinClause(_nameGenerator.GetNewName(), expression.Type, expression); - AddJoinMethod(_queryModel, join); + AddJoin(_queryModel, join); _joins.Add(key, join); } diff --git a/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetch.cs b/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetch.cs index 31401f7df81..69323f0a006 100644 --- a/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetch.cs +++ b/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetch.cs @@ -1,6 +1,8 @@ using System; using System.Linq; using NHibernate.Hql.Ast; +using NHibernate.Hql.Ast.ANTLR; +using NHibernate.Persister.Entity; using NHibernate.Type; using Remotion.Linq.EagerFetching; @@ -20,7 +22,7 @@ public void Process(FetchRequestBase resultOperator, QueryModelVisitor queryMode public void Process(FetchRequestBase resultOperator, QueryModelVisitor queryModelVisitor, IntermediateHqlTree tree, string sourceAlias) { - Process(resultOperator, queryModelVisitor, tree, null, sourceAlias); + Process(resultOperator, queryModelVisitor, tree, tree.GetFromNodeByAlias(sourceAlias), sourceAlias); } private void Process( @@ -54,23 +56,25 @@ private void Process( .GetClassMetadata(resultOperator.RelationMember.ReflectedType); if (metadata == null) { - var entityName = queryModelVisitor.VisitorParameters.SessionFactory.GetImplementors( - resultOperator.RelationMember.ReflectedType.FullName).FirstOrDefault(); - if (!string.IsNullOrEmpty(entityName)) + foreach (var entityName in queryModelVisitor.VisitorParameters.SessionFactory + .GetImplementors(resultOperator.RelationMember.ReflectedType.FullName)) { - metadata = queryModelVisitor.VisitorParameters.SessionFactory.GetClassMetadata(entityName); + if (queryModelVisitor.VisitorParameters.SessionFactory.GetClassMetadata(entityName) is IPropertyMapping propertyMapping + && propertyMapping.TryToType(resultOperator.RelationMember.Name, out propType)) + break; } } - - propType = metadata?.GetPropertyType(resultOperator.RelationMember.Name); + else + { + propType = metadata.GetPropertyType(resultOperator.RelationMember.Name); + } } - + if (propType != null && !propType.IsAssociationType) { if (currentNode == null) { - currentNode = tree.GetFromRangeClause() - ?? throw new InvalidOperationException($"Property {resultOperator.RelationMember.Name} cannot be fetched for this type of query."); + throw new InvalidOperationException($"Property {resultOperator.RelationMember.Name} cannot be fetched for this type of query."); } currentNode.AddChild(tree.TreeBuilder.Fetch()); @@ -81,12 +85,13 @@ private void Process( { if (componentType == null) { - componentType = propType as ComponentType; - if (componentType == null) + if (!propType.IsComponentType) { throw new InvalidOperationException( $"Property {innerFetch.RelationMember.Name} cannot be fetched from a non component type property {resultOperator.RelationMember.Name}."); } + + componentType = (ComponentType) propType; } var subTypeIndex = componentType.GetPropertyIndex(innerFetch.RelationMember.Name); diff --git a/src/NHibernate/Loader/AbstractEntityJoinWalker.cs b/src/NHibernate/Loader/AbstractEntityJoinWalker.cs index 4eb40d53210..606c55423d1 100644 --- a/src/NHibernate/Loader/AbstractEntityJoinWalker.cs +++ b/src/NHibernate/Loader/AbstractEntityJoinWalker.cs @@ -121,7 +121,7 @@ private void InitStatementString(OuterJoinableAssociation rootAssociation, SqlSt Suffixes = BasicLoader.GenerateSuffixes(joins + 1); var suffix = Suffixes[joins]; - selectClause = new SqlString(rootAssociation.GetSelectFragment(suffix, null, null) + SelectString(associations)); + selectClause = new SqlString(rootAssociation.GetSelectFragment(suffix, null) + SelectString(associations)); } JoinFragment ojf = MergeOuterJoins(associations); diff --git a/src/NHibernate/Loader/JoinWalker.cs b/src/NHibernate/Loader/JoinWalker.cs index e2650582718..bbe9d93a1bb 100644 --- a/src/NHibernate/Loader/JoinWalker.cs +++ b/src/NHibernate/Loader/JoinWalker.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Text.RegularExpressions; using NHibernate.Collection; using NHibernate.Engine; @@ -348,10 +347,7 @@ private void WalkCollectionTree(IQueryableCollection persister, string alias, st string[] aliasedLhsColumns = persister.GetElementColumnNames(alias); string[] lhsColumns = persister.ElementColumnNames; - // if the current depth is 0, the root thing being loaded is the - // many-to-many collection itself. Here, it is alright to use - // an inner join... - bool useInnerJoin = _depth == 0; + const bool useInnerJoin = false; var joinType = GetJoinType( @@ -365,6 +361,11 @@ private void WalkCollectionTree(IQueryableCollection persister, string alias, st _depth - 1, null); + // It's safe to always use inner join for many-to-many not-found ignore mapping as it's processed by table group join; + // otherwise we need left join for proper not-found exception handling + if (joinType == JoinType.LeftOuterJoin && ((EntityType) type).IsNullable) + joinType = JoinType.InnerJoin; + AddAssociationToJoinTreeIfNecessary( associationType, aliasedLhsColumns, @@ -884,41 +885,50 @@ protected JoinFragment MergeOuterJoins(IList associati JoinFragment outerjoin = Dialect.CreateOuterJoinFragment(); var sortedAssociations = GetSortedAssociations(associations); - OuterJoinableAssociation last = null; - foreach (OuterJoinableAssociation oj in sortedAssociations) + for (var index = 0; index < sortedAssociations.Count; index++) { - if (last != null && last.IsManyToManyWith(oj)) + OuterJoinableAssociation oj = sortedAssociations[index]; + if (oj.IsCollection && oj.Joinable is IQueryableCollection qc && qc.IsManyToMany && index < sortedAssociations.Count - 1) { - oj.AddManyToManyJoin(outerjoin, (IQueryableCollection) last.Joinable); + var entityAssociation = sortedAssociations[index + 1]; + var f = qc.GetManyToManyFilterFragment(entityAssociation.RHSAlias, enabledFilters); + if (oj.IsManyToManyWith(entityAssociation) + && TableGroupJoinHelper.ProcessAsTableGroupJoin( + new[] {oj, entityAssociation}, + new[] {oj.On, entityAssociation.On, string.IsNullOrEmpty(f) ? SqlString.Empty : new SqlString(f)}, + true, + outerjoin, + _ => true)) + { + index++; + continue; + } } - else + + // NH Different behavior : NH1179 and NH1293 + // Apply filters for entity joins and Many-To-One associations + SqlString filter = null; + var enabledFiltersForJoin = oj.ForceFilter ? enabledFilters : enabledFiltersForManyToOne; + if (oj.ForceFilter || enabledFiltersForJoin.Count > 0) { - // NH Different behavior : NH1179 and NH1293 - // Apply filters for entity joins and Many-To-One associations - SqlString filter = null; - var enabledFiltersForJoin = oj.ForceFilter ? enabledFilters : enabledFiltersForManyToOne; - if (oj.ForceFilter || enabledFiltersForJoin.Count > 0) + string manyToOneFilterFragment = oj.Joinable.FilterFragment(oj.RHSAlias, enabledFiltersForJoin); + bool joinClauseDoesNotContainsFilterAlready = + oj.On?.IndexOfCaseInsensitive(manyToOneFilterFragment) == -1; + if (joinClauseDoesNotContainsFilterAlready) { - string manyToOneFilterFragment = oj.Joinable.FilterFragment(oj.RHSAlias, enabledFiltersForJoin); - bool joinClauseDoesNotContainsFilterAlready = - oj.On?.IndexOfCaseInsensitive(manyToOneFilterFragment) == -1; - if (joinClauseDoesNotContainsFilterAlready) - { - filter = new SqlString(manyToOneFilterFragment); - } + filter = new SqlString(manyToOneFilterFragment); } + } - if (TableGroupJoinHelper.ProcessAsTableGroupJoin(new[] {oj}, new[] {oj.On, filter}, true, outerjoin, alias => true, factory)) - continue; + if (TableGroupJoinHelper.ProcessAsTableGroupJoin(new[] {oj}, new[] {oj.On, filter}, true, outerjoin, _ => true)) + continue; - oj.AddJoins(outerjoin); + oj.AddJoins(outerjoin); - // Ensure that the join condition is added to the join, not the where clause. - // Adding the condition to the where clause causes left joins to become inner joins. - if (SqlStringHelper.IsNotEmpty(filter)) - outerjoin.AddFromFragmentString(filter); - } - last = oj; + // Ensure that the join condition is added to the join, not the where clause. + // Adding the condition to the where clause causes left joins to become inner joins. + if (SqlStringHelper.IsNotEmpty(filter)) + outerjoin.AddFromFragmentString(filter); } return outerjoin; @@ -1212,7 +1222,6 @@ public string SelectString(IList associations) for (int i = 0; i < associations.Count; i++) { OuterJoinableAssociation join = associations[i]; - OuterJoinableAssociation next = (i == associations.Count - 1) ? null : associations[i + 1]; IJoinable joinable = join.Joinable; string entitySuffix = (suffixes == null || entityAliasCount >= suffixes.Length) ? null : suffixes[entityAliasCount]; @@ -1221,7 +1230,7 @@ public string SelectString(IList associations) ? null : collectionSuffixes[collectionAliasCount]; - string selectFragment = join.GetSelectFragment(entitySuffix, collectionSuffix, next); + string selectFragment = join.GetSelectFragment(entitySuffix, collectionSuffix); if (!string.IsNullOrWhiteSpace(selectFragment)) { @@ -1243,7 +1252,7 @@ public string SelectString(IList associations) [Obsolete("This method has no more usages and will be removed in a future version")] protected static string GetSelectFragment(OuterJoinableAssociation join, string entitySuffix, string collectionSuffix, OuterJoinableAssociation next = null) { - return join.GetSelectFragment(entitySuffix, collectionSuffix, next); + return join.GetSelectFragment(entitySuffix, collectionSuffix); } protected interface IJoinQueueEntry diff --git a/src/NHibernate/Loader/OuterJoinableAssociation.cs b/src/NHibernate/Loader/OuterJoinableAssociation.cs index 47a2e01e613..74180f89408 100644 --- a/src/NHibernate/Loader/OuterJoinableAssociation.cs +++ b/src/NHibernate/Loader/OuterJoinableAssociation.cs @@ -169,6 +169,8 @@ public bool IsManyToManyWith(OuterJoinableAssociation other) return false; } + //Since 5.4 + [Obsolete("This method is not used anymore and will be removed in a next major version")] public void AddManyToManyJoin(JoinFragment outerjoin, IQueryableCollection collection) { string manyToManyFilter = collection.GetManyToManyFilterFragment(rhsAlias, enabledFilters); @@ -204,7 +206,7 @@ internal bool ShouldFetchCollectionPersister() throw new ArgumentOutOfRangeException(nameof(SelectMode), SelectMode.ToString()); } - internal string GetSelectFragment(string entitySuffix, string collectionSuffix, OuterJoinableAssociation next) + internal string GetSelectFragment(string entitySuffix, string collectionSuffix) { switch (SelectMode) { @@ -212,8 +214,8 @@ internal string GetSelectFragment(string entitySuffix, string collectionSuffix, case SelectMode.Fetch: #pragma warning disable 618 return Joinable.SelectFragment( - next?.Joinable, - next?.RHSAlias, + null, + null, RHSAlias, entitySuffix, collectionSuffix, @@ -224,8 +226,8 @@ internal string GetSelectFragment(string entitySuffix, string collectionSuffix, #pragma warning disable 618 return ReflectHelper.CastOrThrow(Joinable, "fetch lazy properties") .SelectFragment( - next?.Joinable, - next?.RHSAlias, + null, + null, RHSAlias, entitySuffix, collectionSuffix, @@ -236,8 +238,6 @@ internal string GetSelectFragment(string entitySuffix, string collectionSuffix, case SelectMode.FetchLazyPropertyGroup: return ReflectHelper.CastOrThrow(Joinable, "fetch lazy property") .SelectFragment( - next?.Joinable, - next?.RHSAlias, RHSAlias, collectionSuffix, ShouldFetchCollectionPersister(), diff --git a/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs b/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs index 612900b989b..a5c18cf92cb 100644 --- a/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs +++ b/src/NHibernate/Persister/Collection/AbstractCollectionPersister.cs @@ -1777,8 +1777,15 @@ public virtual string SelectFragment( return SelectFragment(rhs, rhsAlias, lhsAlias, collectionSuffix, includeCollectionColumns, new EntityLoadInfo(entitySuffix) {IncludeLazyProps = true}); } - //6.0 TODO: Make abstract + // 6.0 TODO: Remove + [Obsolete("Please use overload without rhs and rhsAlias parameters")] public virtual string SelectFragment(IJoinable rhs, string rhsAlias, string lhsAlias, string currentCollectionSuffix, bool includeCollectionColumns, EntityLoadInfo entityInfo) + { + return SelectFragment(lhsAlias, currentCollectionSuffix, includeCollectionColumns, entityInfo); + } + + // 6.0 TODO: Make abstract + public virtual string SelectFragment(string lhsAlias, string collectionSuffix, bool includeCollectionColumns, EntityLoadInfo entityInfo) { throw new NotImplementedException("SelectFragment with fetching lazy properties option is not implemented by " + GetType().FullName); } diff --git a/src/NHibernate/Persister/Collection/BasicCollectionPersister.cs b/src/NHibernate/Persister/Collection/BasicCollectionPersister.cs index 65eb87b0724..d28a1a5b7ee 100644 --- a/src/NHibernate/Persister/Collection/BasicCollectionPersister.cs +++ b/src/NHibernate/Persister/Collection/BasicCollectionPersister.cs @@ -262,51 +262,15 @@ protected override int DoUpdateRows(object id, IPersistentCollection collection, } } + [Obsolete("Please use overload without rhs and rhsAlias parameters")] public override string SelectFragment(IJoinable rhs, string rhsAlias, string lhsAlias, string collectionSuffix, bool includeCollectionColumns, EntityLoadInfo entityInfo) { - // we need to determine the best way to know that two joinables - // represent a single many-to-many... - if (rhs != null && IsManyToMany && !rhs.IsCollection) - { - IAssociationType elementType = (IAssociationType) ElementType; - if (rhs.Equals(elementType.GetAssociatedJoinable(Factory))) - { - return ManyToManySelectFragment(rhs, rhsAlias, lhsAlias, collectionSuffix, elementType); - } - } - return includeCollectionColumns - ? GetSelectFragment(lhsAlias, collectionSuffix).ToSqlStringFragment(false) - : string.Empty; + return SelectFragment(lhsAlias, collectionSuffix, includeCollectionColumns, entityInfo); } - private string ManyToManySelectFragment( - IJoinable rhs, - string rhsAlias, - string lhsAlias, - string collectionSuffix, - IAssociationType elementType) + public override string SelectFragment(string lhsAlias, string collectionSuffix, bool includeCollectionColumns, EntityLoadInfo entityInfo) { - SelectFragment frag = GenerateSelectFragment(lhsAlias, collectionSuffix); - - // We need to select in the associated entity table instead of taking the collection actual element, - // because filters can be applied to the entity table outer join. In such case, we need to return null - // for filtered-out elements. (It is tempting to switch to an inner join and just use - // SelectFragment(lhsAlias, collectionSuffix) for many-to-many too, but this would hinder the proper - // handling of the not-found feature.) - var elementColumnNames = string.IsNullOrEmpty(elementType.RHSUniqueKeyPropertyName) - ? rhs.KeyColumnNames - // rhs is the entity persister, it does not handle being referenced through an unique key by a - // collection and always yield its identifier columns as KeyColumnNames. We need to resolve the - // key columns instead. - // 6.0 TODO: consider breaking again that IJoinable.SelectFragment interface for transmitting - // the OuterJoinableAssociation instead of its Joinable property. This would allow to get the - // adequate columns directly instead of re-computing them. - : ((IPropertyMapping) rhs).ToColumns(elementType.RHSUniqueKeyPropertyName); - frag.AddColumns(rhsAlias, elementColumnNames, elementColumnAliases); - AppendIndexColumns(frag, lhsAlias); - AppendIdentifierColumns(frag, lhsAlias); - - return frag.ToSqlStringFragment(false); + return includeCollectionColumns ? GetSelectFragment(lhsAlias, collectionSuffix).ToSqlStringFragment(false) : string.Empty; } /// diff --git a/src/NHibernate/Persister/Collection/OneToManyPersister.cs b/src/NHibernate/Persister/Collection/OneToManyPersister.cs index 2973deec937..2f525fe65f0 100644 --- a/src/NHibernate/Persister/Collection/OneToManyPersister.cs +++ b/src/NHibernate/Persister/Collection/OneToManyPersister.cs @@ -294,7 +294,13 @@ protected override int DoUpdateRows(object id, IPersistentCollection collection, } } + [Obsolete("Please use overload without rhs and rhsAlias parameters")] public override string SelectFragment(IJoinable rhs, string rhsAlias, string lhsAlias, string collectionSuffix, bool includeCollectionColumns, EntityLoadInfo entityInfo) + { + return SelectFragment(lhsAlias, collectionSuffix, includeCollectionColumns, entityInfo); + } + + public override string SelectFragment(string lhsAlias, string collectionSuffix, bool includeCollectionColumns, EntityLoadInfo entityInfo) { var buf = new StringBuilder(); @@ -317,7 +323,7 @@ public override string SelectFragment(IJoinable rhs, string rhsAlias, string lhs { var selectMode = ReflectHelper.CastOrThrow(ElementPersister, "fetch lazy properties"); if (selectMode != null) - return buf.Append(selectMode.SelectFragment(null, null, lhsAlias, null, false, entityInfo)).ToString(); + return buf.Append(selectMode.SelectFragment(lhsAlias, null, false, entityInfo)).ToString(); } var ojl = (IOuterJoinLoadable)ElementPersister; diff --git a/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs b/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs index 5e2f3e4748d..205fdb1f212 100644 --- a/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs +++ b/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs @@ -4461,10 +4461,17 @@ public string SelectFragment( IJoinable rhs, string rhsAlias, string lhsAlias, string entitySuffix, string collectionSuffix, bool includeCollectionColumns, bool includeLazyProperties) { - return SelectFragment(rhs, rhsAlias, lhsAlias, collectionSuffix, includeCollectionColumns, new EntityLoadInfo(entitySuffix) {IncludeLazyProps = includeLazyProperties}); + return SelectFragment(lhsAlias, collectionSuffix, includeCollectionColumns, new EntityLoadInfo(entitySuffix) {IncludeLazyProps = includeLazyProperties}); } + //Since v5.5 + [Obsolete("Please use overload without rhs and rhsAlias parameters")] public string SelectFragment(IJoinable rhs, string rhsAlias, string lhsAlias, string collectionSuffix, bool includeCollectionColumns, EntityLoadInfo entityInfo) + { + return SelectFragment(lhsAlias, collectionSuffix, includeCollectionColumns, entityInfo); + } + + public string SelectFragment(string lhsAlias, string collectionSuffix, bool includeCollectionColumns, EntityLoadInfo entityInfo) { return GetIdentifierSelectFragment(lhsAlias, entityInfo.EntitySuffix).ToSqlStringFragment(false) + GetPropertiesSelectFragment( diff --git a/src/NHibernate/Persister/Entity/ISupportSelectModeJoinable.cs b/src/NHibernate/Persister/Entity/ISupportSelectModeJoinable.cs index bff0512d40f..45ddb1d2123 100644 --- a/src/NHibernate/Persister/Entity/ISupportSelectModeJoinable.cs +++ b/src/NHibernate/Persister/Entity/ISupportSelectModeJoinable.cs @@ -36,6 +36,6 @@ public EntityLoadInfo(string entitySuffix) // 6.0 TODO: merge into 'IJoinable'. internal interface ISupportLazyPropsJoinable { - string SelectFragment(IJoinable rhs, string rhsAlias, string lhsAlias, string collectionSuffix, bool includeCollectionColumns, EntityLoadInfo entityInfo); + string SelectFragment(string lhsAlias, string collectionSuffix, bool includeCollectionColumns, EntityLoadInfo entityInfo); } } diff --git a/src/NHibernate/Proxy/FieldInterceptorProxyBuilder.cs b/src/NHibernate/Proxy/FieldInterceptorProxyBuilder.cs index 9e1326c3b2a..4fed7814108 100644 --- a/src/NHibernate/Proxy/FieldInterceptorProxyBuilder.cs +++ b/src/NHibernate/Proxy/FieldInterceptorProxyBuilder.cs @@ -57,10 +57,9 @@ public static TypeInfo CreateProxyType(System.Type baseType) var assemblyBuilder = ProxyBuilderHelper.DefineDynamicAssembly(AppDomain.CurrentDomain, name); -#if NETFX || NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER if (!baseType.IsVisible) ProxyBuilderHelper.GenerateInstanceOfIgnoresAccessChecksToAttribute(assemblyBuilder, baseType.Assembly.GetName().Name); -#endif + var moduleBuilder = ProxyBuilderHelper.DefineDynamicModule(assemblyBuilder, moduleName); const TypeAttributes typeAttributes = TypeAttributes.AutoClass | TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.BeforeFieldInit; @@ -94,11 +93,11 @@ public static TypeInfo CreateProxyType(System.Type baseType) private static void CreateProxiedMethod(TypeBuilder typeBuilder, MethodInfo method, FieldInfo fieldInterceptorField) { - if (ReflectHelper.IsPropertyGet(method)) + if (ReflectHelper.IsPropertyGet(method) && method.GetParameters().Length == 0) { ImplementGet(typeBuilder, method, fieldInterceptorField); } - else if (ReflectHelper.IsPropertySet(method)) + else if (ReflectHelper.IsPropertySet(method) && method.GetParameters().Length == 1) { ImplementSet(typeBuilder, method, fieldInterceptorField); } diff --git a/src/NHibernate/Util/ReflectHelper.cs b/src/NHibernate/Util/ReflectHelper.cs index 06352101745..e665a68a9c3 100644 --- a/src/NHibernate/Util/ReflectHelper.cs +++ b/src/NHibernate/Util/ReflectHelper.cs @@ -820,31 +820,18 @@ private static MethodInfo SafeGetMethod(System.Type type, MethodInfo method, Sys return foundMethod; } - internal static object GetConstantValue(string qualifiedName) - { - return GetConstantValue(qualifiedName, null); - } - internal static object GetConstantValue(string qualifiedName, ISessionFactoryImplementor sfi) { string className = StringHelper.Qualifier(qualifiedName); - if (!string.IsNullOrEmpty(className)) - { - System.Type t = System.Type.GetType(className); - - if (t == null && sfi != null) - { - t = System.Type.GetType(sfi.GetImportedClassName(className)); - } + if (string.IsNullOrEmpty(className)) + return null; - if (t != null) - { - return GetConstantValue(t, StringHelper.Unqualify(qualifiedName)); - } - } + var t = System.Type.GetType(sfi?.GetImportedClassName(className) ?? className); - return null; + return t == null + ? null + : GetConstantValue(t, StringHelper.Unqualify(qualifiedName)); } // Since v5