diff --git a/src/NHibernate.Test/NHSpecificTest/GH3657/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH3657/Entity.cs
new file mode 100644
index 00000000000..75b7aa6e253
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH3657/Entity.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace NHibernate.Test.NHSpecificTest.GH3657
+{
+ class Entity
+ {
+ public virtual Guid Id { get; set; }
+ public virtual string Name { get; set; }
+ }
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/GH3657/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/GH3657/Fixture.cs
new file mode 100644
index 00000000000..ec931e6fc80
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH3657/Fixture.cs
@@ -0,0 +1,54 @@
+using log4net;
+using NHibernate.Cfg;
+using NHibernate.Impl;
+using NUnit.Framework;
+
+namespace NHibernate.Test.NHSpecificTest.GH3657
+{
+ [TestFixture]
+ public class Fixture
+ {
+ private static readonly ILog _log = LogManager.GetLogger(typeof(Fixture));
+ private const string TestSessionFactoryName = "TestName";
+
+ private Configuration _cfg;
+ private ISessionFactory _builtSessionFactory;
+
+ [OneTimeSetUp]
+ public void TestFixtureSetUp()
+ {
+ _cfg = TestConfigurationHelper.GetDefaultConfiguration();
+ var type = GetType();
+ _cfg.AddResource(type.Namespace + ".Mappings.hbm.xml", type.Assembly);
+ _cfg.SetProperty(Environment.SessionFactoryName, TestSessionFactoryName);
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ _builtSessionFactory?.Dispose();
+ _builtSessionFactory = null;
+ }
+
+ private ISessionFactory SessionFactoryBuilder()
+ {
+ Assert.That(_builtSessionFactory, Is.Null, "SessionFactory was already built");
+
+ _builtSessionFactory = _cfg.BuildSessionFactory();
+ _log.Info("Successfully built session factory");
+
+ return _builtSessionFactory;
+ }
+
+ [Test]
+ public void GetOrAddTwice()
+ {
+ var factory = SessionFactoryObjectFactory.GetOrBuildNamedInstance(TestSessionFactoryName, SessionFactoryBuilder);
+ Assert.That(factory, Is.Not.Null, "Failed to get the factory once");
+
+ var factory2 = SessionFactoryObjectFactory.GetOrBuildNamedInstance(TestSessionFactoryName, SessionFactoryBuilder);
+ Assert.That(factory2, Is.Not.Null, "Failed to get the factory twice");
+ Assert.That(factory, Is.SameAs(factory2), "The two factories should be the same");
+ }
+ }
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/GH3657/Mappings.hbm.xml b/src/NHibernate.Test/NHSpecificTest/GH3657/Mappings.hbm.xml
new file mode 100644
index 00000000000..0f099887505
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH3657/Mappings.hbm.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/NHibernate/Impl/SessionFactoryObjectFactory.cs b/src/NHibernate/Impl/SessionFactoryObjectFactory.cs
index 2936f5c2405..fa768d2d8d3 100644
--- a/src/NHibernate/Impl/SessionFactoryObjectFactory.cs
+++ b/src/NHibernate/Impl/SessionFactoryObjectFactory.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
@@ -78,16 +79,46 @@ public static void RemoveInstance(string uid, string name, IDictionary
- /// Returns a Named Instance of the SessionFactory from the local "cache" identified by name.
+ /// Get an instance of the SessionFactory from the local "cache" identified by name if it
+ /// exists, otherwise run the provided factory and return its result.
///
/// The name of the ISessionFactory.
+ /// The ISessionFactory factory to use if the instance is not
+ /// found.
/// An instantiated ISessionFactory.
+ ///
+ /// It is the caller responsibility to ensure
+ /// will add and yield a session factory of the requested .
+ /// If the session factory instantiation is performed concurrently outside of a
+ /// GetOrAddNamedInstance call, this method may yield an instance of it still being
+ /// built, which may lead to threading issues.
+ ///
+ [MethodImpl(MethodImplOptions.Synchronized)]
+ public static ISessionFactory GetOrBuildNamedInstance(string name, Func instanceBuilder)
+ {
+ if (instanceBuilder == null)
+ throw new ArgumentNullException(nameof(instanceBuilder));
+
+ if (NamedInstances.TryGetValue(name, out var factory))
+ return factory;
+ return instanceBuilder();
+ }
+
+ ///
+ /// Returns a Named Instance of the SessionFactory from the local "cache" identified by name.
+ ///
+ /// The name of the ISessionFactory.
+ /// An ISessionFactory if found, otherwise.
+ /// If the session factory instantiation is performed concurrently, this method
+ /// may yield an instance of it still being built, which may lead to threading issues.
+ /// Use to get or
+ /// built the session factory in such case.
[MethodImpl(MethodImplOptions.Synchronized)]
public static ISessionFactory GetNamedInstance(string name)
{
log.Debug("lookup: name={0}", name);
ISessionFactory factory;
- bool found=NamedInstances.TryGetValue(name, out factory);
+ bool found = NamedInstances.TryGetValue(name, out factory);
if (!found)
{
log.Warn("Not found: {0}", name);
@@ -99,7 +130,7 @@ public static ISessionFactory GetNamedInstance(string name)
/// Returns an Instance of the SessionFactory from the local "cache" identified by UUID.
///
/// The identifier of the ISessionFactory.
- /// An instantiated ISessionFactory.
+ /// An ISessionFactory if found, otherwise.
[MethodImpl(MethodImplOptions.Synchronized)]
public static ISessionFactory GetInstance(string uid)
{