diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2673/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2673/Fixture.cs
new file mode 100644
index 00000000000..911f59f0585
--- /dev/null
+++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2673/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.IO;
+using System.Runtime.Serialization;
+using System.Runtime.Serialization.Formatters.Binary;
+using NHibernate.Cfg.MappingSchema;
+using NHibernate.Mapping.ByCode;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.GH2673
+{
+ using System.Threading.Tasks;
+ [TestFixture(true)]
+ [TestFixture(false)]
+ public class FixtureAsync : TestCaseMappingByCode
+ {
+ private readonly bool _withLazyProperties;
+
+ public FixtureAsync(bool withLazyProperties)
+ {
+ _withLazyProperties = withLazyProperties;
+ }
+
+ protected override void OnSetUp()
+ {
+ using (var session = OpenSession())
+ using (var transaction = session.BeginTransaction())
+ {
+ var role1 = new Role {Id = 1, Name = "role1"};
+ session.Save(role1);
+ var role2 = new Role {Id = 2, Name = "role2"};
+ session.Save(role2);
+
+ var r1 = new Resource() {Id = 1, Name = "r1", ResourceRole = role1};
+ session.Save(r1);
+
+ var r2 = new Resource() {Id = 2, Name = "r2", ResourceRole = role2};
+ session.Save(r2);
+
+ var r3 = new Resource() {Id = 3, Name = "r3", ResourceRole = role2};
+ session.Save(r3);
+
+ r1.Manager = r2;
+ r2.Manager = r3;
+ r3.Manager = r1;
+ 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 DeserializeSameTypeAssociationWithInitializedProxyAndCircularReferencesAsync()
+ {
+ using (var session = OpenSession())
+ {
+ var r1 = await (session.LoadAsync(1));
+ var r2 = await (session.LoadAsync(2));
+ var r3 = await (session.LoadAsync(3));
+
+ var list = await (session.QueryOver()
+ .Fetch(SelectMode.Fetch, res => res.Manager)
+ .ListAsync());
+
+ try
+ {
+ var serialised = SpoofSerialization(list[0]);
+ SpoofSerialization(session);
+ }
+ catch (SerializationException)
+ {
+ //Lazy properties case throws due to circular references. See GH-2563
+ if (!_withLazyProperties)
+ throw;
+ }
+ }
+ }
+
+ [Test]
+ public async Task DeserializeSameTypeAssociationWithInitializedAndNotInitializedProxyAsync()
+ {
+ using (var session = OpenSession())
+ {
+ var r1 = await (session.GetAsync(1));
+ var r2 = await (session.GetAsync(2));
+ var r1Name = r1.Name;
+ var serialised = SpoofSerialization(r1);
+ Assert.That(serialised.Name, Is.EqualTo("r1"));
+ }
+ }
+
+ private T SpoofSerialization(T obj)
+ {
+ var formatter = new BinaryFormatter
+ {
+#if !NETFX
+ SurrogateSelector = new NHibernate.Util.SerializationHelper.SurrogateSelector()
+#endif
+ };
+ var stream = new MemoryStream();
+ formatter.Serialize(stream, obj);
+
+ stream.Position = 0;
+
+ return (T) formatter.Deserialize(stream);
+ }
+
+ protected override HbmMapping GetMappings()
+ {
+ var mapper = new ModelMapper();
+ mapper.Class(
+ m =>
+ {
+ m.Table("ResTable");
+ m.Id(x => x.Id, (i) => i.Generator(Generators.Assigned));
+ m.Property(x => x.Name, x => x.Lazy(_withLazyProperties));
+ m.ManyToOne(x => x.Manager, x => x.ForeignKey("none"));
+ m.ManyToOne(x => x.ResourceRole, x => x.ForeignKey("none"));
+ });
+ mapper.Class(
+ m =>
+ {
+ m.Table("RoleTable");
+ m.Id(x => x.Id, (i) => i.Generator(Generators.Assigned));
+ m.Property(x => x.Name);
+ });
+ return mapper.CompileMappingForAllExplicitlyAddedEntities();
+ }
+ }
+}
diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH2707/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH2707/FixtureByCode.cs
index ed8a151bd42..d4914df99c2 100644
--- a/src/NHibernate.Test/Async/NHSpecificTest/GH2707/FixtureByCode.cs
+++ b/src/NHibernate.Test/Async/NHSpecificTest/GH2707/FixtureByCode.cs
@@ -59,7 +59,7 @@ public async Task EntityAndCustomTypeInConditionalResultAsync()
using (var s = OpenSession())
await ((from x in s.Query()
let parent = x.Parent
- //NH-3005 - Contditional on custom type
+ //NH-3005 - Conditional with custom type
where (parent.IsChiusa ? x.CustomType : parent.CustomType) == x.CustomType
select new
{
diff --git a/src/NHibernate.Test/NHSpecificTest/GH2673/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH2673/Entity.cs
new file mode 100644
index 00000000000..dd65748ec7c
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH2673/Entity.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace NHibernate.Test.NHSpecificTest.GH2673
+{
+ [Serializable]
+ public class Resource
+ {
+ public virtual int Id { get; set; }
+ public virtual Resource Manager { get; set; }
+ public virtual string Name { get; set; }
+ public virtual Role ResourceRole { get; set; }
+ }
+
+ [Serializable]
+ public class Role
+ {
+ public virtual int Id { get; set; }
+ public virtual string Name { get; set; }
+ }
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/GH2673/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH2673/Fixture.cs
new file mode 100644
index 00000000000..1f1410b157d
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH2673/Fixture.cs
@@ -0,0 +1,136 @@
+using System.IO;
+using System.Runtime.Serialization;
+using System.Runtime.Serialization.Formatters.Binary;
+using NHibernate.Cfg.MappingSchema;
+using NHibernate.Mapping.ByCode;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.GH2673
+{
+ [TestFixture(true)]
+ [TestFixture(false)]
+ public class Fixture : TestCaseMappingByCode
+ {
+ private readonly bool _withLazyProperties;
+
+ public Fixture(bool withLazyProperties)
+ {
+ _withLazyProperties = withLazyProperties;
+ }
+
+ protected override void OnSetUp()
+ {
+ using (var session = OpenSession())
+ using (var transaction = session.BeginTransaction())
+ {
+ var role1 = new Role {Id = 1, Name = "role1"};
+ session.Save(role1);
+ var role2 = new Role {Id = 2, Name = "role2"};
+ session.Save(role2);
+
+ var r1 = new Resource() {Id = 1, Name = "r1", ResourceRole = role1};
+ session.Save(r1);
+
+ var r2 = new Resource() {Id = 2, Name = "r2", ResourceRole = role2};
+ session.Save(r2);
+
+ var r3 = new Resource() {Id = 3, Name = "r3", ResourceRole = role2};
+ session.Save(r3);
+
+ r1.Manager = r2;
+ r2.Manager = r3;
+ r3.Manager = r1;
+ 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 DeserializeSameTypeAssociationWithInitializedProxyAndCircularReferences()
+ {
+ using (var session = OpenSession())
+ {
+ var r1 = session.Load(1);
+ var r2 = session.Load(2);
+ var r3 = session.Load(3);
+
+ var list = session.QueryOver()
+ .Fetch(SelectMode.Fetch, res => res.Manager)
+ .List();
+
+ try
+ {
+ var serialised = SpoofSerialization(list[0]);
+ SpoofSerialization(session);
+ }
+ catch (SerializationException)
+ {
+ //Lazy properties case throws due to circular references. See GH-2563
+ if (!_withLazyProperties)
+ throw;
+ }
+ }
+ }
+
+ [Test]
+ public void DeserializeSameTypeAssociationWithInitializedAndNotInitializedProxy()
+ {
+ using (var session = OpenSession())
+ {
+ var r1 = session.Get(1);
+ var r2 = session.Get(2);
+ var r1Name = r1.Name;
+ var serialised = SpoofSerialization(r1);
+ Assert.That(serialised.Name, Is.EqualTo("r1"));
+ }
+ }
+
+ private T SpoofSerialization(T obj)
+ {
+ var formatter = new BinaryFormatter
+ {
+#if !NETFX
+ SurrogateSelector = new NHibernate.Util.SerializationHelper.SurrogateSelector()
+#endif
+ };
+ var stream = new MemoryStream();
+ formatter.Serialize(stream, obj);
+
+ stream.Position = 0;
+
+ return (T) formatter.Deserialize(stream);
+ }
+
+ protected override HbmMapping GetMappings()
+ {
+ var mapper = new ModelMapper();
+ mapper.Class(
+ m =>
+ {
+ m.Table("ResTable");
+ m.Id(x => x.Id, (i) => i.Generator(Generators.Assigned));
+ m.Property(x => x.Name, x => x.Lazy(_withLazyProperties));
+ m.ManyToOne(x => x.Manager, x => x.ForeignKey("none"));
+ m.ManyToOne(x => x.ResourceRole, x => x.ForeignKey("none"));
+ });
+ mapper.Class(
+ m =>
+ {
+ m.Table("RoleTable");
+ m.Id(x => x.Id, (i) => i.Generator(Generators.Assigned));
+ m.Property(x => x.Name);
+ });
+ return mapper.CompileMappingForAllExplicitlyAddedEntities();
+ }
+ }
+}
diff --git a/src/NHibernate/Proxy/NHibernateProxyFactoryInfo.cs b/src/NHibernate/Proxy/NHibernateProxyFactoryInfo.cs
index 70f3af1e2c9..5aa9c0d1624 100644
--- a/src/NHibernate/Proxy/NHibernateProxyFactoryInfo.cs
+++ b/src/NHibernate/Proxy/NHibernateProxyFactoryInfo.cs
@@ -74,5 +74,10 @@ public void GetObjectData(SerializationInfo info, StreamingContext context)
info.AddValue(nameof(_componentIdType), _componentIdType);
info.AddValue(nameof(_isClassProxy), _isClassProxy);
}
+
+ internal NHibernateProxyFactoryInfo Clone()
+ {
+ return new NHibernateProxyFactoryInfo(_entityName, _persistentClass, _interfaces, _getIdentifierMethod, _setIdentifierMethod, _componentIdType, _isClassProxy);
+ }
}
}
diff --git a/src/NHibernate/Proxy/NHibernateProxyObjectReference.cs b/src/NHibernate/Proxy/NHibernateProxyObjectReference.cs
index b717d7061f3..a2c3a362ff5 100644
--- a/src/NHibernate/Proxy/NHibernateProxyObjectReference.cs
+++ b/src/NHibernate/Proxy/NHibernateProxyObjectReference.cs
@@ -51,7 +51,8 @@ public object GetRealObject(StreamingContext context)
[SecurityCritical]
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
- info.AddValue(nameof(_proxyFactoryInfo), _proxyFactoryInfo);
+ //Save a copy as it seems IObjectReference deserialization can't properly handle multiple objects with the same reference
+ info.AddValue(nameof(_proxyFactoryInfo), _proxyFactoryInfo.Clone());
info.AddValue(nameof(_identifier), _identifier);
info.AddValue(nameof(_implementation), _implementation);
}