diff --git a/.github/renovate.json b/.github/renovate.json
index 32d5a56f64c..689fc32f5d9 100644
--- a/.github/renovate.json
+++ b/.github/renovate.json
@@ -15,9 +15,7 @@
],
"packageRules": [
{
- "matchPackagePrefixes": [
- "NUnit"
- ],
+ "matchSourceUrls": ["https://github.com/nunit/nunit"],
"groupName": "NUnit"
},
{
diff --git a/releasenotes.txt b/releasenotes.txt
index 5fd64fc3975..1a7f3e97521 100644
--- a/releasenotes.txt
+++ b/releasenotes.txt
@@ -1,4 +1,29 @@
-Build 5.4.2
+Build 5.4.3
+=============================
+
+Release notes - NHibernate - Version 5.4.3
+
+11 issues were resolved in this release.
+
+** Bug
+
+ * #3317 Issue with components list lazy loading with not lazy association
+ * #3307 IsDirty performance hit since 5.4.0
+ * #3295 C# 8/11 Static interface members support
+ * #3291 Npgsql 6+ issues with null DateTime parameter types
+ * #3290 Incorrect fetch of Many-to-Many relation
+ * #3289 Fetching lazy loaded component causes n + 1 query when querying a subclass abstraction
+ * #3288 NullReferenceException is thrown when using Fetch
+
+** Task
+
+ * #3349 Release 5.4.3
+ * #3348 Merge 5.3.18 in 5.4.x
+ * #3318 Merge 5.3.17 in 5.4.x
+ * #3302 Upgrade NUnit3TestAdapter to fix "Unknown framework version 7.0"
+
+
+Build 5.4.2
=============================
Release notes - NHibernate - Version 5.4.2
@@ -234,6 +259,42 @@ 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.18
+=============================
+
+Release notes - NHibernate - Version 5.3.18
+
+3 issues were resolved in this release.
+
+** Bug
+
+ * #3333 Lazy property with nosetter accessor remains uninitialized
+ * #3330 Linq with FetchLazyProperties() resets lazy property changes
+
+** Task
+
+ * #3346 Release 5.3.18
+
+
+Build 5.3.17
+=============================
+
+Release notes - NHibernate - Version 5.3.17
+
+5 issues were resolved in this release.
+
+** Bug
+
+ * #3306 Invalid SQL when referencing nullable entity in correlated subquery
+ * #3304 Fix SetSnapShot CopyTo variance failure
+ * #3294 Undefined join type failure with cross joins and Informix
+
+** Task
+
+ * #3315 Release 5.3.17
+ * #3300 Backport handling of null DateTime parameters in Npgsql 6+
+
+
Build 5.3.16
=============================
diff --git a/src/NHibernate.Test.VisualBasic/NHibernate.Test.VisualBasic.vbproj b/src/NHibernate.Test.VisualBasic/NHibernate.Test.VisualBasic.vbproj
index ee40ac1072f..95d2047bc2b 100644
--- a/src/NHibernate.Test.VisualBasic/NHibernate.Test.VisualBasic.vbproj
+++ b/src/NHibernate.Test.VisualBasic/NHibernate.Test.VisualBasic.vbproj
@@ -28,7 +28,7 @@
-
+
diff --git a/src/NHibernate.Test/Async/FetchLazyProperties/FetchLazyPropertiesFixture.cs b/src/NHibernate.Test/Async/FetchLazyProperties/FetchLazyPropertiesFixture.cs
index 2783bfead0d..6a812cd5888 100644
--- a/src/NHibernate.Test/Async/FetchLazyProperties/FetchLazyPropertiesFixture.cs
+++ b/src/NHibernate.Test/Async/FetchLazyProperties/FetchLazyPropertiesFixture.cs
@@ -276,6 +276,45 @@ public async Task TestLinqFetchAllPropertiesAsync()
AssertFetchAllProperties(person);
}
+ [TestCase(true)]
+ [TestCase(false)]
+ public async Task TestLinqFetchAllProperties_WhenLazyPropertyChangedAsync(bool initLazyPropertyFetchGroup)
+ {
+ Person person;
+ using (var s = OpenSession())
+ {
+ person = await (s.GetAsync(1));
+ if (initLazyPropertyFetchGroup)
+ CollectionAssert.AreEqual(new byte[] { 0 }, person.Image);
+
+ person.Image = new byte[] { 1, 2, 3 };
+
+ var allPersons = await (s.Query().FetchLazyProperties().ToListAsync());
+ // After execute FetchLazyProperties(), I expected to see that the person.Image would be { 1, 2, 3 }.
+ // Because I changed this person.Image manually, I didn't want to lose those changes.
+ // But test failed. Оld value returned { 0 }.
+ CollectionAssert.AreEqual(new byte[] { 1, 2, 3 }, person.Image);
+ }
+ }
+
+ [TestCase(true)]
+ [TestCase(false)]
+ public async Task TestLinqFetchProperty_WhenLazyPropertyChangedAsync(bool initLazyPropertyFetchGroup)
+ {
+ Person person;
+ using (var s = OpenSession())
+ {
+ person = await (s.GetAsync(1));
+ if (initLazyPropertyFetchGroup)
+ CollectionAssert.AreEqual(new byte[] { 0 }, person.Image);
+
+ person.Image = new byte[] { 1, 2, 3 };
+
+ var allPersons = await (s.Query().Fetch(x => x.Image).ToListAsync());
+ CollectionAssert.AreEqual(new byte[] { 1, 2, 3 }, person.Image);
+ }
+ }
+
private static void AssertFetchAllProperties(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 e7714c07a49..f99b79e5420 100644
--- a/src/NHibernate.Test/Async/LazyProperty/LazyPropertyFixture.cs
+++ b/src/NHibernate.Test/Async/LazyProperty/LazyPropertyFixture.cs
@@ -13,10 +13,10 @@
using System.Linq;
using NHibernate.Cfg;
using NHibernate.Intercept;
+using NHibernate.Linq;
using NHibernate.Tuple.Entity;
using NUnit.Framework;
using NUnit.Framework.Constraints;
-using NHibernate.Linq;
namespace NHibernate.Test.LazyProperty
{
@@ -67,6 +67,7 @@ protected override void OnSetUp()
Id = 1,
ALotOfText = "a lot of text ...",
Image = new byte[10],
+ NoSetterImage = new byte[10],
FieldInterceptor = "Why not that name?"
});
tx.Commit();
@@ -391,5 +392,58 @@ public async Task CanMergeTransientWithLazyPropertyInCollectionAsync()
Assert.That(book.Words.First().Content, Is.EqualTo(new byte[1] { 0 }));
}
}
+
+ [Test(Description = "GH-3333")]
+ public async Task GetLazyPropertyWithNoSetterAccessor_PropertyShouldBeInitializedAsync()
+ {
+ using (ISession s = OpenSession())
+ {
+ var book = await (s.GetAsync(1));
+ var image = book.NoSetterImage;
+ // Fails. Property remains uninitialized after it has been accessed.
+ Assert.That(NHibernateUtil.IsPropertyInitialized(book, "NoSetterImage"), Is.True);
+ }
+ }
+
+ [Test(Description = "GH-3333")]
+ public async Task GetLazyPropertyWithNoSetterAccessorTwice_ResultsAreSameObjectAsync()
+ {
+ using (ISession s = OpenSession())
+ {
+ var book = await (s.GetAsync(1));
+ var image = book.NoSetterImage;
+ var sameImage = book.NoSetterImage;
+ // Fails. Each call to a property getter returns a new object.
+ Assert.That(ReferenceEquals(image, sameImage), Is.True);
+ }
+ }
+
+ [Test]
+ public async Task CanSetValueForLazyPropertyNoSetterAsync()
+ {
+ Book book;
+ using (ISession s = OpenSession())
+ {
+ book = await (s.GetAsync(1));
+ book.NoSetterImage = new byte[]{10};
+ }
+
+ Assert.That(NHibernateUtil.IsPropertyInitialized(book, nameof(book.NoSetterImage)), Is.True);
+ CollectionAssert.AreEqual(book.NoSetterImage, new byte[] { 10 });
+ }
+
+ [Test]
+ public async Task CanFetchLazyPropertyNoSetterAsync()
+ {
+ using (ISession s = OpenSession())
+ {
+ var book = await (s
+ .Query()
+ .Fetch(x => x.NoSetterImage)
+ .FirstAsync(x => x.Id == 1));
+
+ Assert.That(NHibernateUtil.IsPropertyInitialized(book, nameof(book.NoSetterImage)), Is.True);
+ }
+ }
}
}
diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3288/FetchAndCollectionJoinFixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3288/FetchAndCollectionJoinFixture.cs
new file mode 100644
index 00000000000..0a1d6d97655
--- /dev/null
+++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3288/FetchAndCollectionJoinFixture.cs
@@ -0,0 +1,58 @@
+//------------------------------------------------------------------------------
+//
+// 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.Linq;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.GH3288
+{
+ using System.Threading.Tasks;
+ [TestFixture]
+ public class FetchAndCollectionJoinFixtureAsync : BugTestCase
+ {
+ protected override void OnSetUp()
+ {
+ using var session = OpenSession();
+ using var transaction = session.BeginTransaction();
+ var middleEntity = new MiddleEntity();
+ middleEntity.Components.Add(new Component { MiddleEntity = middleEntity, Value = 1 });
+ var te = new TopEntity
+ {
+ MiddleEntity = middleEntity
+ };
+ session.Save(middleEntity);
+ session.Save(te);
+
+ transaction.Commit();
+ }
+
+ protected override void OnTearDown()
+ {
+ using var session = OpenSession();
+ using var transaction = session.BeginTransaction();
+ session.Delete("from System.Object");
+
+ transaction.Commit();
+ }
+
+ [Test]
+ public async Task ReuseEntityJoinWithCollectionJoinAsync()
+ {
+ using var session = OpenSession();
+
+ var entities = await (session.Query()
+ .Fetch(e => e.MiddleEntity)
+ .Where(e => e.MiddleEntity.Components.Any(e => e.Value != 0))
+ .ToListAsync());
+ Assert.That(entities.Count, Is.EqualTo(1));
+ }
+ }
+}
diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3289/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3289/FixtureByCode.cs
new file mode 100644
index 00000000000..721d35848ab
--- /dev/null
+++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3289/FixtureByCode.cs
@@ -0,0 +1,111 @@
+//------------------------------------------------------------------------------
+//
+// 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.GH3289
+{
+ using System.Threading.Tasks;
+ [TestFixture]
+ public class ByCodeFixtureAsync : TestCaseMappingByCode
+ {
+ protected override HbmMapping GetMappings()
+ {
+ var mapper = new ModelMapper();
+ mapper.Class(rc =>
+ {
+ rc.Id(x => x.Id, m => m.Generator(Generators.Identity));
+ rc.Property(x => x.Name);
+ rc.Component(x => x.Component);
+ });
+ mapper.Class(rc =>
+ {
+ rc.Id(x => x.Id, m => m.Generator(Generators.Identity));
+ rc.Property(x => x.Name);
+ rc.Component(x => x.Component);
+ });
+ mapper.JoinedSubclass(rc =>
+ {
+ rc.Key(k => k.Column("Id"));
+ rc.Property(x => x.SomeProperty);
+ });
+ mapper.Component(rc =>
+ {
+ rc.Property(x => x.Field);
+ rc.Lazy(true);
+ });
+
+ return mapper.CompileMappingForAllExplicitlyAddedEntities();
+ }
+
+ protected override void OnSetUp()
+ {
+ using var session = OpenSession();
+ using var transaction = session.BeginTransaction();
+ var e1 = new SubEntity { Name = "Jim" };
+ session.Save(e1);
+
+ transaction.Commit();
+ }
+
+ protected override void OnTearDown()
+ {
+ using var session = OpenSession();
+ using var transaction = session.BeginTransaction();
+
+ if (Dialect.SupportsTemporaryTables)
+ session.CreateQuery("delete from System.Object").ExecuteUpdate();
+ else
+ session.Delete("from System.Object");
+
+ transaction.Commit();
+ }
+
+ [Test]
+ public async Task TestSubEntityInterfaceWithFetchIsPropertyInitializedAsync()
+ {
+ using var session = OpenSession();
+ var data = await (session.Query()
+ .Fetch(e => e.Component)
+ .ToListAsync());
+ var result = NHibernateUtil.IsPropertyInitialized(data[0], "Component");
+
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public async Task TestEntityInterfaceWithFetchIsPropertyInitializedAsync()
+ {
+ using var session = OpenSession();
+ var data = await (session.Query()
+ .Fetch(e => e.Component)
+ .ToListAsync());
+ var result = NHibernateUtil.IsPropertyInitialized(data[0], "Component");
+
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public async Task TestSubEntityWithFetchIsPropertyInitializedAsync()
+ {
+ using var session = OpenSession();
+ var data = await (session.Query()
+ .Fetch(e => e.Component)
+ .ToListAsync());
+ var result = NHibernateUtil.IsPropertyInitialized(data[0], "Component");
+
+ Assert.That(result, Is.True);
+ }
+ }
+}
diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3290/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3290/Fixture.cs
new file mode 100644
index 00000000000..77229193cbf
--- /dev/null
+++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3290/Fixture.cs
@@ -0,0 +1,147 @@
+//------------------------------------------------------------------------------
+//
+// 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.Collections.Generic;
+using NHibernate.Cfg;
+using NHibernate.Cfg.MappingSchema;
+using NHibernate.Mapping.ByCode;
+using NHibernate.Transform;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.GH3290
+{
+ using System.Threading.Tasks;
+ [TestFixture(true)]
+ [TestFixture(false)]
+ public class FixtureAsync : TestCaseMappingByCode
+ {
+ private readonly bool _detectFetchLoops;
+
+ public FixtureAsync(bool detectFetchLoops)
+ {
+ _detectFetchLoops = detectFetchLoops;
+ }
+
+ protected override HbmMapping GetMappings()
+ {
+ var mapper = new ModelMapper();
+ mapper.Class(rc =>
+ {
+ rc.Id(x => x.Id, map => map.Generator(Generators.GuidComb));
+
+ rc.Property(
+ x => x.Name
+ );
+
+ rc.Set(
+ x => x.Children,
+ v =>
+ {
+ v.Table("EntityToEntity");
+ v.Cascade(Mapping.ByCode.Cascade.None);
+ v.Inverse(true);
+ v.Key(x =>
+ {
+ x.Column("ParentId");
+ x.NotNullable(true);
+ });
+ v.Lazy(CollectionLazy.Lazy);
+ v.Fetch(CollectionFetchMode.Join);
+ },
+ h => h.ManyToMany(m => m.Column("ChildId"))
+ );
+
+ rc.Set(
+ x => x.Parents,
+ v =>
+ {
+ v.Table("EntityToEntity");
+ v.Cascade(Mapping.ByCode.Cascade.All);
+
+ v.Key(x =>
+ {
+ x.Column("ChildId");
+ x.NotNullable(true);
+ });
+ v.Lazy(CollectionLazy.Lazy);
+ v.Fetch(CollectionFetchMode.Join);
+ },
+ h => h.ManyToMany(m => m.Column("ParentId"))
+ );
+ });
+
+ return mapper.CompileMappingForAllExplicitlyAddedEntities();
+ }
+
+ protected override void Configure(Configuration configuration)
+ {
+ configuration.SetProperty(Environment.DetectFetchLoops, _detectFetchLoops ? "true" : "false");
+ configuration.SetProperty(Environment.MaxFetchDepth, _detectFetchLoops ? "-1" : "2");
+ }
+
+ protected override void OnSetUp()
+ {
+ using var session = OpenSession();
+ using var transaction = session.BeginTransaction();
+
+ var person = new Entity
+ {
+ Name = "pers",
+ Parents = new HashSet()
+ };
+ session.Save(person);
+ var job = new Entity
+ {
+ Name = "job",
+ Children = new HashSet()
+ };
+ session.Save(job);
+
+ job.Children.Add(person);
+ person.Parents.Add(job);
+
+ transaction.Commit();
+ }
+
+ protected override void OnTearDown()
+ {
+ using var session = OpenSession();
+ using var transaction = session.BeginTransaction();
+
+ session.CreateSQLQuery("delete from EntityToEntity").ExecuteUpdate();
+ session.CreateQuery("delete from System.Object").ExecuteUpdate();
+
+ transaction.Commit();
+ }
+
+ [Test]
+ public async Task QueryWithFetchAsync()
+ {
+ using var session = OpenSession();
+ using var _ = session.BeginTransaction();
+
+ var all = await (session
+ .QueryOver()
+ .Fetch(SelectMode.Fetch, x => x.Children)
+ .Fetch(SelectMode.Fetch, x => x.Parents)
+ .TransformUsing(Transformers.DistinctRootEntity)
+ .ListAsync());
+
+ foreach (var entity in all)
+ {
+ var isPerson = entity.Name == "pers";
+ if (isPerson)
+ Assert.That(entity.Parents, Has.Count.EqualTo(1), "Person's job not found or non-unique.");
+ else
+ Assert.That(entity.Children, Has.Count.EqualTo(1), "Job's employee not found or non-unique.");
+ }
+ }
+ }
+}
diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3291/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3291/Fixture.cs
new file mode 100644
index 00000000000..0d898115b80
--- /dev/null
+++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3291/Fixture.cs
@@ -0,0 +1,79 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by AsyncGenerator.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+
+using System;
+using System.Linq;
+using NHibernate.Criterion;
+using NUnit.Framework;
+using NHibernate.Linq;
+
+namespace NHibernate.Test.NHSpecificTest.GH3291
+{
+ using System.Threading.Tasks;
+ [TestFixture]
+ public class FixtureAsync : BugTestCase
+ {
+ protected override void OnSetUp()
+ {
+ using var session = OpenSession();
+ using var transaction = session.BeginTransaction();
+
+ var e1 = new Person { Name = "Bob", DateOfBirth = new DateTime(2009, 12, 23) };
+ session.Save(e1);
+
+ var e2 = new Person { Name = "Sally", DateOfBirth = new DateTime(2018, 9, 30) };
+ session.Save(e2);
+
+ 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 LinqAsync()
+ {
+ using var session = OpenSession();
+ using var _ = session.BeginTransaction();
+
+ DateTime? dateOfSearch = null;
+
+ var result = await ((
+ from person in session.Query()
+ where dateOfSearch == null || person.DateOfBirth > dateOfSearch
+ select person).ToListAsync());
+
+ Assert.That(result, Has.Count.EqualTo(2));
+ }
+
+ [Test]
+ public async Task HqlAsync()
+ {
+ using var session = OpenSession();
+ using var _ = session.BeginTransaction();
+
+ DateTime? dateOfSearch = null;
+
+ var result =
+ await (session.CreateQuery("from Person where :DateOfSearch is null OR DateOfBirth > :DateOfSearch")
+ .SetParameter("DateOfSearch", dateOfSearch, NHibernateUtil.DateTime)
+ .ListAsync());
+
+ Assert.That(result, Has.Count.EqualTo(2));
+ }
+ }
+}
diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Fixture.cs
new file mode 100644
index 00000000000..ba446d194f9
--- /dev/null
+++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Fixture.cs
@@ -0,0 +1,69 @@
+//------------------------------------------------------------------------------
+//
+// 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 NUnit.Framework;
+using NHibernate.Linq;
+
+namespace NHibernate.Test.NHSpecificTest.GH3306NullableEntityCorrelatedSubquery
+{
+ using System.Threading.Tasks;
+ [TestFixture]
+ public class FixtureAsync : BugTestCase
+ {
+ private const string NAME_JOE = "Joe";
+ private const string NAME_ALLEN = "Allen";
+
+ protected override void OnSetUp()
+ {
+ using (var session = OpenSession())
+ using (var tx = session.BeginTransaction())
+ {
+ var joe = new Customer { Name = NAME_JOE };
+ session.Save(joe);
+
+ var allen = new Customer { Name = NAME_ALLEN };
+ session.Save(allen);
+
+ var joeInvoice0 = new Invoice { Customer = joe, Number = 0 };
+ session.Save(joeInvoice0);
+
+ var allenInvoice1 = new Invoice { Customer = allen, Number = 1 };
+ session.Save(allenInvoice1);
+
+ tx.Commit();
+ }
+ }
+
+ protected override void OnTearDown()
+ {
+ using (var session = OpenSession())
+ using (var tx = session.BeginTransaction())
+ {
+ session.Delete("from Invoice");
+ session.Delete("from Customer");
+ tx.Commit();
+ }
+ }
+
+ [Test]
+ public async Task NullableEntityInCorrelatedSubqueryAsync()
+ {
+ using (var s = OpenSession())
+ {
+ var customers = s.Query().Where(c => c.Name == NAME_JOE);
+ var results = await (s.Query()
+ .Where(i => customers.Any(c => c.Invoices.Any(ci => ci.Customer == i.Customer))).ToListAsync());
+
+ Assert.That(results.Count, Is.EqualTo(1));
+ }
+ }
+ }
+}
diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3317/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3317/FixtureByCode.cs
new file mode 100644
index 00000000000..1ca210a186e
--- /dev/null
+++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3317/FixtureByCode.cs
@@ -0,0 +1,123 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by AsyncGenerator.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+
+using System;
+using System.Linq;
+using NHibernate.Cfg.MappingSchema;
+using NHibernate.Mapping.ByCode;
+using NHibernate.Linq;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.GH3317
+{
+ using System.Threading.Tasks;
+ [TestFixture(true)]
+ [TestFixture(false)]
+ public class ComponentsListFixtureAsync : TestCaseMappingByCode
+ {
+ private readonly bool _fetchJoinMapping;
+ private Guid _id;
+
+ public ComponentsListFixtureAsync(bool fetchJoinMapping)
+ {
+ _fetchJoinMapping = fetchJoinMapping;
+ }
+
+ protected override void OnSetUp()
+ {
+ using var session = OpenSession();
+ using var tr = session.BeginTransaction();
+ var root = new Entity();
+ root.Entries.Add(new ComponentListEntry { ComponentReference = null, DummyString = "one", });
+
+ session.Save(root);
+ tr.Commit();
+ _id = root.Id;
+ }
+
+ [Test]
+ public async Task LazyLoadingAsync()
+ {
+ using var newSession = OpenSession();
+ var reloadedRoot = await (newSession.GetAsync(_id));
+ Assert.AreEqual(1, reloadedRoot.Entries.Count);
+ }
+
+ [Test]
+ public async Task QueryOverFetchAsync()
+ {
+ using var newSession = OpenSession();
+ var reloadedRoot = await (newSession.QueryOver()
+ .Fetch(SelectMode.Fetch, x => x.Entries)
+ .Where(x => x.Id == _id)
+ .SingleOrDefaultAsync());
+ Assert.AreEqual(1, reloadedRoot.Entries.Count);
+ }
+
+ [Test]
+ public async Task LinqFetchAsync()
+ {
+ using var newSession = OpenSession();
+ var reloadedRoot = await (newSession.Query()
+ .Fetch(x => x.Entries)
+ .Where(x => x.Id == _id)
+ .SingleOrDefaultAsync());
+ Assert.AreEqual(1, reloadedRoot.Entries.Count);
+ }
+
+ protected override void OnTearDown()
+ {
+ using var session = OpenSession();
+ using var transaction = session.BeginTransaction();
+ session.CreateSQLQuery("delete from Entries").ExecuteUpdate();
+ session.Delete("from System.Object");
+ transaction.Commit();
+ }
+
+ protected override HbmMapping GetMappings()
+ {
+ var mapper = new ModelMapper();
+
+ mapper.Class(rc =>
+ {
+ rc.Id(x => x.Id, map => map.Generator(Generators.GuidComb));
+ rc.Lazy(false);
+ rc.Property(x => x.Name);
+
+ rc.Bag(
+ x => x.Entries,
+ v =>
+ {
+ if (_fetchJoinMapping)
+ v.Fetch(CollectionFetchMode.Join);
+ },
+ h => h.Component(cmp =>
+ {
+ cmp.Property(x => x.DummyString);
+ cmp.ManyToOne(x => x.ComponentReference);
+ }));
+ });
+ mapper.Class(rc =>
+ {
+ rc.Id(x => x.Id, map => map.Generator(Generators.GuidComb));
+ rc.Lazy(false);
+ rc.ManyToOne(x => x.Parent, m => m.NotNullable(true));
+ });
+ mapper.Class(rc =>
+ {
+ rc.Id(x => x.Id, map => map.Generator(Generators.GuidComb));
+ rc.Lazy(false);
+ rc.Property(x => x.Name);
+ });
+
+ return mapper.CompileMappingForAllExplicitlyAddedEntities();
+ }
+ }
+}
diff --git a/src/NHibernate.Test/FetchLazyProperties/FetchLazyPropertiesFixture.cs b/src/NHibernate.Test/FetchLazyProperties/FetchLazyPropertiesFixture.cs
index 43d68dce93d..7a89e998c36 100644
--- a/src/NHibernate.Test/FetchLazyProperties/FetchLazyPropertiesFixture.cs
+++ b/src/NHibernate.Test/FetchLazyProperties/FetchLazyPropertiesFixture.cs
@@ -265,6 +265,45 @@ public void TestLinqFetchAllProperties()
AssertFetchAllProperties(person);
}
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestLinqFetchAllProperties_WhenLazyPropertyChanged(bool initLazyPropertyFetchGroup)
+ {
+ Person person;
+ using (var s = OpenSession())
+ {
+ person = s.Get(1);
+ if (initLazyPropertyFetchGroup)
+ CollectionAssert.AreEqual(new byte[] { 0 }, person.Image);
+
+ person.Image = new byte[] { 1, 2, 3 };
+
+ var allPersons = s.Query().FetchLazyProperties().ToList();
+ // After execute FetchLazyProperties(), I expected to see that the person.Image would be { 1, 2, 3 }.
+ // Because I changed this person.Image manually, I didn't want to lose those changes.
+ // But test failed. Оld value returned { 0 }.
+ CollectionAssert.AreEqual(new byte[] { 1, 2, 3 }, person.Image);
+ }
+ }
+
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestLinqFetchProperty_WhenLazyPropertyChanged(bool initLazyPropertyFetchGroup)
+ {
+ Person person;
+ using (var s = OpenSession())
+ {
+ person = s.Get(1);
+ if (initLazyPropertyFetchGroup)
+ CollectionAssert.AreEqual(new byte[] { 0 }, person.Image);
+
+ person.Image = new byte[] { 1, 2, 3 };
+
+ var allPersons = s.Query().Fetch(x => x.Image).ToList();
+ CollectionAssert.AreEqual(new byte[] { 1, 2, 3 }, person.Image);
+ }
+ }
+
private static void AssertFetchAllProperties(Person person)
{
Assert.That(person, Is.Not.Null);
diff --git a/src/NHibernate.Test/LazyProperty/Book.cs b/src/NHibernate.Test/LazyProperty/Book.cs
index 546df8a248b..3dcfe73c567 100644
--- a/src/NHibernate.Test/LazyProperty/Book.cs
+++ b/src/NHibernate.Test/LazyProperty/Book.cs
@@ -17,6 +17,14 @@ public virtual string ALotOfText
public virtual byte[] Image { get; set; }
+ private byte[] _NoSetterImage;
+
+ public virtual byte[] NoSetterImage
+ {
+ get { return _NoSetterImage; }
+ set { _NoSetterImage = value; }
+ }
+
public virtual string FieldInterceptor { get; set; }
public virtual IList Words { get; set; }
diff --git a/src/NHibernate.Test/LazyProperty/LazyPropertyFixture.cs b/src/NHibernate.Test/LazyProperty/LazyPropertyFixture.cs
index 5efb7d6e29d..302271f41a3 100644
--- a/src/NHibernate.Test/LazyProperty/LazyPropertyFixture.cs
+++ b/src/NHibernate.Test/LazyProperty/LazyPropertyFixture.cs
@@ -3,6 +3,7 @@
using System.Linq;
using NHibernate.Cfg;
using NHibernate.Intercept;
+using NHibernate.Linq;
using NHibernate.Tuple.Entity;
using NUnit.Framework;
using NUnit.Framework.Constraints;
@@ -55,6 +56,7 @@ protected override void OnSetUp()
Id = 1,
ALotOfText = "a lot of text ...",
Image = new byte[10],
+ NoSetterImage = new byte[10],
FieldInterceptor = "Why not that name?"
});
tx.Commit();
@@ -385,5 +387,58 @@ public void CanMergeTransientWithLazyPropertyInCollection()
Assert.That(book.Words.First().Content, Is.EqualTo(new byte[1] { 0 }));
}
}
+
+ [Test(Description = "GH-3333")]
+ public void GetLazyPropertyWithNoSetterAccessor_PropertyShouldBeInitialized()
+ {
+ using (ISession s = OpenSession())
+ {
+ var book = s.Get(1);
+ var image = book.NoSetterImage;
+ // Fails. Property remains uninitialized after it has been accessed.
+ Assert.That(NHibernateUtil.IsPropertyInitialized(book, "NoSetterImage"), Is.True);
+ }
+ }
+
+ [Test(Description = "GH-3333")]
+ public void GetLazyPropertyWithNoSetterAccessorTwice_ResultsAreSameObject()
+ {
+ using (ISession s = OpenSession())
+ {
+ var book = s.Get(1);
+ var image = book.NoSetterImage;
+ var sameImage = book.NoSetterImage;
+ // Fails. Each call to a property getter returns a new object.
+ Assert.That(ReferenceEquals(image, sameImage), Is.True);
+ }
+ }
+
+ [Test]
+ public void CanSetValueForLazyPropertyNoSetter()
+ {
+ Book book;
+ using (ISession s = OpenSession())
+ {
+ book = s.Get(1);
+ book.NoSetterImage = new byte[]{10};
+ }
+
+ Assert.That(NHibernateUtil.IsPropertyInitialized(book, nameof(book.NoSetterImage)), Is.True);
+ CollectionAssert.AreEqual(book.NoSetterImage, new byte[] { 10 });
+ }
+
+ [Test]
+ public void CanFetchLazyPropertyNoSetter()
+ {
+ using (ISession s = OpenSession())
+ {
+ var book = s
+ .Query()
+ .Fetch(x => x.NoSetterImage)
+ .First(x => x.Id == 1);
+
+ Assert.That(NHibernateUtil.IsPropertyInitialized(book, nameof(book.NoSetterImage)), Is.True);
+ }
+ }
}
}
diff --git a/src/NHibernate.Test/LazyProperty/Mappings.hbm.xml b/src/NHibernate.Test/LazyProperty/Mappings.hbm.xml
index 91189c36e30..533576580cd 100644
--- a/src/NHibernate.Test/LazyProperty/Mappings.hbm.xml
+++ b/src/NHibernate.Test/LazyProperty/Mappings.hbm.xml
@@ -11,6 +11,7 @@
+
diff --git a/src/NHibernate.Test/NHSpecificTest/GH3288/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH3288/Entity.cs
new file mode 100644
index 00000000000..9f85c760a14
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH3288/Entity.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+
+namespace NHibernate.Test.NHSpecificTest.GH3288
+{
+ class TopEntity
+ {
+ public virtual int Id { get; set; }
+ public virtual MiddleEntity MiddleEntity { get; set; }
+ }
+ class MiddleEntity
+ {
+ public virtual int Id { get; set; }
+ public virtual string Name { get; set; }
+ public virtual ISet Components { get; set; } = new HashSet();
+ }
+
+ class Component
+ {
+ public virtual MiddleEntity MiddleEntity { get; set; }
+ public virtual int Value { get; set; }
+
+ public override bool Equals(object obj)
+ {
+ return (obj as Component)?.MiddleEntity.Id == MiddleEntity.Id;
+ }
+
+ public override int GetHashCode()
+ {
+ return MiddleEntity.Id.GetHashCode();
+ }
+ }
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/GH3288/FetchAndCollectionJoinFixture.cs b/src/NHibernate.Test/NHSpecificTest/GH3288/FetchAndCollectionJoinFixture.cs
new file mode 100644
index 00000000000..440c4f772bd
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH3288/FetchAndCollectionJoinFixture.cs
@@ -0,0 +1,47 @@
+using System.Linq;
+using NHibernate.Linq;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.GH3288
+{
+ [TestFixture]
+ public class FetchAndCollectionJoinFixture : BugTestCase
+ {
+ protected override void OnSetUp()
+ {
+ using var session = OpenSession();
+ using var transaction = session.BeginTransaction();
+ var middleEntity = new MiddleEntity();
+ middleEntity.Components.Add(new Component { MiddleEntity = middleEntity, Value = 1 });
+ var te = new TopEntity
+ {
+ MiddleEntity = middleEntity
+ };
+ session.Save(middleEntity);
+ session.Save(te);
+
+ transaction.Commit();
+ }
+
+ protected override void OnTearDown()
+ {
+ using var session = OpenSession();
+ using var transaction = session.BeginTransaction();
+ session.Delete("from System.Object");
+
+ transaction.Commit();
+ }
+
+ [Test]
+ public void ReuseEntityJoinWithCollectionJoin()
+ {
+ using var session = OpenSession();
+
+ var entities = session.Query()
+ .Fetch(e => e.MiddleEntity)
+ .Where(e => e.MiddleEntity.Components.Any(e => e.Value != 0))
+ .ToList();
+ Assert.That(entities.Count, Is.EqualTo(1));
+ }
+ }
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/GH3288/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH3288/Mappings.hbm.xml
new file mode 100644
index 00000000000..b9cd68ded6f
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH3288/Mappings.hbm.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/NHibernate.Test/NHSpecificTest/GH3289/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH3289/FixtureByCode.cs
new file mode 100644
index 00000000000..8bc20205c12
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH3289/FixtureByCode.cs
@@ -0,0 +1,100 @@
+using System.Linq;
+using NHibernate.Cfg.MappingSchema;
+using NHibernate.Linq;
+using NHibernate.Mapping.ByCode;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.GH3289
+{
+ [TestFixture]
+ public class ByCodeFixture : TestCaseMappingByCode
+ {
+ protected override HbmMapping GetMappings()
+ {
+ var mapper = new ModelMapper();
+ mapper.Class(rc =>
+ {
+ rc.Id(x => x.Id, m => m.Generator(Generators.Identity));
+ rc.Property(x => x.Name);
+ rc.Component(x => x.Component);
+ });
+ mapper.Class(rc =>
+ {
+ rc.Id(x => x.Id, m => m.Generator(Generators.Identity));
+ rc.Property(x => x.Name);
+ rc.Component(x => x.Component);
+ });
+ mapper.JoinedSubclass(rc =>
+ {
+ rc.Key(k => k.Column("Id"));
+ rc.Property(x => x.SomeProperty);
+ });
+ mapper.Component(rc =>
+ {
+ rc.Property(x => x.Field);
+ rc.Lazy(true);
+ });
+
+ return mapper.CompileMappingForAllExplicitlyAddedEntities();
+ }
+
+ protected override void OnSetUp()
+ {
+ using var session = OpenSession();
+ using var transaction = session.BeginTransaction();
+ var e1 = new SubEntity { Name = "Jim" };
+ session.Save(e1);
+
+ transaction.Commit();
+ }
+
+ protected override void OnTearDown()
+ {
+ using var session = OpenSession();
+ using var transaction = session.BeginTransaction();
+
+ if (Dialect.SupportsTemporaryTables)
+ session.CreateQuery("delete from System.Object").ExecuteUpdate();
+ else
+ session.Delete("from System.Object");
+
+ transaction.Commit();
+ }
+
+ [Test]
+ public void TestSubEntityInterfaceWithFetchIsPropertyInitialized()
+ {
+ using var session = OpenSession();
+ var data = session.Query()
+ .Fetch(e => e.Component)
+ .ToList();
+ var result = NHibernateUtil.IsPropertyInitialized(data[0], "Component");
+
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void TestEntityInterfaceWithFetchIsPropertyInitialized()
+ {
+ using var session = OpenSession();
+ var data = session.Query()
+ .Fetch(e => e.Component)
+ .ToList();
+ var result = NHibernateUtil.IsPropertyInitialized(data[0], "Component");
+
+ Assert.That(result, Is.True);
+ }
+
+ [Test]
+ public void TestSubEntityWithFetchIsPropertyInitialized()
+ {
+ using var session = OpenSession();
+ var data = session.Query()
+ .Fetch(e => e.Component)
+ .ToList();
+ var result = NHibernateUtil.IsPropertyInitialized(data[0], "Component");
+
+ Assert.That(result, Is.True);
+ }
+ }
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/GH3289/Models.cs b/src/NHibernate.Test/NHSpecificTest/GH3289/Models.cs
new file mode 100644
index 00000000000..ffbbeeb7bf7
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH3289/Models.cs
@@ -0,0 +1,37 @@
+namespace NHibernate.Test.NHSpecificTest.GH3289
+{
+ public interface IEntity
+ {
+ int Id { get; set; }
+ string Name { get; set; }
+ Component Component { get; set; }
+ }
+
+ public interface ISubEntity : IEntity
+ {
+ public bool SomeProperty { get; set; }
+ }
+
+ public class Entity : IEntity
+ {
+ public virtual int Id { get; set; }
+ public virtual string Name { get; set; }
+ public virtual Component Component { get; set; }
+ }
+ public class OtherEntity : IEntity
+ {
+ public virtual int Id { get; set; }
+ public virtual string Name { get; set; }
+ public virtual Component Component { get; set; }
+ }
+
+ public class SubEntity : Entity, ISubEntity
+ {
+ public virtual bool SomeProperty { get; set; }
+ }
+
+ public class Component
+ {
+ public virtual string Field { get; set; }
+ }
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/GH3290/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH3290/Entity.cs
new file mode 100644
index 00000000000..0ef176af019
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH3290/Entity.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+
+namespace NHibernate.Test.NHSpecificTest.GH3290
+{
+ class Entity
+ {
+ public virtual Guid Id { get; set; }
+ public virtual string Name { get; set; }
+ public virtual ISet Parents { get; set; }
+ public virtual ISet Children { get; set; }
+ }
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/GH3290/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH3290/Fixture.cs
new file mode 100644
index 00000000000..f7c6fe8dbe0
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH3290/Fixture.cs
@@ -0,0 +1,136 @@
+using System.Collections.Generic;
+using NHibernate.Cfg;
+using NHibernate.Cfg.MappingSchema;
+using NHibernate.Mapping.ByCode;
+using NHibernate.Transform;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.GH3290
+{
+ [TestFixture(true)]
+ [TestFixture(false)]
+ public class Fixture : TestCaseMappingByCode
+ {
+ private readonly bool _detectFetchLoops;
+
+ public Fixture(bool detectFetchLoops)
+ {
+ _detectFetchLoops = detectFetchLoops;
+ }
+
+ protected override HbmMapping GetMappings()
+ {
+ var mapper = new ModelMapper();
+ mapper.Class(rc =>
+ {
+ rc.Id(x => x.Id, map => map.Generator(Generators.GuidComb));
+
+ rc.Property(
+ x => x.Name
+ );
+
+ rc.Set(
+ x => x.Children,
+ v =>
+ {
+ v.Table("EntityToEntity");
+ v.Cascade(Mapping.ByCode.Cascade.None);
+ v.Inverse(true);
+ v.Key(x =>
+ {
+ x.Column("ParentId");
+ x.NotNullable(true);
+ });
+ v.Lazy(CollectionLazy.Lazy);
+ v.Fetch(CollectionFetchMode.Join);
+ },
+ h => h.ManyToMany(m => m.Column("ChildId"))
+ );
+
+ rc.Set(
+ x => x.Parents,
+ v =>
+ {
+ v.Table("EntityToEntity");
+ v.Cascade(Mapping.ByCode.Cascade.All);
+
+ v.Key(x =>
+ {
+ x.Column("ChildId");
+ x.NotNullable(true);
+ });
+ v.Lazy(CollectionLazy.Lazy);
+ v.Fetch(CollectionFetchMode.Join);
+ },
+ h => h.ManyToMany(m => m.Column("ParentId"))
+ );
+ });
+
+ return mapper.CompileMappingForAllExplicitlyAddedEntities();
+ }
+
+ protected override void Configure(Configuration configuration)
+ {
+ configuration.SetProperty(Environment.DetectFetchLoops, _detectFetchLoops ? "true" : "false");
+ configuration.SetProperty(Environment.MaxFetchDepth, _detectFetchLoops ? "-1" : "2");
+ }
+
+ protected override void OnSetUp()
+ {
+ using var session = OpenSession();
+ using var transaction = session.BeginTransaction();
+
+ var person = new Entity
+ {
+ Name = "pers",
+ Parents = new HashSet()
+ };
+ session.Save(person);
+ var job = new Entity
+ {
+ Name = "job",
+ Children = new HashSet()
+ };
+ session.Save(job);
+
+ job.Children.Add(person);
+ person.Parents.Add(job);
+
+ transaction.Commit();
+ }
+
+ protected override void OnTearDown()
+ {
+ using var session = OpenSession();
+ using var transaction = session.BeginTransaction();
+
+ session.CreateSQLQuery("delete from EntityToEntity").ExecuteUpdate();
+ session.CreateQuery("delete from System.Object").ExecuteUpdate();
+
+ transaction.Commit();
+ }
+
+ [Test]
+ public void QueryWithFetch()
+ {
+ using var session = OpenSession();
+ using var _ = session.BeginTransaction();
+
+ var all = session
+ .QueryOver()
+ .Fetch(SelectMode.Fetch, x => x.Children)
+ .Fetch(SelectMode.Fetch, x => x.Parents)
+ .TransformUsing(Transformers.DistinctRootEntity)
+ .List();
+
+ foreach (var entity in all)
+ {
+ var isPerson = entity.Name == "pers";
+ if (isPerson)
+ Assert.That(entity.Parents, Has.Count.EqualTo(1), "Person's job not found or non-unique.");
+ else
+ Assert.That(entity.Children, Has.Count.EqualTo(1), "Job's employee not found or non-unique.");
+ }
+ }
+ }
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/GH3291/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH3291/Fixture.cs
new file mode 100644
index 00000000000..fa01d89b8f9
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH3291/Fixture.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Linq;
+using NHibernate.Criterion;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.GH3291
+{
+ [TestFixture]
+ public class Fixture : BugTestCase
+ {
+ protected override void OnSetUp()
+ {
+ using var session = OpenSession();
+ using var transaction = session.BeginTransaction();
+
+ var e1 = new Person { Name = "Bob", DateOfBirth = new DateTime(2009, 12, 23) };
+ session.Save(e1);
+
+ var e2 = new Person { Name = "Sally", DateOfBirth = new DateTime(2018, 9, 30) };
+ session.Save(e2);
+
+ 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 Linq()
+ {
+ using var session = OpenSession();
+ using var _ = session.BeginTransaction();
+
+ DateTime? dateOfSearch = null;
+
+ var result = (
+ from person in session.Query()
+ where dateOfSearch == null || person.DateOfBirth > dateOfSearch
+ select person).ToList();
+
+ Assert.That(result, Has.Count.EqualTo(2));
+ }
+
+ [Test]
+ public void Hql()
+ {
+ using var session = OpenSession();
+ using var _ = session.BeginTransaction();
+
+ DateTime? dateOfSearch = null;
+
+ var result =
+ session.CreateQuery("from Person where :DateOfSearch is null OR DateOfBirth > :DateOfSearch")
+ .SetParameter("DateOfSearch", dateOfSearch, NHibernateUtil.DateTime)
+ .List();
+
+ Assert.That(result, Has.Count.EqualTo(2));
+ }
+ }
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/GH3291/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH3291/Mappings.hbm.xml
new file mode 100644
index 00000000000..1088c98b593
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH3291/Mappings.hbm.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/NHibernate.Test/NHSpecificTest/GH3291/Person.cs b/src/NHibernate.Test/NHSpecificTest/GH3291/Person.cs
new file mode 100644
index 00000000000..d80efc2e094
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH3291/Person.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace NHibernate.Test.NHSpecificTest.GH3291
+{
+ class Person
+ {
+ public virtual Guid Id { get; set; }
+ public virtual string Name { get; set; }
+ public virtual DateTime? DateOfBirth { get; set; }
+ }
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Fixture.cs
new file mode 100644
index 00000000000..d1d511eca59
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Fixture.cs
@@ -0,0 +1,57 @@
+using System.Linq;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.GH3306NullableEntityCorrelatedSubquery
+{
+ [TestFixture]
+ public class Fixture : BugTestCase
+ {
+ private const string NAME_JOE = "Joe";
+ private const string NAME_ALLEN = "Allen";
+
+ protected override void OnSetUp()
+ {
+ using (var session = OpenSession())
+ using (var tx = session.BeginTransaction())
+ {
+ var joe = new Customer { Name = NAME_JOE };
+ session.Save(joe);
+
+ var allen = new Customer { Name = NAME_ALLEN };
+ session.Save(allen);
+
+ var joeInvoice0 = new Invoice { Customer = joe, Number = 0 };
+ session.Save(joeInvoice0);
+
+ var allenInvoice1 = new Invoice { Customer = allen, Number = 1 };
+ session.Save(allenInvoice1);
+
+ tx.Commit();
+ }
+ }
+
+ protected override void OnTearDown()
+ {
+ using (var session = OpenSession())
+ using (var tx = session.BeginTransaction())
+ {
+ session.Delete("from Invoice");
+ session.Delete("from Customer");
+ tx.Commit();
+ }
+ }
+
+ [Test]
+ public void NullableEntityInCorrelatedSubquery()
+ {
+ using (var s = OpenSession())
+ {
+ var customers = s.Query().Where(c => c.Name == NAME_JOE);
+ var results = s.Query()
+ .Where(i => customers.Any(c => c.Invoices.Any(ci => ci.Customer == i.Customer))).ToList();
+
+ Assert.That(results.Count, Is.EqualTo(1));
+ }
+ }
+ }
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Mappings.hbm.xml
new file mode 100644
index 00000000000..515584fe116
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Mappings.hbm.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/NHibernate.Test/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Model.cs b/src/NHibernate.Test/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Model.cs
new file mode 100644
index 00000000000..2c472579b00
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH3306NullableEntityCorrelatedSubquery/Model.cs
@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+
+namespace NHibernate.Test.NHSpecificTest.GH3306NullableEntityCorrelatedSubquery
+{
+ public class Customer
+ {
+ public virtual int ID { get; protected set; }
+ public virtual ISet Invoices { get; set; }
+ public virtual string Name { get; set; }
+ }
+
+ public class Invoice
+ {
+ public virtual int ID { get; protected set; }
+ public virtual Customer Customer { get; set; }
+ public virtual int Number { get; set; }
+ }
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/GH3317/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH3317/Entity.cs
new file mode 100644
index 00000000000..1e43d5554c1
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH3317/Entity.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+
+namespace NHibernate.Test.NHSpecificTest.GH3317
+{
+ public class Entity
+ {
+ public Guid Id { get; set; }
+ public string Name { get; set; }
+ public IList Entries { get; set; } = new List();
+ }
+
+ public class ComponentListEntry
+ {
+ public string DummyString { get; set; }
+ public EntityWithParent ComponentReference { get; set; }
+ }
+
+ public class EntityWithParent
+ {
+ public Guid Id { get; set; }
+ public ParentEntity Parent { get; set; }
+ }
+
+ public class ParentEntity
+ {
+ public Guid Id { get; set; }
+ public string Name { get; set; }
+ }
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/GH3317/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH3317/FixtureByCode.cs
new file mode 100644
index 00000000000..3847af79449
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH3317/FixtureByCode.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Linq;
+using NHibernate.Cfg.MappingSchema;
+using NHibernate.Mapping.ByCode;
+using NHibernate.Linq;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.GH3317
+{
+ [TestFixture(true)]
+ [TestFixture(false)]
+ public class ComponentsListFixture : TestCaseMappingByCode
+ {
+ private readonly bool _fetchJoinMapping;
+ private Guid _id;
+
+ public ComponentsListFixture(bool fetchJoinMapping)
+ {
+ _fetchJoinMapping = fetchJoinMapping;
+ }
+
+ protected override void OnSetUp()
+ {
+ using var session = OpenSession();
+ using var tr = session.BeginTransaction();
+ var root = new Entity();
+ root.Entries.Add(new ComponentListEntry { ComponentReference = null, DummyString = "one", });
+
+ session.Save(root);
+ tr.Commit();
+ _id = root.Id;
+ }
+
+ [Test]
+ public void LazyLoading()
+ {
+ using var newSession = OpenSession();
+ var reloadedRoot = newSession.Get(_id);
+ Assert.AreEqual(1, reloadedRoot.Entries.Count);
+ }
+
+ [Test]
+ public void QueryOverFetch()
+ {
+ using var newSession = OpenSession();
+ var reloadedRoot = newSession.QueryOver()
+ .Fetch(SelectMode.Fetch, x => x.Entries)
+ .Where(x => x.Id == _id)
+ .SingleOrDefault();
+ Assert.AreEqual(1, reloadedRoot.Entries.Count);
+ }
+
+ [Test]
+ public void LinqFetch()
+ {
+ using var newSession = OpenSession();
+ var reloadedRoot = newSession.Query()
+ .Fetch(x => x.Entries)
+ .Where(x => x.Id == _id)
+ .SingleOrDefault();
+ Assert.AreEqual(1, reloadedRoot.Entries.Count);
+ }
+
+ protected override void OnTearDown()
+ {
+ using var session = OpenSession();
+ using var transaction = session.BeginTransaction();
+ session.CreateSQLQuery("delete from Entries").ExecuteUpdate();
+ session.Delete("from System.Object");
+ transaction.Commit();
+ }
+
+ protected override HbmMapping GetMappings()
+ {
+ var mapper = new ModelMapper();
+
+ mapper.Class(rc =>
+ {
+ rc.Id(x => x.Id, map => map.Generator(Generators.GuidComb));
+ rc.Lazy(false);
+ rc.Property(x => x.Name);
+
+ rc.Bag(
+ x => x.Entries,
+ v =>
+ {
+ if (_fetchJoinMapping)
+ v.Fetch(CollectionFetchMode.Join);
+ },
+ h => h.Component(cmp =>
+ {
+ cmp.Property(x => x.DummyString);
+ cmp.ManyToOne(x => x.ComponentReference);
+ }));
+ });
+ mapper.Class(rc =>
+ {
+ rc.Id(x => x.Id, map => map.Generator(Generators.GuidComb));
+ rc.Lazy(false);
+ rc.ManyToOne(x => x.Parent, m => m.NotNullable(true));
+ });
+ mapper.Class(rc =>
+ {
+ rc.Id(x => x.Id, map => map.Generator(Generators.GuidComb));
+ rc.Lazy(false);
+ rc.Property(x => x.Name);
+ });
+
+ return mapper.CompileMappingForAllExplicitlyAddedEntities();
+ }
+ }
+}
diff --git a/src/NHibernate.Test/NHibernate.Test.csproj b/src/NHibernate.Test/NHibernate.Test.csproj
index 1076bd8b1cd..95f20cc3e32 100644
--- a/src/NHibernate.Test/NHibernate.Test.csproj
+++ b/src/NHibernate.Test/NHibernate.Test.csproj
@@ -63,7 +63,7 @@
-
+
diff --git a/src/NHibernate.Test/StaticProxyTest/StaticProxyFactoryFixture.cs b/src/NHibernate.Test/StaticProxyTest/StaticProxyFactoryFixture.cs
index 1f18e170e8a..722bb604af2 100644
--- a/src/NHibernate.Test/StaticProxyTest/StaticProxyFactoryFixture.cs
+++ b/src/NHibernate.Test/StaticProxyTest/StaticProxyFactoryFixture.cs
@@ -816,6 +816,50 @@ private static BinaryFormatter GetFormatter()
#endif
}
+#if NETCOREAPP3_1_OR_GREATER
+ public interface IWithStaticMethods
+ {
+ // C# 8
+ static void StaticMethod()
+ {
+ }
+
+#if NET7_0_OR_GREATER
+ // C# 11
+ static abstract void StaticAbstractMethod();
+
+ // C# 11
+ static virtual void StaticVirtualMethod()
+ {
+ }
+#endif
+ }
+
+ public class ClassWithStaticInterfaceMethods : IWithStaticMethods
+ {
+ public static void StaticAbstractMethod()
+ {
+ }
+ }
+
+ [Test(Description = "GH3295")]
+ public void VerifyProxyForClassWithStaticInterfaceMethod()
+ {
+ var factory = new StaticProxyFactory();
+ factory.PostInstantiate(
+ typeof(ClassWithStaticInterfaceMethods).FullName,
+ typeof(ClassWithStaticInterfaceMethods),
+ new HashSet { typeof(INHibernateProxy) },
+ null, null, null, true);
+
+ var proxy = factory.GetProxy(1, null);
+ Assert.That(proxy, Is.Not.Null);
+ Assert.That(proxy, Is.InstanceOf());
+
+ Assert.That(factory.GetFieldInterceptionProxy(), Is.InstanceOf());
+ }
+#endif
+
#if NETFX
private static void VerifyGeneratedAssembly(System.Action assemblyGenerator)
{
diff --git a/src/NHibernate.Test/UtilityTest/SetSnapShotFixture.cs b/src/NHibernate.Test/UtilityTest/SetSnapShotFixture.cs
index e00af722111..47b03b50ec9 100644
--- a/src/NHibernate.Test/UtilityTest/SetSnapShotFixture.cs
+++ b/src/NHibernate.Test/UtilityTest/SetSnapShotFixture.cs
@@ -1,7 +1,10 @@
-using System.Collections.Generic;
+using System;
+using System.Collections;
+using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using NHibernate.Collection.Generic.SetHelpers;
+using NSubstitute.ExceptionExtensions;
using NUnit.Framework;
namespace NHibernate.Test.UtilityTest
@@ -70,6 +73,29 @@ public void TestCopyTo()
Assert.That(list, Is.EquivalentTo(array));
}
+ [Test]
+ public void TestCopyToObjectArray()
+ {
+ var list = new List { "test1", null, "test2" };
+ ICollection sn = new SetSnapShot(list);
+
+ var array = new object[3];
+ sn.CopyTo(array, 0);
+ Assert.That(list, Is.EquivalentTo(array));
+ }
+
+ [Test]
+ public void WhenCopyToIsCalledWithIncompatibleArrayTypeThenThrowArgumentOrInvalidCastException()
+ {
+ var list = new List { "test1", null, "test2" };
+ ICollection sn = new SetSnapShot(list);
+
+ var array = new int[3];
+ Assert.That(
+ () => sn.CopyTo(array, 0),
+ Throws.ArgumentException.Or.TypeOf());
+ }
+
[Test]
public void TestSerialization()
{
diff --git a/src/NHibernate.TestDatabaseSetup/NHibernate.TestDatabaseSetup.csproj b/src/NHibernate.TestDatabaseSetup/NHibernate.TestDatabaseSetup.csproj
index 7acfa11bfc3..12c33643f9a 100644
--- a/src/NHibernate.TestDatabaseSetup/NHibernate.TestDatabaseSetup.csproj
+++ b/src/NHibernate.TestDatabaseSetup/NHibernate.TestDatabaseSetup.csproj
@@ -16,7 +16,7 @@
-
+
diff --git a/src/NHibernate.TestDatabaseSetup/TestDatabaseSetup.cs b/src/NHibernate.TestDatabaseSetup/TestDatabaseSetup.cs
index 5dd5fc8fe22..deea09fb3a5 100644
--- a/src/NHibernate.TestDatabaseSetup/TestDatabaseSetup.cs
+++ b/src/NHibernate.TestDatabaseSetup/TestDatabaseSetup.cs
@@ -182,11 +182,7 @@ private static void SetupNpgsql(Cfg.Configuration cfg)
using (var cmd = conn.CreateCommand())
{
- cmd.CommandText =
- @"CREATE OR REPLACE FUNCTION uuid_generate_v4()
- RETURNS uuid
- AS '$libdir/uuid-ossp', 'uuid_generate_v4'
- VOLATILE STRICT LANGUAGE C;";
+ cmd.CommandText = "CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";";
cmd.ExecuteNonQuery();
}
diff --git a/src/NHibernate/Async/Event/Default/AbstractFlushingEventListener.cs b/src/NHibernate/Async/Event/Default/AbstractFlushingEventListener.cs
index dcea3f46739..ae648f8a595 100644
--- a/src/NHibernate/Async/Event/Default/AbstractFlushingEventListener.cs
+++ b/src/NHibernate/Async/Event/Default/AbstractFlushingEventListener.cs
@@ -191,6 +191,7 @@ protected virtual async Task PrepareEntityFlushesAsync(IEventSource session, Can
cancellationToken.ThrowIfCancellationRequested();
log.Debug("processing flush-time cascades");
+ var anything = Anything;
ICollection list = IdentityMap.ConcurrentEntries(session.PersistenceContext.EntityEntries);
//safe from concurrent modification because of how entryList() is implemented on IdentityMap
foreach (DictionaryEntry me in list)
@@ -199,7 +200,7 @@ protected virtual async Task PrepareEntityFlushesAsync(IEventSource session, Can
Status status = entry.Status;
if (status == Status.Loaded || status == Status.Saving || status == Status.ReadOnly)
{
- await (CascadeOnFlushAsync(session, entry.Persister, me.Key, Anything, cancellationToken)).ConfigureAwait(false);
+ await (CascadeOnFlushAsync(session, entry.Persister, me.Key, anything, cancellationToken)).ConfigureAwait(false);
}
}
}
diff --git a/src/NHibernate/Async/Loader/Loader.cs b/src/NHibernate/Async/Loader/Loader.cs
index a7153ea131b..f797434cbe4 100644
--- a/src/NHibernate/Async/Loader/Loader.cs
+++ b/src/NHibernate/Async/Loader/Loader.cs
@@ -802,8 +802,13 @@ private async Task UpdateLazyPropertiesFromResultSetAsync(DbDataReader rs, int i
? persister.EntityMetamodel.BytecodeEnhancementMetadata.GetUninitializedLazyProperties(entry.LoadedState)
: persister.EntityMetamodel.BytecodeEnhancementMetadata.GetUninitializedLazyProperties(obj);
- var updateLazyProperties = fetchLazyProperties?.Intersect(uninitializedProperties).ToArray();
- if (updateLazyProperties?.Length == 0)
+ if (uninitializedProperties.Count == 0)
+ return;
+
+ var updateLazyProperties = fetchAllProperties
+ ? uninitializedProperties.ToArray()
+ : fetchLazyProperties.Intersect(uninitializedProperties).ToArray();
+ if (updateLazyProperties.Length == 0)
{
return; // No new lazy properites were loaded
}
@@ -819,7 +824,7 @@ private async Task UpdateLazyPropertiesFromResultSetAsync(DbDataReader rs, int i
? EntityAliases[i].SuffixedPropertyAliases
: GetSubclassEntityAliases(i, persister);
- if (!await (persister.InitializeLazyPropertiesAsync(rs, id, obj, cols, updateLazyProperties, fetchAllProperties, session, cancellationToken)).ConfigureAwait(false))
+ if (!await (persister.InitializeLazyPropertiesAsync(rs, id, obj, cols, updateLazyProperties, false, session, cancellationToken)).ConfigureAwait(false))
{
return;
}
diff --git a/src/NHibernate/Collection/Generic/SetHelpers/SetSnapShot.cs b/src/NHibernate/Collection/Generic/SetHelpers/SetSnapShot.cs
index a8985c46c03..475bebf6c2e 100644
--- a/src/NHibernate/Collection/Generic/SetHelpers/SetSnapShot.cs
+++ b/src/NHibernate/Collection/Generic/SetHelpers/SetSnapShot.cs
@@ -114,12 +114,16 @@ IEnumerator IEnumerable.GetEnumerator()
void ICollection.CopyTo(Array array, int index)
{
- if (!(array is T[] typedArray))
+ if (array is T[] typedArray)
{
- throw new ArgumentException($"Array must be of type {typeof(T[])}.", nameof(array));
+ CopyTo(typedArray, index);
+ return;
}
- CopyTo(typedArray, index);
+ if (_hasNull)
+ array.SetValue(default(T), index);
+ ICollection keysCollection = _values.Keys;
+ keysCollection.CopyTo(array, index + (_hasNull ? 1 : 0));
}
public int Count => _values.Count + (_hasNull ? 1 : 0);
@@ -153,12 +157,26 @@ protected SetSnapShot(SerializationInfo info, StreamingContext context) : base(i
void ICollection.CopyTo(Array array, int index)
{
- if (!(array is T[] typedArray))
+ if (array is T[] typedArray)
{
- throw new ArgumentException($"Array must be of type {typeof(T[])}.", nameof(array));
+ CopyTo(typedArray, index);
+ return;
}
- CopyTo(typedArray, index);
+ if (array == null)
+ throw new ArgumentNullException(nameof(array));
+
+ if (index < 0)
+ throw new ArgumentOutOfRangeException(nameof(index), index, "Array index cannot be negative");
+
+ if (index > array.Length || Count > array.Length - index)
+ throw new ArgumentException("Provided array is too small", nameof(array));
+
+ foreach (var value in this)
+ {
+ array.SetValue(value, index);
+ index++;
+ }
}
bool ICollection.IsSynchronized => false;
diff --git a/src/NHibernate/Dialect/InformixDialect.cs b/src/NHibernate/Dialect/InformixDialect.cs
index 2f942815a57..4be77acd721 100644
--- a/src/NHibernate/Dialect/InformixDialect.cs
+++ b/src/NHibernate/Dialect/InformixDialect.cs
@@ -333,6 +333,9 @@ public override JoinFragment CreateOuterJoinFragment()
return new InformixJoinFragment();
}
+ ///
+ public override bool SupportsCrossJoin => false;
+
/// The SQL literal value to which this database maps boolean values.
/// The boolean value
/// The appropriate SQL literal.
diff --git a/src/NHibernate/Dialect/InformixDialect0940.cs b/src/NHibernate/Dialect/InformixDialect0940.cs
index bdfcae4f27f..13563df43e8 100644
--- a/src/NHibernate/Dialect/InformixDialect0940.cs
+++ b/src/NHibernate/Dialect/InformixDialect0940.cs
@@ -126,10 +126,7 @@ public override JoinFragment CreateOuterJoinFragment()
return new ANSIJoinFragment();
}
- ///
- public override bool SupportsCrossJoin => false;
-
- ///
+ ///
/// Does this Dialect have some kind of LIMIT syntax?
///
/// False, unless overridden.
diff --git a/src/NHibernate/Driver/NpgsqlDriver.cs b/src/NHibernate/Driver/NpgsqlDriver.cs
index c638e256221..9a4a4b06771 100644
--- a/src/NHibernate/Driver/NpgsqlDriver.cs
+++ b/src/NHibernate/Driver/NpgsqlDriver.cs
@@ -1,3 +1,4 @@
+using System;
using System.Data;
using System.Data.Common;
using NHibernate.AdoNet;
@@ -74,14 +75,37 @@ protected override void InitializeParameter(DbParameter dbParam, string name, Sq
// Since the .NET currency type has 4 decimal places, we use a decimal type in PostgreSQL instead of its native 2 decimal currency type.
dbParam.DbType = DbType.Decimal;
}
- else if (DriverVersionMajor < 6 || sqlType.DbType != DbType.DateTime)
+ else
{
dbParam.DbType = sqlType.DbType;
}
- else
+ }
+
+ public override void AdjustCommand(DbCommand command)
+ {
+ if (DriverVersionMajor >= 6)
{
- // Let Npgsql 6 driver to decide parameter type
+ for (var i = 0; i < command.Parameters.Count; i++)
+ {
+ var parameter = command.Parameters[i];
+ if (parameter.DbType == DbType.DateTime &&
+ parameter.Value is DateTime dateTime &&
+ dateTime.Kind != DateTimeKind.Utc)
+ {
+ // There are breaking changes in Npgsql 6 as following:
+ // UTC DateTime is now strictly mapped to timestamptz,
+ // while Local/Unspecified DateTime is now strictly mapped to timestamp.
+ //
+ // DbType.DateTime now maps to timestamptz, not timestamp.
+ // DbType.DateTime2 continues to map to timestamp
+ //
+ // See more details here: https://www.npgsql.org/doc/release-notes/6.0.html#detailed-notes
+ parameter.DbType = DbType.DateTime2;
+ }
+ }
}
+
+ base.AdjustCommand(command);
}
// Prior to v3, Npgsql was expecting DateTime for time.
diff --git a/src/NHibernate/Event/Default/AbstractFlushingEventListener.cs b/src/NHibernate/Event/Default/AbstractFlushingEventListener.cs
index f9f968a4b2a..979ea774c8c 100644
--- a/src/NHibernate/Event/Default/AbstractFlushingEventListener.cs
+++ b/src/NHibernate/Event/Default/AbstractFlushingEventListener.cs
@@ -188,6 +188,7 @@ protected virtual void PrepareEntityFlushes(IEventSource session)
{
log.Debug("processing flush-time cascades");
+ var anything = Anything;
ICollection list = IdentityMap.ConcurrentEntries(session.PersistenceContext.EntityEntries);
//safe from concurrent modification because of how entryList() is implemented on IdentityMap
foreach (DictionaryEntry me in list)
@@ -196,7 +197,7 @@ protected virtual void PrepareEntityFlushes(IEventSource session)
Status status = entry.Status;
if (status == Status.Loaded || status == Status.Saving || status == Status.ReadOnly)
{
- CascadeOnFlush(session, entry.Persister, me.Key, Anything);
+ CascadeOnFlush(session, entry.Persister, me.Key, anything);
}
}
}
diff --git a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs
index 7cc0bcae9c6..ac22725e582 100644
--- a/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs
+++ b/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs
@@ -511,7 +511,7 @@ internal string GetEntitySuffix(FromElement fromElement)
internal string GetCollectionSuffix(FromElement fromElement)
{
- if (!fromElement.CollectionJoin && fromElement.QueryableCollection == null)
+ if (fromElement.QueryableCollection == null)
{
return null;
}
diff --git a/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs b/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs
index f0ac747bf4d..4b0f127a2ca 100644
--- a/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs
+++ b/src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs
@@ -398,7 +398,7 @@ private void DereferenceEntity(EntityType entityType, bool implicitJoin, string
bool joinIsNeeded;
//For nullable entity comparisons we always need to add join (like not constrained one-to-one or not-found ignore associations)
- bool comparisonWithNullableEntity = entityType.IsNullable && Walker.IsComparativeExpressionClause;
+ bool comparisonWithNullableEntity = entityType.IsNullable && Walker.IsComparativeExpressionClause && !IsCorrelatedSubselect;
if ( IsDotNode( parent ) )
{
diff --git a/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetch.cs b/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetch.cs
index a042af3c12d..31401f7df81 100644
--- a/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetch.cs
+++ b/src/NHibernate/Linq/Visitors/ResultOperatorProcessors/ProcessFetch.cs
@@ -52,6 +52,16 @@ private void Process(
{
var metadata = queryModelVisitor.VisitorParameters.SessionFactory
.GetClassMetadata(resultOperator.RelationMember.ReflectedType);
+ if (metadata == null)
+ {
+ var entityName = queryModelVisitor.VisitorParameters.SessionFactory.GetImplementors(
+ resultOperator.RelationMember.ReflectedType.FullName).FirstOrDefault();
+ if (!string.IsNullOrEmpty(entityName))
+ {
+ metadata = queryModelVisitor.VisitorParameters.SessionFactory.GetClassMetadata(entityName);
+ }
+ }
+
propType = metadata?.GetPropertyType(resultOperator.RelationMember.Name);
}
diff --git a/src/NHibernate/Loader/JoinWalker.cs b/src/NHibernate/Loader/JoinWalker.cs
index 731ee2cc670..e2650582718 100644
--- a/src/NHibernate/Loader/JoinWalker.cs
+++ b/src/NHibernate/Loader/JoinWalker.cs
@@ -192,7 +192,15 @@ private void AddAssociationToJoinTree(IAssociationType type, string[] aliasedLhs
if (qc != null)
{
- _joinQueue.Enqueue(new CollectionJoinQueueEntry(qc, subalias, path, pathAlias));
+ var collection = new CollectionJoinQueueEntry(qc, subalias, path, pathAlias);
+ // Many-to-Many element entity join needs to be added right after collection bridge table
+ // (see IsManyToManyWith, ManyToManySelectFragment, IsManyToManyRoot usages)
+ if (qc.IsManyToMany)
+ {
+ collection.Walk(this);
+ return;
+ }
+ _joinQueue.Enqueue(collection);
}
else if (joinable is IOuterJoinLoadable jl)
{
@@ -367,6 +375,7 @@ private void WalkCollectionTree(IQueryableCollection persister, string alias, st
}
else if (type.IsComponentType)
{
+ _joinQueue.Enqueue(NextLevelJoinQueueEntry.Instance);
WalkCompositeElementTree(
(IAbstractComponentType) type,
persister.ElementColumnNames,
diff --git a/src/NHibernate/Loader/Loader.cs b/src/NHibernate/Loader/Loader.cs
index 53294db26de..ad3b5109021 100644
--- a/src/NHibernate/Loader/Loader.cs
+++ b/src/NHibernate/Loader/Loader.cs
@@ -1208,8 +1208,13 @@ private void UpdateLazyPropertiesFromResultSet(DbDataReader rs, int i, object ob
? persister.EntityMetamodel.BytecodeEnhancementMetadata.GetUninitializedLazyProperties(entry.LoadedState)
: persister.EntityMetamodel.BytecodeEnhancementMetadata.GetUninitializedLazyProperties(obj);
- var updateLazyProperties = fetchLazyProperties?.Intersect(uninitializedProperties).ToArray();
- if (updateLazyProperties?.Length == 0)
+ if (uninitializedProperties.Count == 0)
+ return;
+
+ var updateLazyProperties = fetchAllProperties
+ ? uninitializedProperties.ToArray()
+ : fetchLazyProperties.Intersect(uninitializedProperties).ToArray();
+ if (updateLazyProperties.Length == 0)
{
return; // No new lazy properites were loaded
}
@@ -1225,7 +1230,7 @@ private void UpdateLazyPropertiesFromResultSet(DbDataReader rs, int i, object ob
? EntityAliases[i].SuffixedPropertyAliases
: GetSubclassEntityAliases(i, persister);
- if (!persister.InitializeLazyProperties(rs, id, obj, cols, updateLazyProperties, fetchAllProperties, session))
+ if (!persister.InitializeLazyProperties(rs, id, obj, cols, updateLazyProperties, false, session))
{
return;
}
diff --git a/src/NHibernate/Proxy/ProxyBuilderHelper.cs b/src/NHibernate/Proxy/ProxyBuilderHelper.cs
index 64ee3879e67..7f922911616 100644
--- a/src/NHibernate/Proxy/ProxyBuilderHelper.cs
+++ b/src/NHibernate/Proxy/ProxyBuilderHelper.cs
@@ -21,6 +21,8 @@ namespace NHibernate.Proxy
{
internal static class ProxyBuilderHelper
{
+ private const BindingFlags ProxiableMethodsBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
+
private static readonly ConstructorInfo ObjectConstructor = typeof(object).GetConstructor(System.Type.EmptyTypes);
private static readonly ConstructorInfo SecurityCriticalAttributeConstructor = typeof(SecurityCriticalAttribute).GetConstructor(System.Type.EmptyTypes);
private static readonly ConstructorInfo IgnoresAccessChecksToAttributeConstructor = typeof(IgnoresAccessChecksToAttribute).GetConstructor(new[] {typeof(string)});
@@ -94,10 +96,7 @@ internal static void CallDefaultBaseConstructor(ILGenerator il, System.Type pare
internal static IEnumerable GetProxiableMethods(System.Type type)
{
- const BindingFlags candidateMethodsBindingFlags =
- BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
-
- return type.GetMethods(candidateMethodsBindingFlags).Where(m => m.IsProxiable());
+ return type.GetMethods(ProxiableMethodsBindingFlags).Where(m => m.IsProxiable());
}
internal static IEnumerable GetProxiableMethods(System.Type type, IEnumerable interfaces)
@@ -105,12 +104,12 @@ internal static IEnumerable GetProxiableMethods(System.Type type, IE
if (type.IsInterface || type == typeof(object) || type.GetInterfaces().Length == 0)
{
return GetProxiableMethods(type)
- .Concat(interfaces.SelectMany(i => i.GetMethods()))
+ .Concat(interfaces.SelectMany(i => i.GetMethods(ProxiableMethodsBindingFlags)))
.Distinct();
}
var proxiableMethods = new HashSet(GetProxiableMethods(type), new MethodInfoComparer(type));
- foreach (var interfaceMethod in interfaces.SelectMany(i => i.GetMethods()))
+ foreach (var interfaceMethod in interfaces.SelectMany(i => i.GetMethods(ProxiableMethodsBindingFlags)))
{
proxiableMethods.Add(interfaceMethod);
}
diff --git a/src/NHibernate/Tuple/Entity/PocoEntityTuplizer.cs b/src/NHibernate/Tuple/Entity/PocoEntityTuplizer.cs
index 3913d678032..2a79d56ca74 100644
--- a/src/NHibernate/Tuple/Entity/PocoEntityTuplizer.cs
+++ b/src/NHibernate/Tuple/Entity/PocoEntityTuplizer.cs
@@ -12,6 +12,7 @@
using NHibernate.Util;
using System.Runtime.Serialization;
using NHibernate.Bytecode.Lightweight;
+using NHibernate.Intercept;
namespace NHibernate.Tuple.Entity
{
@@ -306,6 +307,16 @@ public override bool IsLifecycleImplementor
public override void SetPropertyValue(object entity, int i, object value)
{
+ // If there is no property setter we need to manually intercept value for proper lazy property handling.
+ if (IsInstrumented && setters[i].PropertyName == null)
+ {
+ IFieldInterceptor interceptor = _enhancementMetadata.ExtractInterceptor(entity);
+ if (interceptor != null)
+ {
+ value = interceptor.Intercept(entity, EntityMetamodel.PropertyNames[i], value, true);
+ }
+ }
+
if (isBytecodeProviderImpl && optimizer?.AccessOptimizer != null)
{
optimizer.AccessOptimizer.SetPropertyValue(entity, i, value);