diff --git a/doc/reference/modules/configuration.xml b/doc/reference/modules/configuration.xml
index a97775563d5..0028ecc58a7 100644
--- a/doc/reference/modules/configuration.xml
+++ b/doc/reference/modules/configuration.xml
@@ -1081,6 +1081,20 @@ in the parameter binding.
+
+
+ sqlite.binaryguid
+
+
+ SQLite can store GUIDs in binary or text form, controlled by the BinaryGuid
+ connection string parameter (default is 'true'). The BinaryGuid setting will affect
+ how to cast GUID to string in SQL. NHibernate will attempt to detect this
+ setting automatically from the connection string, but if the connection
+ or connection string is being handled by the application instead of by NHibernate,
+ you can use the sqlite.binaryguid NHibernate setting to override the behavior.
+ The value can be true or false.
+
+
nhibernate-logger
diff --git a/src/NHibernate.Test/NHSpecificTest/NH3426/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH3426/Fixture.cs
index 6bf273f93da..8da7a871fc1 100644
--- a/src/NHibernate.Test/NHSpecificTest/NH3426/Fixture.cs
+++ b/src/NHibernate.Test/NHSpecificTest/NH3426/Fixture.cs
@@ -1,14 +1,57 @@
using System;
using System.Linq;
+using NHibernate.Cfg;
using NHibernate.Cfg.MappingSchema;
+using NHibernate.Dialect;
using NHibernate.Mapping.ByCode;
using NUnit.Framework;
+using Environment = NHibernate.Cfg.Environment;
namespace NHibernate.Test.NHSpecificTest.NH3426
{
- [TestFixture]
+ ///
+ /// Verify that we can convert a GUID column to a string in the standard GUID format inside
+ /// the database engine.
+ ///
+ [TestFixture(true)]
+ [TestFixture(false)]
public class Fixture : TestCaseMappingByCode
{
+ private readonly bool _useBinaryGuid;
+
+ public Fixture(bool useBinaryGuid)
+ {
+ _useBinaryGuid = useBinaryGuid;
+ }
+
+ protected override bool AppliesTo(Dialect.Dialect dialect)
+ {
+ // For SQLite, we run the tests for both storage modes (SQLite specific setting).
+ if (dialect is SQLiteDialect)
+ return true;
+
+ // For all other dialects, run the tests only once since the storage mode
+ // is not relevant. (We use the case of _useBinaryGuid==true since this is probably
+ // what most engines do internally.)
+ return _useBinaryGuid;
+ }
+
+ protected override void Configure(Configuration configuration)
+ {
+ base.Configure(configuration);
+
+ if (Dialect is SQLiteDialect)
+ {
+ var connStr = configuration.Properties[Environment.ConnectionString];
+
+ if (_useBinaryGuid)
+ connStr += "BinaryGuid=True;";
+ else
+ connStr += "BinaryGuid=False;";
+
+ configuration.Properties[Environment.ConnectionString] = connStr;
+ }
+ }
protected override HbmMapping GetMappings()
{
@@ -56,7 +99,7 @@ public void SelectGuidToString()
.Select(x => new { Id = x.Id.ToString() })
.ToList();
- Assert.AreEqual(id.ToUpper(), list[0].Id.ToUpper());
+ Assert.That(list[0].Id.ToUpper(), Is.EqualTo(id.ToUpper()));
}
}
@@ -98,5 +141,53 @@ public void CompareStringColumnWithNullableGuidToString()
Assert.That(list, Has.Count.EqualTo(1));
}
}
+
+ [Test]
+ public void SelectGuidToStringImplicit()
+ {
+ if (Dialect is SQLiteDialect && _useBinaryGuid)
+ Assert.Ignore("Fails with BinaryGuid=True due to GH-2109. (2019-04-09).");
+
+ if (Dialect is FirebirdDialect || Dialect is MySQLDialect || Dialect is Oracle8iDialect)
+ Assert.Ignore("Since strguid() is not applied, it fails on Firebird, MySQL and Oracle " +
+ "because a simple cast cannot be used for GUID to string conversion on " +
+ "these dialects. See GH-2109.");
+
+ using (var session = OpenSession())
+ {
+ // Verify in-db GUID to string conversion when ToString() is applied to the entity that has
+ // a GUID id column (that is, we deliberately avoid mentioning the Id property). This
+ // exposes bug GH-2109.
+ var list = session.Query()
+ .Select(x => new { Id = x.ToString() })
+ .ToList();
+
+ Assert.That(list[0].Id.ToUpper(), Is.EqualTo(id.ToUpper()));
+ }
+ }
+
+ [Test]
+ public void WhereGuidToStringImplicit()
+ {
+ if (Dialect is SQLiteDialect && _useBinaryGuid)
+ Assert.Ignore("Fails with BinaryGuid=True due to GH-2109. (2019-04-09).");
+
+ if (Dialect is FirebirdDialect || Dialect is MySQLDialect || Dialect is Oracle8iDialect)
+ Assert.Ignore("Since strguid() is not applied, it fails on Firebird, MySQL and Oracle " +
+ "because a simple cast cannot be used for GUID to string conversion on " +
+ "these dialects. See GH-2109.");
+
+ using (var session = OpenSession())
+ {
+ // Verify in-db GUID to string conversion when ToString() is applied to the entity that has
+ // a GUID id column (that is, we deliberately avoid mentioning the Id property). This
+ // exposes bug GH-2109.
+ var list = session.Query()
+ .Where(x => x.ToString().ToUpper() == id)
+ .ToList();
+
+ Assert.That(list, Has.Count.EqualTo(1));
+ }
+ }
}
}
diff --git a/src/NHibernate/Cfg/Environment.cs b/src/NHibernate/Cfg/Environment.cs
index 173454971de..6a0faf96b40 100644
--- a/src/NHibernate/Cfg/Environment.cs
+++ b/src/NHibernate/Cfg/Environment.cs
@@ -280,6 +280,18 @@ public static string Version
///
public const string FirebirdDisableParameterCasting = "firebird.disable_parameter_casting";
+ ///
+ ///
+ /// SQLite can store GUIDs in binary or text form, controlled by the BinaryGuid
+ /// connection string parameter (default is 'true'). The BinaryGuid setting will affect
+ /// how to cast GUID to string in SQL. NHibernate will attempt to detect this
+ /// setting automatically from the connection string, but if the connection
+ /// or connection string is being handled by the application instead of by NHibernate,
+ /// you can use the 'sqlite.binaryguid' NHibernate setting to override the behavior.
+ ///
+ ///
+ public const string SqliteBinaryGuid = "sqlite.binaryguid";
+
///
/// Set whether tracking the session id or not. When , each session
/// will have an unique that can be retrieved by ,
@@ -540,5 +552,40 @@ private static IObjectsFactory CreateCustomObjectsFactory(string assemblyQualifi
}
}
+
+ ///
+ /// Get a named connection string, if configured.
+ ///
+ ///
+ /// Thrown when a was found
+ /// in the settings parameter but could not be found in the app.config.
+ ///
+ internal static string GetNamedConnectionString(IDictionary settings)
+ {
+ string connStringName;
+ if (!settings.TryGetValue(ConnectionStringName, out connStringName))
+ return null;
+
+ ConnectionStringSettings connectionStringSettings = ConfigurationManager.ConnectionStrings[connStringName];
+ if (connectionStringSettings == null)
+ throw new HibernateException($"Could not find named connection string '{connStringName}'.");
+
+ return connectionStringSettings.ConnectionString;
+ }
+
+
+ ///
+ /// Get the configured connection string, from if that
+ /// is set, otherwise from , or null if that isn't
+ /// set either.
+ ///
+ internal static string GetConfiguredConnectionString(IDictionary settings)
+ {
+ // Connection string in the configuration overrides named connection string.
+ if (!settings.TryGetValue(ConnectionString, out string connString))
+ connString = GetNamedConnectionString(settings);
+
+ return connString;
+ }
}
}
diff --git a/src/NHibernate/Connection/ConnectionProvider.cs b/src/NHibernate/Connection/ConnectionProvider.cs
index 0d908f1f672..4ca2ce9424e 100644
--- a/src/NHibernate/Connection/ConnectionProvider.cs
+++ b/src/NHibernate/Connection/ConnectionProvider.cs
@@ -65,22 +65,15 @@ public virtual void Configure(IDictionary settings)
}
///
- /// Get the .NET 2.0 named connection string
+ /// Get a named connection string, if configured.
///
///
/// Thrown when a was found
- /// in the settings parameter but could not be found in the app.config
+ /// in the settings parameter but could not be found in the app.config.
///
protected virtual string GetNamedConnectionString(IDictionary settings)
{
- string connStringName;
- if(!settings.TryGetValue(Environment.ConnectionStringName, out connStringName))
- return null;
-
- ConnectionStringSettings connectionStringSettings = ConfigurationManager.ConnectionStrings[connStringName];
- if (connectionStringSettings == null)
- throw new HibernateException(string.Format("Could not find named connection string {0}", connStringName));
- return connectionStringSettings.ConnectionString;
+ return Environment.GetNamedConnectionString(settings);
}
///
diff --git a/src/NHibernate/Dialect/SQLiteDialect.cs b/src/NHibernate/Dialect/SQLiteDialect.cs
index dd72bdf5d8a..1d056f4603c 100644
--- a/src/NHibernate/Dialect/SQLiteDialect.cs
+++ b/src/NHibernate/Dialect/SQLiteDialect.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Text;
@@ -18,6 +19,13 @@ namespace NHibernate.Dialect
///
public class SQLiteDialect : Dialect
{
+ ///
+ /// The effective value of the BinaryGuid connection string parameter.
+ /// The default value in SQLite is true.
+ ///
+ private bool _binaryGuid = true;
+
+
///
///
///
@@ -94,8 +102,50 @@ protected virtual void RegisterFunctions()
// NH-3787: SQLite requires the cast in SQL too for not defaulting to string.
RegisterFunction("transparentcast", new CastFunction());
-
- RegisterFunction("strguid", new SQLFunctionTemplate(NHibernateUtil.String, "substr(hex(?1), 7, 2) || substr(hex(?1), 5, 2) || substr(hex(?1), 3, 2) || substr(hex(?1), 1, 2) || '-' || substr(hex(?1), 11, 2) || substr(hex(?1), 9, 2) || '-' || substr(hex(?1), 15, 2) || substr(hex(?1), 13, 2) || '-' || substr(hex(?1), 17, 4) || '-' || substr(hex(?1), 21) "));
+
+ if (_binaryGuid)
+ RegisterFunction("strguid", new SQLFunctionTemplate(NHibernateUtil.String, "substr(hex(?1), 7, 2) || substr(hex(?1), 5, 2) || substr(hex(?1), 3, 2) || substr(hex(?1), 1, 2) || '-' || substr(hex(?1), 11, 2) || substr(hex(?1), 9, 2) || '-' || substr(hex(?1), 15, 2) || substr(hex(?1), 13, 2) || '-' || substr(hex(?1), 17, 4) || '-' || substr(hex(?1), 21) "));
+ else
+ RegisterFunction("strguid", new SQLFunctionTemplate(NHibernateUtil.String, "cast(?1 as char)"));
+ }
+
+
+ public override void Configure(IDictionary settings)
+ {
+ base.Configure(settings);
+
+ ConfigureBinaryGuid(settings);
+
+ // Re-register functions depending on settings.
+ RegisterFunctions();
+ }
+
+ private void ConfigureBinaryGuid(IDictionary settings)
+ {
+ // We can use a SQLite specific setting to force it, but in common cases it
+ // should be detected automatically from the connection string below.
+ settings.TryGetValue(Cfg.Environment.SqliteBinaryGuid, out var strBinaryGuid);
+
+ if (string.IsNullOrWhiteSpace(strBinaryGuid))
+ {
+ string connectionString = Cfg.Environment.GetConfiguredConnectionString(settings);
+ if (!string.IsNullOrWhiteSpace(connectionString))
+ {
+ var builder = new DbConnectionStringBuilder {ConnectionString = connectionString};
+
+ strBinaryGuid = GetConnectionStringProperty(builder, "BinaryGuid");
+ }
+ }
+
+ // Note that "BinaryGuid=false" is supported by System.Data.SQLite but not Microsoft.Data.Sqlite.
+
+ _binaryGuid = string.IsNullOrWhiteSpace(strBinaryGuid) || bool.Parse(strBinaryGuid);
+ }
+
+ private string GetConnectionStringProperty(DbConnectionStringBuilder builder, string propertyName)
+ {
+ builder.TryGetValue(propertyName, out object propertyValue);
+ return (string) propertyValue;
}
#region private static readonly string[] DialectKeywords = { ... }