CVE-2024-39677: Insufficient Information (NVD-CWE-noinfo)
value: NHibernate is an object-relational mapper for the .NET framework. A SQL injection vulnerability exists in some types implementing ILiteralType.ObjectToSQLString. Callers of these methods are exposed to the vulnerability, which includes mappings using inheritance with discriminator values; HQL queries referencing a static field of the application; users of the SqlInsertBuilder and SqlUpdateBuilder utilities, calling their AddColumn overload taking a literal value; and any direct use of the ObjectToSQLString methods for building SQL queries on the user side. This vulnerability is fixed in 5.4.9 and 5.5.2.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-07-08T15:15Z
Repository: https://github.com/nhibernate/nhibernate-core
Language: C#
Files changed: doc/reference/modules/configuration.xml, src/NHibernate.Config.Templates/SapSQLAnywhere.cfg.xml, src/NHibernate.Test/Async/NHSpecificTest/GH3516/FixtureByCode.cs, src/NHibernate.Test/NHSpecificTest/GH3516/Entity.cs, src/NHibernate.Test/NHSpecificTest/GH3516/FixtureByCode.cs, src/NHibernate.Test/NHSpecificTest/GH3516/Hierarchy.cs, src/NHibernate.Test/SqlCommandTest/SqlInsertBuilderFixture.cs, src/NHibernate.Test/TestDialect.cs, src/NHibernate.Test/TestDialects/Oracle10gTestDialect.cs, src/NHibernate.Test/TestDialects/SapSQLAnywhere17TestDialect.cs, src/NHibernate/Async/Type/ByteType.cs, src/NHibernate/Async/Type/Int16Type.cs, src/NHibernate/Async/Type/Int32Type.cs, src/NHibernate/Async/Type/Int64Type.cs, src/NHibernate/Async/Type/TicksType.cs, src/NHibernate/Async/Type/TimeAsTimeSpanType.cs, src/NHibernate/Async/Type/TimeSpanType.cs, src/NHibernate/Async/Type/UInt16Type.cs, src/NHibernate/Async/Type/UInt32Type.cs, src/NHibernate/Async/Type/UInt64Type.cs, src/NHibernate/Cfg/Environment.cs, src/NHibernate/Dialect/DB2Dialect.cs, src/NHibernate/Dialect/Dialect.cs, src/NHibernate/Dialect/IngresDialect.cs, src/NHibernate/Dialect/MsSql2000Dialect.cs, src/NHibernate/Dialect/MySQLDialect.cs, src/NHibernate/Dialect/Oracle8iDialect.cs, src/NHibernate/Dialect/SybaseASA9Dialect.cs, src/NHibernate/Dialect/SybaseSQLAnywhere10Dialect.cs, src/NHibernate/Type/AbstractCharType.cs, src/NHibernate/Type/AbstractDateTimeType.cs, src/NHibernate/Type/AbstractStringType.cs, src/NHibernate/Type/ByteType.cs, src/NHibernate/Type/CharBooleanType.cs, src/NHibernate/Type/DateTimeOffSetType.cs, src/NHibernate/Type/DateType.cs, src/NHibernate/Type/DecimalType.cs, src/NHibernate/Type/DoubleType.cs, src/NHibernate/Type/EnumCharType.cs, src/NHibernate/Type/GuidType.cs, src/NHibernate/Type/Int16Type.cs, src/NHibernate/Type/Int32Type.cs, src/NHibernate/Type/Int64Type.cs, src/NHibernate/Type/SByteType.cs, src/NHibernate/Type/SingleType.cs, src/NHibernate/Type/TicksType.cs, src/NHibernate/Type/TimeAsTimeSpanType.cs, src/NHibernate/Type/TimeSpanType.cs, src/NHibernate/Type/TimeType.cs, src/NHibernate/Type/UInt16Type.cs, src/NHibernate/Type/UInt32Type.cs, src/NHibernate/Type/UInt64Type.cs, src/NHibernate/Type/UriType.cs, src/NHibernate/nhibernate-configuration.xsd
Commit date: 2024-07-03 00:56:52 +0200
Commit message: Handle SQL injection vulnerabilities within ObjectToSQLString (#3547)

Co-authored-by: Alex Zaytsev <hazzik@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Diff: diff --git a/doc/reference/modules/configuration.xml b/doc/reference/modules/configuration.xml
index 15656f556..ee6644cad 100644
--- a/doc/reference/modules/configuration.xml
+++ b/doc/reference/modules/configuration.xml
@@ -869,10 +869,24 @@ var session = sessions.OpenSession(conn);
                                 <emphasis role="strong">eg.</emphasis>
                                 <literal>classname.of.SQLExceptionConverter, assembly</literal>
                             </para>
                         </entry>
                     </row>
+                    <row>
+                        <entry>
+                            <literal>escape_backslash_in_strings</literal>
+                        </entry>
+                        <entry>
+                            Indicates if the database needs to have backslash escaped in string literals.
+                            The default value is dialect dependant. That is <literal>false</literal> for
+                            most dialects.
+                            <para>
+                                <emphasis role="strong">eg.</emphasis> 
+                                <literal>true</literal> | <literal>false</literal>
+                            </para>
+                        </entry>
+                    </row>
                     <row>
                         <entry>
                             <literal>show_sql</literal>
                         </entry>
                         <entry>
@@ -1513,16 +1527,10 @@ in the parameter binding.</programlisting>
                         <row>
                             <entry>PostgreSQL</entry>
                             <entry><literal>NHibernate.Dialect.PostgreSQLDialect</literal></entry>
                             <entry></entry>
                         </row>
-                        <row>
-                            <entry>PostgreSQL</entry>
-                            <entry><literal>NHibernate.Dialect.PostgreSQLDialect</literal></entry>
-                            <entry>
-                            </entry>
-                        </row>
                         <row>
                             <entry>PostgreSQL 8.1</entry>
                             <entry><literal>NHibernate.Dialect.PostgreSQL81Dialect</literal></entry>
                             <entry>
                                 This dialect supports <literal>FOR UPDATE NOWAIT</literal>
diff --git a/src/NHibernate.Config.Templates/SapSQLAnywhere.cfg.xml b/src/NHibernate.Config.Templates/SapSQLAnywhere.cfg.xml
index 9512fef12..1ce5a50bb 100644
--- a/src/NHibernate.Config.Templates/SapSQLAnywhere.cfg.xml
+++ b/src/NHibernate.Config.Templates/SapSQLAnywhere.cfg.xml
@@ -13,9 +13,9 @@ for your own use before compiling tests in Visual Studio.
 		     It also requires to have the MSDTC service running, even for transaction scopes using a
 		     single connection. -->
 		<property name="connection.connection_string">
 			UID=DBA;PWD=sql;Server=localhost;DBN=nhibernate;DBF=c:\nhibernate.db;ASTOP=No;Enlist=false;
 		</property>
-		<property name="dialect">NHibernate.Dialect.SybaseSQLAnywhere12Dialect</property>
+		<property name="dialect">NHibernate.Dialect.SapSQLAnywhere17Dialect</property>
 		<property name="query.substitutions">true=1;false=0</property>
 	</session-factory>
 </hibernate-configuration>
diff --git a/src/NHibernate.Test/Async/NHSpecificTest/GH3516/FixtureByCode.cs b/src/NHibernate.Test/Async/NHSpecificTest/GH3516/FixtureByCode.cs
new file mode 100644
index 000000000..f63dcb437
--- /dev/null
+++ b/src/NHibernate.Test/Async/NHSpecificTest/GH3516/FixtureByCode.cs
@@ -0,0 +1,399 @@
+﻿//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by AsyncGenerator.
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using NHibernate.Cfg.MappingSchema;
+using NHibernate.Mapping.ByCode;
+using NHibernate.SqlTypes;
+using NHibernate.Type;
+using NUnit.Framework;
+using NUnit.Framework.Internal;
+
+namespace NHibernate.Test.NHSpecificTest.GH3516
+{
+	using System.Threading.Tasks;
+	[TestFixture]
+	public class FixtureByCodeAsync : TestCaseMappingByCode
+	{
+
+		private readonly HashSet<string> _unsupportedProperties = new();
+
+		protected override HbmMapping GetMappings()
+		{
+			var mapper = new ModelMapper();
+			mapper.Class<Entity>(rc =>
+			{
+				rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
+				rc.Property(x => x.Name);
+				rc.Property(x => x.FirstChar);
+				rc.Property(x => x.CharacterEnum, m => m.Type<EnumCharType<CharEnum>>());
+				rc.Property(x => x.UriProperty);
+
+				rc.Property(x => x.ByteProperty);
+				rc.Property(x => x.DecimalProperty);
+				rc.Property(x => x.DoubleProperty);
+				rc.Property(x => x.FloatProperty);
+				rc.Property(x => x.ShortProperty);
+				rc.Property(x => x.IntProperty);
+				rc.Property(x => x.LongProperty);
+
+				if (TestDialect.SupportsSqlType(SqlTypeFactory.SByte))
+					rc.Property(x => x.SByteProperty);
+				else
+					_unsupportedProperties.Add(nameof(Entity.SByteProperty));
+
+				if (TestDialect.SupportsSqlType(SqlTypeFactory.UInt16))
+					rc.Property(x => x.UShortProperty);
+				else
+					_unsupportedProperties.Add(nameof(Entity.UShortProperty));
+
+				if (TestDialect.SupportsSqlType(SqlTypeFactory.UInt32))
+					rc.Property(x => x.UIntProperty);
+				else
+					_unsupportedProperties.Add(nameof(Entity.UIntProperty));
+
+				if (TestDialect.SupportsSqlType(SqlTypeFactory.UInt64))
+					rc.Property(x => x.ULongProperty);
+				else
+					_unsupportedProperties.Add(nameof(Entity.ULongProperty));
+
+				rc.Property(x => x.DateTimeProperty);
+				rc.Property(x => x.DateProperty, m => m.Type(NHibernateUtil.Date));
+				if (TestDialect.SupportsSqlType(SqlTypeFactory.DateTimeOffSet))
+					rc.Property(x => x.DateTimeOffsetProperty);
+				else
+					_unsupportedProperties.Add(nameof(Entity.DateTimeOffsetProperty));
+				rc.Property(x => x.TimeProperty, m => m.Type(NHibernateUtil.Time));
+
+				rc.Property(x => x.GuidProperty);
+			});
+
+			mapper.Class<BaseClass>(rc =>
+			{
+				rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
+				rc.Discriminator(x => x.Column("StringDiscriminator"));
+				rc.Property(x => x.Name);
+				rc.Abstract(true);
+			});
+			mapper.Subclass<Subclass1>(rc => rc.DiscriminatorValue(Entity.NameWithSingleQuote));
+			mapper.Subclass<Subclass2>(rc => rc.DiscriminatorValue(Entity.NameWithEscapedSingleQuote));
+
+			mapper.Import<CharEnum>();
+
+			return mapper.CompileMappingForAllExplicitlyAddedEntities();
+		}
+
+		private CultureInfo _backupCulture;
+		private CultureInfo _backupUICulture;
+
+		protected override void OnSetUp()
+		{
+			using var session = OpenSession();
+			using var transaction = session.BeginTransaction();
+			session.Save(
+				new Entity
+				{
+					Name = Entity.NameWithSingleQuote,
+					FirstChar = Entity.QuoteInitial,
+					CharacterEnum = CharEnum.SingleQuote,
+					UriProperty = Entity.UriWithSingleQuote
+				});
+			session.Save(
+				new Entity
+				{
+					Name = Entity.NameWithEscapedSingleQuote,
+					FirstChar = Entity.BackslashInitial,
+					CharacterEnum = CharEnum.Backslash,
+					UriProperty = Entity.UriWithEscapedSingleQuote
+				});
+
+			transaction.Commit();
+
+			_backupCulture = CultureInfo.CurrentCulture;
+			_backupUICulture = CultureInfo.CurrentUICulture;
+		}
+
+		protected override void OnTearDown()
+		{
+			if (_backupCulture != null)
+			{
+				CultureInfo.CurrentCulture = _backupCulture;
+				CultureInfo.CurrentUICulture = _backupUICulture;
+			}
+
+			using var session = OpenSession();
+			using var transaction = session.BeginTransaction();
+			session.CreateQuery("delete from System.Object").ExecuteUpdate();
+
+			transaction.Commit();
+		}
+
+		private static readonly string[] StringInjectionsProperties =
+		{
+			nameof(Entity.NameWithSingleQuote), nameof(Entity.NameWithEscapedSingleQuote)
+		};
+
+		[TestCaseSource(nameof(StringInjectionsProperties))]
+		public void SqlInjectionInStringsAsync(string propertyName)
+		{
+			using var session = OpenSession();
+
+			var query = session.CreateQuery($"from Entity e where e.Name = Entity.{propertyName}");
+			IList<Entity> list = null;
+			Assert.That(async () => list = await (query.ListAsync<Entity>()), Throws.Nothing);
+			Assert.That(list, Has.Count.EqualTo(1), $"Unable to find entity with name {propertyName}");
+		}
+
+		private static readonly string[] SpecialNames =
+		{
+			"\0; drop table Entity; --",
+			"\b; drop table Entity; --",
+			"\n; drop table Entity; --",
+			"\r; drop table Entity; --",
+			"\t; drop table Entity; --",
+			"\x1A; drop table Entity; --",
+			"\"; drop table Entity; --",
+			"\\; drop table Entity; --"
+		};
+
+		[TestCaseSource(nameof(SpecialNames))]
+		public async Task StringsWithSpecialCharactersAsync(string name)
+		{
+			// We may not even be able to insert the entity.
+			var wasInserted = false;
+			try
+			{
+				using var s = OpenSession();
+				using var t = s.BeginTransaction();
+				var e = new Entity { Name = name };
+				await (s.SaveAsync(e));
+				await (t.CommitAsync());
+
+				wasInserted = true;
+			}
+			catch (Exception e)
+			{
+				Assert.Warn($"The entity insertion failed with message {e}");
+			}
+
+			try
+			{
+				using var session = OpenSession();
+				Entity.ArbitraryStringValue = name;
+				var list = await (session.CreateQuery($"from Entity e where e.Name = Entity.{nameof(Entity.ArbitraryStringValue)}").ListAsync<Entity>());
+				if (wasInserted && list.Count != 1)
+					Assert.Warn($"Unable to find entity with name {nameof(Entity.ArbitraryStringValue)}");
+			}
+			catch (Exception e)
+			{
+				Assert.Warn($"The query has failed with message {e}");
+			}
+
+			// Check the db is not wrecked.
+			if (wasInserted)
+			{
+				using var session = OpenSession();
+				var list = await (session
+					.CreateQuery("from Entity e where e.Name = :name")
+					.SetString("name", name)
+					.ListAsync<Entity>());
+				Assert.That(list, Has.Count.EqualTo(1));
+			}
+			else
+			{
+				using var session = OpenSession();
+				var all = await (session.CreateQuery("from Entity e").ListAsync<Entity>());
+				Assert.That(all, Has.Count.GreaterThan(0));
+			}
+		}
+
+		[Test]
+		public async Task SqlInjectionInStringDiscriminatorAsync()
+		{
+			using var session = OpenSession();
+
+			await (session.SaveAsync(new Subclass1 { Name = "Subclass1" }));
+			await (session.SaveAsync(new Subclass2 { Name = "Subclass2" }));
+
+			// ObjectToSQLString is used for generating the inserts.
+			Assert.That(session.Flush, Throws.Nothing, "Unable to flush the subclasses");
+
+			foreach (var entityName in new[] { nameof(Subclass1), nameof(Subclass2) })
+			{
+				var query = session.CreateQuery($"from {entityName}");
+				IList list = null;
+				Assert.That(async () => list = await (query.ListAsync()), Throws.Nothing, $"Unable to list entities of {entityName}");
+				Assert.That(list, Has.Count.EqualTo(1), $"Unable to find the {entityName} entity");
+			}
+		}
+
+		private static readonly string[] CharInjectionsProperties =
+		{
+			nameof(Entity.QuoteInitial), nameof(Entity.BackslashInitial)
+		};
+
+		[TestCaseSource(nameof(CharInjectionsProperties))]
+		public void SqlInjectionInCharAsync(string propertyName)
+		{
+			using var session = OpenSession();
+			var query = session.CreateQuery($"from Entity e where e.FirstChar = Entity.{propertyName}");
+			IList<Entity> list = null;
+			Assert.That(async () => list = await (query.ListAsync<Entity>()), Throws.Nothing);
+			Assert.That(list, Is.Not.Null.And.Count.EqualTo(1), $"Unable to find entity with initial {propertyName}");
+		}
+
+		private static readonly string[] CharEnumInjections =
+		{
+			nameof(CharEnum.SingleQuote), nameof(CharEnum.Backslash)
+		};
+
+		[TestCaseSource(nameof(CharEnumInjections))]
+		public void SqlInjectionWithCharEnumAsync(string enumName)
+		{
+			using var session = OpenSession();
+
+			var query = session.CreateQuery($"from Entity e where e.CharacterEnum = CharEnum.{enumName}");
+			IList<Entity> list = null;
+			Assert.That(async () => list = await (query.ListAsync<Entity>()), Throws.Nothing);
+			Assert.That(list, Has.Count.EqualTo(1), $"Unable to find entity with CharacterEnum {enumName}");
+		}
+
+		private static readonly string[] UriInjections =
+		{
+			nameof(Entity.UriWithSingleQuote), nameof(Entity.UriWithEscapedSingleQuote)
+		};
+
+		[TestCaseSource(nameof(UriInjections))]
+		public void SqlInjectionWithUriAsync(string propertyName)
+		{
+			using var session = OpenSession();
+
+			var query = session.CreateQuery($"from Entity e where e.UriProperty = Entity.{propertyName}");
+			IList<Entity> list = null;
+			Assert.That(async () => list = await (query.ListAsync<Entity>()), Throws.Nothing);
+			Assert.That(list, Has.Count.EqualTo(1), $"Unable to find entity with UriProperty {propertyName}");
+		}
+
+		private static readonly string[] NumericalTypesInjections =
+		{
+			nameof(Entity.ByteProperty),
+			nameof(Entity.DecimalProperty),
+			nameof(Entity.DoubleProperty),
+			nameof(Entity.FloatProperty),
+			nameof(Entity.ShortProperty),
+			nameof(Entity.IntProperty),
+			nameof(Entity.LongProperty),
+			nameof(Entity.SByteProperty),
+			nameof(Entity.UShortProperty),
+			nameof(Entity.UIntProperty),
+			nameof(Entity.ULongProperty)
+		};
+
+		[TestCaseSource(nameof(NumericalTypesInjections))]
+		public async Task SqlInjectionInNumericalTypeAsync(string propertyName)
+		{
+			Assume.That(_unsupportedProperties, Does.Not.Contains((object) propertyName), $"The {propertyName} property is unsupported by the dialect");
+
+			Entity.ArbitraryStringValue = "0; drop table Entity; --";
+			using (var session = OpenSession())
+			{
+				IQuery query;
+				// Defining that query is invalid and should throw.
+				try
+				{
+					query = session.CreateQuery($"from Entity e where e.{propertyName} = Entity.{nameof(Entity.ArbitraryStringValue)}");
+				}
+				catch (Exception ex)
+				{
+					// All good.
+					Assert.Pass($"The wicked query creation has been rejected, as it should: {ex}");
+					// Needed for the compiler who does not know "Pass" always throw.
+					return;
+				}
+
+				// The query definition has been accepted, run it.
+				try
+				{
+					await (query.ListAsync<Entity>());
+				}
+				catch (Exception ex)
+				{
+					// Expecting no exception at that point, but the test is to check if the injection succeeded.
+					Assert.Warn($"The wicked query execution has failed: {ex}");
+				}
+			}
+
+			// Check if we can still query Entity. If it succeeds, at least it means the injection failed.
+			using (var session = OpenSession())
+			{
+				IList<Entity> list = null;
+				Assert.That(async () => list = await (session.CreateQuery("from Entity e").ListAsync<Entity>()), Throws.Nothing);
+				Assert.That(list, Has.Count.GreaterThan(0));
+			}
+		}
+
+		private static readonly string[] DateTypesInjections =
+		{
+			nameof(Entity.DateTimeProperty),
+			nameof(Entity.DateProperty),
+			nameof(Entity.DateTimeOffsetProperty),
+			nameof(Entity.TimeProperty)
+		};
+
+		[TestCaseSource(nameof(DateTypesInjections))]
+		public void SqlInjectionWithDatetimeAsync(string propertyName)
+		{
+			Assume.That(_unsupportedProperties, Does.Not.Contains((object) propertyName), $"The {propertyName} property is unsupported by the dialect");
+
+			var wickedCulture = new CultureInfo("en-US");
+			if (propertyName == nameof(Entity.TimeProperty))
+				wickedCulture.DateTimeFormat.ShortTimePattern = "HH:mm:ss\\'\"; drop table Entity; --\"";
+			else
+				wickedCulture.DateTimeFormat.ShortDatePattern = "yyyy-MM-ddTHH:mm:ss\\'\"; drop table Entity; --\"";
+			CultureInfo.CurrentCulture = wickedCulture;
+			CultureInfo.CurrentUICulture = wickedCulture;
+
+			using var session = OpenSession();
+
+			var staticPropertyName = propertyName == nameof(Entity.DateTimeOffsetProperty) ?
+				nameof(Entity.StaticDateTimeOffsetProperty) : nameof(Entity.StaticDateProperty);
+			var query = session.CreateQuery($"from Entity e where e.{propertyName} = Entity.{staticPropertyName}");
+			IList<Entity> list = null;
+			Assume.That(() => list = query.List<Entity>(), Throws.Nothing,
+				"The first execution of the query failed, the injection has likely failed");
+			// Execute again to check the table is still here.
+			Assert.That(async () => list = await (query.ListAsync<Entity>()), Throws.Nothing,
+				"The second execution of the query failed although the first one did not: the injection has succeeded");
+		}
+
+		private static readonly string[] GuidInjections =
+		{
+			Entity.NameWithSingleQuote, Entity.NameWithEscapedSingleQuote
+		};
+
+		[TestCaseSource(nameof(GuidInjections))]
+		public void SqlInjectionWithGuidAsync(string injection)
+		{
+			Entity.ArbitraryStringValue = $"{Guid.NewGuid()}{injection}";
+			using var session = OpenSession();
+
+			var query = session.CreateQuery($"from Entity e where e.GuidProperty = Entity.{nameof(Entity.ArbitraryStringValue)}");
+			IList<Entity> list = null;
+			Assume.That(() => list = query.List<Entity>(), Throws.Nothing,
+				"The first execution of the query failed, the injection has likely failed");
+			// Execute again to check the table is still here.
+			Assert.That(async () => list = await (query.ListAsync<Entity>()), Throws.Nothing,
+				"The second execution of the query failed although the first one did not: the injection has succeeded");
+		}
+	}
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/GH3516/Entity.cs b/src/NHibernate.Test/NHSpecificTest/GH3516/Entity.cs
new file mode 100644
index 000000000..0326e5575
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH3516/Entity.cs
@@ -0,0 +1,54 @@
+﻿using System;
+
+namespace NHibernate.Test.NHSpecificTest.GH3516
+{
+	public class Entity
+	{
+		public virtual Guid Id { get; set; }
+		public virtual string Name { get; set; }
+		public virtual char FirstChar { get; set; }
+		public virtual CharEnum CharacterEnum { get; set; } = CharEnum.SimpleChar;
+		public virtual Uri UriProperty { get; set; }
+
+		public virtual byte ByteProperty { get; set; }
+		public virtual decimal DecimalProperty { get; set; }
+		public virtual double DoubleProperty { get; set; }
+		public virtual float FloatProperty { get; set; }
+		public virtual short ShortProperty { get; set; }
+		public virtual int IntProperty { get; set; }
+		public virtual long LongProperty { get; set; }
+		public virtual sbyte SByteProperty { get; set; }
+		public virtual ushort UShortProperty { get; set; }
+		public virtual uint UIntProperty { get; set; }
+		public virtual ulong ULongProperty { get; set; }
+
+		public virtual DateTime DateTimeProperty { get; set; } = StaticDateProperty;
+		public virtual DateTime DateProperty { get; set; } = StaticDateProperty;
+		public virtual DateTimeOffset DateTimeOffsetProperty { get; set; } = StaticDateProperty;
+		public virtual DateTime TimeProperty { get; set; } = StaticDateProperty;
+
+		public virtual Guid GuidProperty { get; set; } = Guid.Empty;
+
+		public const string NameWithSingleQuote = "'; drop table Entity; --";
+		public const string NameWithEscapedSingleQuote = @"\'; drop table Entity; --";
+
+		// Do not switch to property, the feature of referencing static fields in HQL does not work with properties.
+		public static string ArbitraryStringValue;
+
+		public const char QuoteInitial = '\'';
+		public const char BackslashInitial = '\\';
+
+		public static readonly Uri UriWithSingleQuote = new("https://somewhere/?sql='; drop table Entity; --");
+		public static readonly Uri UriWithEscapedSingleQuote = new(@"https://somewhere/?sql=\'; drop table Entity; --");
+
+		public static readonly DateTime StaticDateProperty = DateTime.Today;
+		public static readonly DateTimeOffset StaticDateTimeOffsetProperty = DateTimeOffset.Now;
+	}
+
+	public enum CharEnum
+	{
+		SimpleChar = 'A',
+		SingleQuote = '\'',
+		Backslash = '\\'
+	}
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/GH3516/FixtureByCode.cs b/src/NHibernate.Test/NHSpecificTest/GH3516/FixtureByCode.cs
new file mode 100644
index 000000000..235fad90b
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH3516/FixtureByCode.cs
@@ -0,0 +1,388 @@
+﻿using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using NHibernate.Cfg.MappingSchema;
+using NHibernate.Mapping.ByCode;
+using NHibernate.SqlTypes;
+using NHibernate.Type;
+using NUnit.Framework;
+using NUnit.Framework.Internal;
+
+namespace NHibernate.Test.NHSpecificTest.GH3516
+{
+	[TestFixture]
+	public class FixtureByCode : TestCaseMappingByCode
+	{
+
+		private readonly HashSet<string> _unsupportedProperties = new();
+
+		protected override HbmMapping GetMappings()
+		{
+			var mapper = new ModelMapper();
+			mapper.Class<Entity>(rc =>
+			{
+				rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
+				rc.Property(x => x.Name);
+				rc.Property(x => x.FirstChar);
+				rc.Property(x => x.CharacterEnum, m => m.Type<EnumCharType<CharEnum>>());
+				rc.Property(x => x.UriProperty);
+
+				rc.Property(x => x.ByteProperty);
+				rc.Property(x => x.DecimalProperty);
+				rc.Property(x => x.DoubleProperty);
+				rc.Property(x => x.FloatProperty);
+				rc.Property(x => x.ShortProperty);
+				rc.Property(x => x.IntProperty);
+				rc.Property(x => x.LongProperty);
+
+				if (TestDialect.SupportsSqlType(SqlTypeFactory.SByte))
+					rc.Property(x => x.SByteProperty);
+				else
+					_unsupportedProperties.Add(nameof(Entity.SByteProperty));
+
+				if (TestDialect.SupportsSqlType(SqlTypeFactory.UInt16))
+					rc.Property(x => x.UShortProperty);
+				else
+					_unsupportedProperties.Add(nameof(Entity.UShortProperty));
+
+				if (TestDialect.SupportsSqlType(SqlTypeFactory.UInt32))
+					rc.Property(x => x.UIntProperty);
+				else
+					_unsupportedProperties.Add(nameof(Entity.UIntProperty));
+
+				if (TestDialect.SupportsSqlType(SqlTypeFactory.UInt64))
+					rc.Property(x => x.ULongProperty);
+				else
+					_unsupportedProperties.Add(nameof(Entity.ULongProperty));
+
+				rc.Property(x => x.DateTimeProperty);
+				rc.Property(x => x.DateProperty, m => m.Type(NHibernateUtil.Date));
+				if (TestDialect.SupportsSqlType(SqlTypeFactory.DateTimeOffSet))
+					rc.Property(x => x.DateTimeOffsetProperty);
+				else
+					_unsupportedProperties.Add(nameof(Entity.DateTimeOffsetProperty));
+				rc.Property(x => x.TimeProperty, m => m.Type(NHibernateUtil.Time));
+
+				rc.Property(x => x.GuidProperty);
+			});
+
+			mapper.Class<BaseClass>(rc =>
+			{
+				rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
+				rc.Discriminator(x => x.Column("StringDiscriminator"));
+				rc.Property(x => x.Name);
+				rc.Abstract(true);
+			});
+			mapper.Subclass<Subclass1>(rc => rc.DiscriminatorValue(Entity.NameWithSingleQuote));
+			mapper.Subclass<Subclass2>(rc => rc.DiscriminatorValue(Entity.NameWithEscapedSingleQuote));
+
+			mapper.Import<CharEnum>();
+
+			return mapper.CompileMappingForAllExplicitlyAddedEntities();
+		}
+
+		private CultureInfo _backupCulture;
+		private CultureInfo _backupUICulture;
+
+		protected override void OnSetUp()
+		{
+			using var session = OpenSession();
+			using var transaction = session.BeginTransaction();
+			session.Save(
+				new Entity
+				{
+					Name = Entity.NameWithSingleQuote,
+					FirstChar = Entity.QuoteInitial,
+					CharacterEnum = CharEnum.SingleQuote,
+					UriProperty = Entity.UriWithSingleQuote
+				});
+			session.Save(
+				new Entity
+				{
+					Name = Entity.NameWithEscapedSingleQuote,
+					FirstChar = Entity.BackslashInitial,
+					CharacterEnum = CharEnum.Backslash,
+					UriProperty = Entity.UriWithEscapedSingleQuote
+				});
+
+			transaction.Commit();
+
+			_backupCulture = CultureInfo.CurrentCulture;
+			_backupUICulture = CultureInfo.CurrentUICulture;
+		}
+
+		protected override void OnTearDown()
+		{
+			if (_backupCulture != null)
+			{
+				CultureInfo.CurrentCulture = _backupCulture;
+				CultureInfo.CurrentUICulture = _backupUICulture;
+			}
+
+			using var session = OpenSession();
+			using var transaction = session.BeginTransaction();
+			session.CreateQuery("delete from System.Object").ExecuteUpdate();
+
+			transaction.Commit();
+		}
+
+		private static readonly string[] StringInjectionsProperties =
+		{
+			nameof(Entity.NameWithSingleQuote), nameof(Entity.NameWithEscapedSingleQuote)
+		};
+
+		[TestCaseSource(nameof(StringInjectionsProperties))]
+		public void SqlInjectionInStrings(string propertyName)
+		{
+			using var session = OpenSession();
+
+			var query = session.CreateQuery($"from Entity e where e.Name = Entity.{propertyName}");
+			IList<Entity> list = null;
+			Assert.That(() => list = query.List<Entity>(), Throws.Nothing);
+			Assert.That(list, Has.Count.EqualTo(1), $"Unable to find entity with name {propertyName}");
+		}
+
+		private static readonly string[] SpecialNames =
+		{
+			"\0; drop table Entity; --",
+			"\b; drop table Entity; --",
+			"\n; drop table Entity; --",
+			"\r; drop table Entity; --",
+			"\t; drop table Entity; --",
+			"\x1A; drop table Entity; --",
+			"\"; drop table Entity; --",
+			"\\; drop table Entity; --"
+		};
+
+		[TestCaseSource(nameof(SpecialNames))]
+		public void StringsWithSpecialCharacters(string name)
+		{
+			// We may not even be able to insert the entity.
+			var wasInserted = false;
+			try
+			{
+				using var s = OpenSession();
+				using var t = s.BeginTransaction();
+				var e = new Entity { Name = name };
+				s.Save(e);
+				t.Commit();
+
+				wasInserted = true;
+			}
+			catch (Exception e)
+			{
+				Assert.Warn($"The entity insertion failed with message {e}");
+			}
+
+			try
+			{
+				using var session = OpenSession();
+				Entity.ArbitraryStringValue = name;
+				var list = session.CreateQuery($"from Entity e where e.Name = Entity.{nameof(Entity.ArbitraryStringValue)}").List<Entity>();
+				if (wasInserted && list.Count != 1)
+					Assert.Warn($"Unable to find entity with name {nameof(Entity.ArbitraryStringValue)}");
+			}
+			catch (Exception e)
+			{
+				Assert.Warn($"The query has failed with message {e}");
+			}
+
+			// Check the db is not wrecked.
+			if (wasInserted)
+			{
+				using var session = OpenSession();
+				var list = session
+					.CreateQuery("from Entity e where e.Name = :name")
+					.SetString("name", name)
+					.List<Entity>();
+				Assert.That(list, Has.Count.EqualTo(1));
+			}
+			else
+			{
+				using var session = OpenSession();
+				var all = session.CreateQuery("from Entity e").List<Entity>();
+				Assert.That(all, Has.Count.GreaterThan(0));
+			}
+		}
+
+		[Test]
+		public void SqlInjectionInStringDiscriminator()
+		{
+			using var session = OpenSession();
+
+			session.Save(new Subclass1 { Name = "Subclass1" });
+			session.Save(new Subclass2 { Name = "Subclass2" });
+
+			// ObjectToSQLString is used for generating the inserts.
+			Assert.That(session.Flush, Throws.Nothing, "Unable to flush the subclasses");
+
+			foreach (var entityName in new[] { nameof(Subclass1), nameof(Subclass2) })
+			{
+				var query = session.CreateQuery($"from {entityName}");
+				IList list = null;
+				Assert.That(() => list = query.List(), Throws.Nothing, $"Unable to list entities of {entityName}");
+				Assert.That(list, Has.Count.EqualTo(1), $"Unable to find the {entityName} entity");
+			}
+		}
+
+		private static readonly string[] CharInjectionsProperties =
+		{
+			nameof(Entity.QuoteInitial), nameof(Entity.BackslashInitial)
+		};
+
+		[TestCaseSource(nameof(CharInjectionsProperties))]
+		public void SqlInjectionInChar(string propertyName)
+		{
+			using var session = OpenSession();
+			var query = session.CreateQuery($"from Entity e where e.FirstChar = Entity.{propertyName}");
+			IList<Entity> list = null;
+			Assert.That(() => list = query.List<Entity>(), Throws.Nothing);
+			Assert.That(list, Is.Not.Null.And.Count.EqualTo(1), $"Unable to find entity with initial {propertyName}");
+		}
+
+		private static readonly string[] CharEnumInjections =
+		{
+			nameof(CharEnum.SingleQuote), nameof(CharEnum.Backslash)
+		};
+
+		[TestCaseSource(nameof(CharEnumInjections))]
+		public void SqlInjectionWithCharEnum(string enumName)
+		{
+			using var session = OpenSession();
+
+			var query = session.CreateQuery($"from Entity e where e.CharacterEnum = CharEnum.{enumName}");
+			IList<Entity> list = null;
+			Assert.That(() => list = query.List<Entity>(), Throws.Nothing);
+			Assert.That(list, Has.Count.EqualTo(1), $"Unable to find entity with CharacterEnum {enumName}");
+		}
+
+		private static readonly string[] UriInjections =
+		{
+			nameof(Entity.UriWithSingleQuote), nameof(Entity.UriWithEscapedSingleQuote)
+		};
+
+		[TestCaseSource(nameof(UriInjections))]
+		public void SqlInjectionWithUri(string propertyName)
+		{
+			using var session = OpenSession();
+
+			var query = session.CreateQuery($"from Entity e where e.UriProperty = Entity.{propertyName}");
+			IList<Entity> list = null;
+			Assert.That(() => list = query.List<Entity>(), Throws.Nothing);
+			Assert.That(list, Has.Count.EqualTo(1), $"Unable to find entity with UriProperty {propertyName}");
+		}
+
+		private static readonly string[] NumericalTypesInjections =
+		{
+			nameof(Entity.ByteProperty),
+			nameof(Entity.DecimalProperty),
+			nameof(Entity.DoubleProperty),
+			nameof(Entity.FloatProperty),
+			nameof(Entity.ShortProperty),
+			nameof(Entity.IntProperty),
+			nameof(Entity.LongProperty),
+			nameof(Entity.SByteProperty),
+			nameof(Entity.UShortProperty),
+			nameof(Entity.UIntProperty),
+			nameof(Entity.ULongProperty)
+		};
+
+		[TestCaseSource(nameof(NumericalTypesInjections))]
+		public void SqlInjectionInNumericalType(string propertyName)
+		{
+			Assume.That(_unsupportedProperties, Does.Not.Contains((object) propertyName), $"The {propertyName} property is unsupported by the dialect");
+
+			Entity.ArbitraryStringValue = "0; drop table Entity; --";
+			using (var session = OpenSession())
+			{
+				IQuery query;
+				// Defining that query is invalid and should throw.
+				try
+				{
+					query = session.CreateQuery($"from Entity e where e.{propertyName} = Entity.{nameof(Entity.ArbitraryStringValue)}");
+				}
+				catch (Exception ex)
+				{
+					// All good.
+					Assert.Pass($"The wicked query creation has been rejected, as it should: {ex}");
+					// Needed for the compiler who does not know "Pass" always throw.
+					return;
+				}
+
+				// The query definition has been accepted, run it.
+				try
+				{
+					query.List<Entity>();
+				}
+				catch (Exception ex)
+				{
+					// Expecting no exception at that point, but the test is to check if the injection succeeded.
+					Assert.Warn($"The wicked query execution has failed: {ex}");
+				}
+			}
+
+			// Check if we can still query Entity. If it succeeds, at least it means the injection failed.
+			using (var session = OpenSession())
+			{
+				IList<Entity> list = null;
+				Assert.That(() => list = session.CreateQuery("from Entity e").List<Entity>(), Throws.Nothing);
+				Assert.That(list, Has.Count.GreaterThan(0));
+			}
+		}
+
+		private static readonly string[] DateTypesInjections =
+		{
+			nameof(Entity.DateTimeProperty),
+			nameof(Entity.DateProperty),
+			nameof(Entity.DateTimeOffsetProperty),
+			nameof(Entity.TimeProperty)
+		};
+
+		[TestCaseSource(nameof(DateTypesInjections))]
+		public void SqlInjectionWithDatetime(string propertyName)
+		{
+			Assume.That(_unsupportedProperties, Does.Not.Contains((object) propertyName), $"The {propertyName} property is unsupported by the dialect");
+
+			var wickedCulture = new CultureInfo("en-US");
+			if (propertyName == nameof(Entity.TimeProperty))
+				wickedCulture.DateTimeFormat.ShortTimePattern = "HH:mm:ss\\'\"; drop table Entity; --\"";
+			else
+				wickedCulture.DateTimeFormat.ShortDatePattern = "yyyy-MM-ddTHH:mm:ss\\'\"; drop table Entity; --\"";
+			CultureInfo.CurrentCulture = wickedCulture;
+			CultureInfo.CurrentUICulture = wickedCulture;
+
+			using var session = OpenSession();
+
+			var staticPropertyName = propertyName == nameof(Entity.DateTimeOffsetProperty) ?
+				nameof(Entity.StaticDateTimeOffsetProperty) : nameof(Entity.StaticDateProperty);
+			var query = session.CreateQuery($"from Entity e where e.{propertyName} = Entity.{staticPropertyName}");
+			IList<Entity> list = null;
+			Assume.That(() => list = query.List<Entity>(), Throws.Nothing,
+				"The first execution of the query failed, the injection has likely failed");
+			// Execute again to check the table is still here.
+			Assert.That(() => list = query.List<Entity>(), Throws.Nothing,
+				"The second execution of the query failed although the first one did not: the injection has succeeded");
+		}
+
+		private static readonly string[] GuidInjections =
+		{
+			Entity.NameWithSingleQuote, Entity.NameWithEscapedSingleQuote
+		};
+
+		[TestCaseSource(nameof(GuidInjections))]
+		public void SqlInjectionWithGuid(string injection)
+		{
+			Entity.ArbitraryStringValue = $"{Guid.NewGuid()}{injection}";
+			using var session = OpenSession();
+
+			var query = session.CreateQuery($"from Entity e where e.GuidProperty = Entity.{nameof(Entity.ArbitraryStringValue)}");
+			IList<Entity> list = null;
+			Assume.That(() => list = query.List<Entity>(), Throws.Nothing,
+				"The first execution of the query failed, the injection has likely failed");
+			// Execute again to check the table is still here.
+			Assert.That(() => list = query.List<Entity>(), Throws.Nothing,
+				"The second execution of the query failed although the first one did not: the injection has succeeded");
+		}
+	}
+}
diff --git a/src/NHibernate.Test/NHSpecificTest/GH3516/Hierarchy.cs b/src/NHibernate.Test/NHSpecificTest/GH3516/Hierarchy.cs
new file mode 100644
index 000000000..cb5dca258
--- /dev/null
+++ b/src/NHibernate.Test/NHSpecificTest/GH3516/Hierarchy.cs
@@ -0,0 +1,18 @@
+﻿using System;
+
+namespace NHibernate.Test.NHSpecificTest.GH3516
+{
+	public class BaseClass
+	{
+		public virtual Guid Id { get; set; }
+		public virtual string Name { get; set; }
+	}
+
+	public class Subclass1 : BaseClass
+	{
+	}
+
+	public class Subclass2 : BaseClass
+	{
+	}
+}
diff --git a/src/NHibernate.Test/SqlCommandTest/SqlInsertBuilderFixture.cs b/src/NHibernate.Test/SqlCommandTest/SqlInsertBuilderFixture.cs
index ec1d5c0cb..ce076e007 100644
--- a/src/NHibernate.Test/SqlCommandTest/SqlInsertBuilderFixture.cs
+++ b/src/NHibernate.Test/SqlCommandTest/SqlInsertBuilderFixture.cs
@@ -54,11 +54,11 @@ public void Commented()
 			SqlInsertBuilder insert = new SqlInsertBuilder(factoryImpl);
 
 			insert.SetTableName("test_insert_builder");
 
 #pragma warning disable CS0618 // Type or member is obsolete
-			insert.AddColumn("stringColumn", "aSQLValue", (ILiteralType)NHibernateUtil.String);
+			insert.AddColumn("stringColumn", "aSQLValue", (ILiteralType)NHibernateUtil.AnsiString);
 #pragma warning restore CS0618 // Type or member is obsolete
 			insert.SetComment("Test insert");
 			string expectedSql =
 	"/* Test insert */ INSERT INTO test_insert_builder (stringColumn) VALUES ('aSQLValue')";
 			Assert.AreEqual(expectedSql, insert.ToSqlString().ToString(), "SQL String");
diff --git a/src/NHibernate.Test/TestDialect.cs b/src/NHibernate.Test/TestDialect.cs
index 49b2f7e0f..0d1de762a 100644
--- a/src/NHibernate.Test/TestDialect.cs
+++ b/src/NHibernate.Test/TestDialect.cs
@@ -103,11 +103,11 @@ public bool SupportsEmptyInsertsOrHasNonIdentityNativeGenerator
 		/// <summary>
 		/// Supports condition not bound to any data, like "where @p1 = @p2".
 		/// </summary>
 		public virtual bool SupportsNonDataBoundCondition => true;
 
-		public bool SupportsSqlType(SqlType sqlType)
+		public virtual bool SupportsSqlType(SqlType sqlType)
 		{
 			try
 			{
 				_dialect.GetTypeName(sqlType);
 				return true;
diff --git a/src/NHibernate.Test/TestDialects/Oracle10gTestDialect.cs b/src/NHibernate.Test/TestDialects/Oracle10gTestDialect.cs
index 4a5fb8d23..281676a6c 100644
--- a/src/NHibernate.Test/TestDialects/Oracle10gTestDialect.cs
+++ b/src/NHibernate.Test/TestDialects/Oracle10gTestDialect.cs
@@ -1,6 +1,8 @@
-﻿using System.Runtime.InteropServices;
+﻿using System.Data;
+using System.Runtime.InteropServices;
+using NHibernate.SqlTypes;
 
 namespace NHibernate.Test.TestDialects
 {
 	public class Oracle10gTestDialect : TestDialect
 	{
@@ -13,10 +15,20 @@ public Oracle10gTestDialect(Dialect.Dialect dialect) : base(dialect)
 		/// </summary>
 		public override bool SupportsSelectForUpdateWithPaging => false;
 
 		public override bool SupportsAggregateInSubSelect => true;
 
+		public override bool SupportsSqlType(SqlType sqlType)
+		{
+			// The Oracle dialects define types for DbType the Oracle driver does not support.
+			return sqlType.DbType switch
+			{
+				DbType.UInt16 or DbType.UInt32 or DbType.UInt64 => false,
+				_ => base.SupportsSqlType(sqlType)
+			};
+		}
+
 		/// <inheritdoc />
 		/// <remarks>Canceling a query hangs under Linux with OracleManagedDataClientDriver 21.6.1.</remarks>
 		public override bool SupportsCancelQuery => !RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
 	}
 }
diff --git a/src/NHibernate.Test/TestDialects/SapSQLAnywhere17TestDialect.cs b/src/NHibernate.Test/TestDialects/SapSQLAnywhere17TestDialect.cs
index 35a35092c..ff3ba11b7 100644
--- a/src/NHibernate.Test/TestDialects/SapSQLAnywhere17TestDialect.cs
+++ b/src/NHibernate.Test/TestDialects/SapSQLAnywhere17TestDialect.cs
@@ -1,6 +1,9 @@
-﻿namespace NHibernate.Test.TestDialects
+﻿using System.Data;
+using NHibernate.SqlTypes;
+
+namespace NHibernate.Test.TestDialects
 {
 	public class SapSQLAnywhere17TestDialect : TestDialect
 	{
 		public SapSQLAnywhere17TestDialect(Dialect.Dialect dialect)
 			: base(dialect)
@@ -38,10 +41,20 @@ public SapSQLAnywhere17TestDialect(Dialect.Dialect dialect)
 		/// <c>numeric</c>. See https://stackoverflow.com/q/52558715/1178314.
 		/// </remarks>
 		public override bool HasBrokenTypeInferenceOnSelectedParameters => true;
 
 		/// <summary>
-		/// Does not support SELECT FOR UPDATE 
+		/// Does not support SELECT FOR UPDATE.
 		/// </summary>
 		public override bool SupportsSelectForUpdate => false;
+
+		public override bool SupportsSqlType(SqlType sqlType)
+		{
+			// The Anywhere dialects define types for DbType the Anywhere driver does not support.
+			return sqlType.DbType switch
+			{
+				DbType.SByte => false,
+				_ => base.SupportsSqlType(sqlType)
+			};
+		}
 	}
 }
diff --git a/src/NHibernate/Async/Type/ByteType.cs b/src/NHibernate/Async/Type/ByteType.cs
index c908d3343..4ea953fa7 100644
--- a/src/NHibernate/Async/Type/ByteType.cs
+++ b/src/NHibernate/Async/Type/ByteType.cs
@@ -10,10 +10,11 @@
 
 using System;
 using System.Collections;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 
 namespace NHibernate.Type
 {
diff --git a/src/NHibernate/Async/Type/Int16Type.cs b/src/NHibernate/Async/Type/Int16Type.cs
index 95cec4937..0d88cbdd2 100644
--- a/src/NHibernate/Async/Type/Int16Type.cs
+++ b/src/NHibernate/Async/Type/Int16Type.cs
@@ -8,15 +8,16 @@
 //------------------------------------------------------------------------------
 
 
 using System;
 using System.Collections;
+using System.Collections.Generic;
+using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
-using System.Collections.Generic;
-using System.Data;
 
 namespace NHibernate.Type
 {
 	using System.Threading.Tasks;
 	using System.Threading;
diff --git a/src/NHibernate/Async/Type/Int32Type.cs b/src/NHibernate/Async/Type/Int32Type.cs
index e7897e1ea..592c61d3b 100644
--- a/src/NHibernate/Async/Type/Int32Type.cs
+++ b/src/NHibernate/Async/Type/Int32Type.cs
@@ -8,15 +8,16 @@
 //------------------------------------------------------------------------------
 
 
 using System;
 using System.Collections;
+using System.Collections.Generic;
+using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
-using System.Collections.Generic;
-using System.Data;
 
 namespace NHibernate.Type
 {
 	using System.Threading.Tasks;
 	using System.Threading;
diff --git a/src/NHibernate/Async/Type/Int64Type.cs b/src/NHibernate/Async/Type/Int64Type.cs
index a4dae3606..566b0e6b2 100644
--- a/src/NHibernate/Async/Type/Int64Type.cs
+++ b/src/NHibernate/Async/Type/Int64Type.cs
@@ -11,10 +11,11 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 
 namespace NHibernate.Type
 {
diff --git a/src/NHibernate/Async/Type/TicksType.cs b/src/NHibernate/Async/Type/TicksType.cs
index 0ffb06b37..b6e003cd7 100644
--- a/src/NHibernate/Async/Type/TicksType.cs
+++ b/src/NHibernate/Async/Type/TicksType.cs
@@ -9,10 +9,11 @@
 
 
 using System;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 
 namespace NHibernate.Type
 {
diff --git a/src/NHibernate/Async/Type/TimeAsTimeSpanType.cs b/src/NHibernate/Async/Type/TimeAsTimeSpanType.cs
index 185baa88e..4ea5f8669 100644
--- a/src/NHibernate/Async/Type/TimeAsTimeSpanType.cs
+++ b/src/NHibernate/Async/Type/TimeAsTimeSpanType.cs
@@ -8,15 +8,16 @@
 //------------------------------------------------------------------------------
 
 
 using System;
 using System.Collections;
+using System.Collections.Generic;
+using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
-using System.Collections.Generic;
-using System.Data;
 
 namespace NHibernate.Type
 {
 	using System.Threading.Tasks;
 	using System.Threading;
diff --git a/src/NHibernate/Async/Type/TimeSpanType.cs b/src/NHibernate/Async/Type/TimeSpanType.cs
index 091741367..9364c6599 100644
--- a/src/NHibernate/Async/Type/TimeSpanType.cs
+++ b/src/NHibernate/Async/Type/TimeSpanType.cs
@@ -8,15 +8,16 @@
 //------------------------------------------------------------------------------
 
 
 using System;
 using System.Collections;
+using System.Collections.Generic;
+using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
-using System.Collections.Generic;
-using System.Data;
 
 namespace NHibernate.Type
 {
 	using System.Threading.Tasks;
 	using System.Threading;
diff --git a/src/NHibernate/Async/Type/UInt16Type.cs b/src/NHibernate/Async/Type/UInt16Type.cs
index b30c8ee87..7384a0c84 100644
--- a/src/NHibernate/Async/Type/UInt16Type.cs
+++ b/src/NHibernate/Async/Type/UInt16Type.cs
@@ -11,10 +11,11 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 
 namespace NHibernate.Type
 {
diff --git a/src/NHibernate/Async/Type/UInt32Type.cs b/src/NHibernate/Async/Type/UInt32Type.cs
index b5aa62179..a1f3bf583 100644
--- a/src/NHibernate/Async/Type/UInt32Type.cs
+++ b/src/NHibernate/Async/Type/UInt32Type.cs
@@ -11,10 +11,11 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 
 namespace NHibernate.Type
 {
diff --git a/src/NHibernate/Async/Type/UInt64Type.cs b/src/NHibernate/Async/Type/UInt64Type.cs
index 08e7c68a1..bee61c38a 100644
--- a/src/NHibernate/Async/Type/UInt64Type.cs
+++ b/src/NHibernate/Async/Type/UInt64Type.cs
@@ -11,10 +11,11 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 
 namespace NHibernate.Type
 {
diff --git a/src/NHibernate/Cfg/Environment.cs b/src/NHibernate/Cfg/Environment.cs
index cc1078025..27da45694 100644
--- a/src/NHibernate/Cfg/Environment.cs
+++ b/src/NHibernate/Cfg/Environment.cs
@@ -117,10 +117,16 @@ public static string Version
 		public const string DetectFetchLoops = "detect_fetch_loops";
 
 		/// <summary> Enable formatting of SQL logged to the console</summary>
 		public const string FormatSql = "format_sql";
 
+		/// <summary>
+		/// Indicates if the database needs to have backslash escaped in string literals.
+		/// </summary>
+		/// <remarks>The default value is dialect dependent.</remarks>
+		public const string EscapeBackslashInStrings = "escape_backslash_in_strings";
+
 		// Since v5.0.1
 		[Obsolete("This setting has no usages and will be removed in a future version")]
 		public const string UseGetGeneratedKeys = "jdbc.use_get_generated_keys";
 
 		// Since v5.0.1
diff --git a/src/NHibernate/Dialect/DB2Dialect.cs b/src/NHibernate/Dialect/DB2Dialect.cs
index a558e6d31..90668e26b 100644
--- a/src/NHibernate/Dialect/DB2Dialect.cs
+++ b/src/NHibernate/Dialect/DB2Dialect.cs
@@ -3,10 +3,11 @@
 using System.Text;
 using NHibernate.Cfg;
 using NHibernate.Dialect.Function;
 using NHibernate.Dialect.Schema;
 using NHibernate.SqlCommand;
+using NHibernate.SqlTypes;
 
 namespace NHibernate.Dialect
 {
 	/// <summary>
 	/// An SQL dialect for DB2.
@@ -294,10 +295,34 @@ public override string ForUpdateString
 		/// <inheritdoc />
 		public override int MaxAliasLength => 32;
 
 		public override long TimestampResolutionInTicks => 10L; // Microseconds.
 
+		/// <inheritdoc />
+		public override string ToStringLiteral(string value, SqlType type)
+		{
+			if (value == null)
+				throw new System.ArgumentNullException(nameof(value));
+			if (type == null)
+				throw new System.ArgumentNullException(nameof(value));
+
+			// See https://www.ibm.com/docs/en/db2/11.5?topic=elements-constants#r0000731__title__7
+			var literal = new StringBuilder(value);
+			var isUnicode = type.DbType == DbType.String || type.DbType == DbType.StringFixedLength;
+			if (isUnicode)
+				literal.Replace(@"\", @"\\");
+
+			literal
+				.Replace("'", "''")
+				.Insert(0, '\'')
+				.Append('\'');
+
+			if (isUnicode)
+				literal.Insert(0, "U&");
+			return literal.ToString();
+		}
+
 		#region Overridden informational metadata
 
 		public override bool SupportsNullInUnique => false;
 
 		public override bool SupportsEmptyInList => false;
diff --git a/src/NHibernate/Dialect/Dialect.cs b/src/NHibernate/Dialect/Dialect.cs
index 9a4db1188..c76992e19 100644
--- a/src/NHibernate/Dialect/Dialect.cs
+++ b/src/NHibernate/Dialect/Dialect.cs
@@ -206,10 +206,11 @@ private static Dialect InstantiateDialect(string dialectName, IDictionary<string
 		public virtual void Configure(IDictionary<string, string> settings)
 		{
 			DefaultCastLength = PropertiesHelper.GetInt32(Environment.QueryDefaultCastLength, settings, 4000);
 			DefaultCastPrecision = PropertiesHelper.GetByte(Environment.QueryDefaultCastPrecision, settings, null) ?? 29;
 			DefaultCastScale = PropertiesHelper.GetByte(Environment.QueryDefaultCastScale, settings, null) ?? 10;
+			EscapeBackslashInStrings = PropertiesHelper.GetBoolean(Environment.EscapeBackslashInStrings, settings, EscapeBackslashInStrings);
 		}
 
 		#endregion
 
 		#region Database type mapping support
@@ -1352,18 +1353,10 @@ public virtual JoinFragment CreateOuterJoinFragment()
 		public virtual CaseFragment CreateCaseFragment()
 		{
 			return new ANSICaseFragment(this);
 		}
 
-		/// <summary> The SQL literal value to which this database maps boolean values. </summary>
-		/// <param name="value">The boolean value </param>
-		/// <returns> The appropriate SQL literal. </returns>
-		public virtual string ToBooleanValueString(bool value)
-		{
-			return value ? "1" : "0";
-		}
-
 		internal static void ExtractColumnOrAliasNames(SqlString select, out List<SqlString> columnsOrAliases, out Dictionary<SqlString, SqlString> aliasToColumn, out Dictionary<SqlString, SqlString> columnToAlias)
 		{
 			columnsOrAliases = new List<SqlString>();
 			aliasToColumn = new Dictionary<SqlString, SqlString>();
 			columnToAlias = new Dictionary<SqlString, SqlString>();
@@ -2074,10 +2067,59 @@ public virtual string ConvertQuotesForCatalogName(string catalogName)
 				: catalogName;
 		}
 
 		#endregion
 
+		#region Literals support
+
+		/// <summary>The SQL literal value to which this database maps boolean values.</summary>
+		/// <param name="value">The boolean value.</param>
+		/// <returns>The appropriate SQL literal.</returns>
+		public virtual string ToBooleanValueString(bool value)
+			=> value ? "1" : "0";
+
+		/// <summary>
+		/// <see langword="true" /> if the database needs to have backslash escaped in string literals.
+		/// </summary>
+		/// <remarks><see langword="false" /> by default in the base dialect, to conform to SQL standard.</remarks>
+		protected virtual bool EscapeBackslashInStrings { get; set; }
+
+		/// <summary>
+		/// <see langword="true" /> if the database needs to have Unicode literals prefixed by <c>N</c>.
+		/// </summary>
+		/// <remarks><see langword="false" /> by default in the base dialect.</remarks>
+		protected virtual bool UseNPrefixForUnicodeStrings => false;
+
+		/// <summary>The SQL string literal value to which this database maps string values.</summary>
+		/// <param name="value">The string value.</param>
+		/// <param name="type">The SQL type of the string value.</param>
+		/// <returns>The appropriate SQL string literal.</returns>
+		/// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> or
+		/// <paramref name="type"/> is <see langword="null" />.</exception>
+		public virtual string ToStringLiteral(string value, SqlType type)
+		{
+			if (value == null)
+				throw new ArgumentNullException(nameof(value));
+			if (type == null)
+				throw new ArgumentNullException(nameof(type));
+
+			var literal = new StringBuilder(value);
+			if (EscapeBackslashInStrings)
+				literal.Replace(@"\", @"\\");
+
+			literal
+				.Replace("'", "''")
+				.Insert(0, '\'')
+				.Append('\'');
+
+			if (UseNPrefixForUnicodeStrings && (type.DbType == DbType.String || type.DbType == DbType.StringFixedLength))
+				literal.Insert(0, 'N');
+			return literal.ToString();
+		}
+
+		#endregion
+
 		#region Union subclass support
 
 		/// <summary> 
 		/// Given a <see cref="DbType"/> type code, determine an appropriate
 		/// null value to use in a select clause.
diff --git a/src/NHibernate/Dialect/IngresDialect.cs b/src/NHibernate/Dialect/IngresDialect.cs
index f6197f328..9f6e8e7b4 100644
--- a/src/NHibernate/Dialect/IngresDialect.cs
+++ b/src/NHibernate/Dialect/IngresDialect.cs
@@ -60,10 +60,15 @@ public IngresDialect()
 		// This dialect seems to target version below 9, since there is Ingres9Dialect deriving from it.
 		// So sticking to 32.
 		/// <inheritdoc />
 		public override int MaxAliasLength => 32;
 
+		/// <inheritdoc />
+		/// <remarks><see langword="true" /> by default for Ingres,
+		/// <see href="https://docs.actian.com/ingres/11.0/index.html#page/SQLRef/String_Literals.htm#ww110572" />.</remarks>
+		protected override bool UseNPrefixForUnicodeStrings => true;
+
 		#region Overridden informational metadata
 
 		public override bool SupportsEmptyInList => false;
 
 		public override bool SupportsSubselectAsInPredicateLHS => false;
diff --git a/src/NHibernate/Dialect/MsSql2000Dialect.cs b/src/NHibernate/Dialect/MsSql2000Dialect.cs
index 1aec544ed..d6fb0e167 100644
--- a/src/NHibernate/Dialect/MsSql2000Dialect.cs
+++ b/src/NHibernate/Dialect/MsSql2000Dialect.cs
@@ -753,10 +753,14 @@ public override bool SupportsSqlBatches
 		/// and three for sp_prepexec (used when preparing is enabled). Set the number to 2097
 		/// as the worst case scenario.
 		/// </summary>
 		public override int? MaxNumberOfParameters => 2097;
 
+		/// <inheritdoc />
+		/// <remarks><see langword="true" /> by default for SQL Server.</remarks>
+		protected override bool UseNPrefixForUnicodeStrings => true;
+
 		#region Overridden informational metadata
 
 		public override bool SupportsEmptyInList => false;
 
 		public override bool AreStringComparisonsCaseInsensitive => true;
diff --git a/src/NHibernate/Dialect/MySQLDialect.cs b/src/NHibernate/Dialect/MySQLDialect.cs
index d7b4be07d..d6cf67b80 100644
--- a/src/NHibernate/Dialect/MySQLDialect.cs
+++ b/src/NHibernate/Dialect/MySQLDialect.cs
@@ -528,10 +528,20 @@ public override long TimestampResolutionInTicks
 		/// NotSupportedException : Multiple simultaneous connections or connections with different 
 		/// connection strings inside the same transaction are not currently supported.
 		/// </remarks>
 		public override bool SupportsConcurrentWritingConnectionsInSameTransaction => false;
 
+		/// <inheritdoc />
+		/// <remarks><see langword="true" /> by default for MySQL,
+		/// <see href="https://dev.mysql.com/doc/refman/8.0/en/string-literals.html" />.</remarks>
+		protected override bool EscapeBackslashInStrings { get; set; } = true;
+
+		/// <inheritdoc />
+		/// <remarks><see langword="true" /> by default for MySQL,
+		/// <see href="https://dev.mysql.com/doc/refman/8.0/en/string-literals.html" />.</remarks>
+		protected override bool UseNPrefixForUnicodeStrings => true;
+
 		#region Overridden informational metadata
 
 		public override bool SupportsEmptyInList => false;
 
 		public override bool AreStringComparisonsCaseInsensitive => true;
diff --git a/src/NHibernate/Dialect/Oracle8iDialect.cs b/src/NHibernate/Dialect/Oracle8iDialect.cs
index 9bbe3e7df..13b9b1298 100644
--- a/src/NHibernate/Dialect/Oracle8iDialect.cs
+++ b/src/NHibernate/Dialect/Oracle8iDialect.cs
@@ -100,10 +100,11 @@ public override void Configure(IDictionary<string, string> settings)
 		{
 			base.Configure(settings);
 
 			// If changing the default value, keep it in sync with OracleDataClientDriverBase.Configure.
 			UseNPrefixedTypesForUnicode = PropertiesHelper.GetBoolean(Environment.OracleUseNPrefixedTypesForUnicode, settings, false);
+
 			RegisterCharacterTypeMappings();
 			RegisterFloatingPointTypeMappings();
 		}
 
 		#region private static readonly string[] DialectKeywords = { ... }
@@ -559,10 +560,14 @@ public override long TimestampResolutionInTicks
 
 		// 30 before 12.1. https://stackoverflow.com/a/756569/1178314
 		/// <inheritdoc />
 		public override int MaxAliasLength => 30;
 
+		/// <inheritdoc />
+		/// <remarks>Returns the same value as <see cref="UseNPrefixedTypesForUnicode" />.</remarks>
+		protected override bool UseNPrefixForUnicodeStrings => UseNPrefixedTypesForUnicode;
+
 		#region Overridden informational metadata
 
 		public override bool SupportsEmptyInList
 		{
 			get { return false; }
diff --git a/src/NHibernate/Dialect/SybaseASA9Dialect.cs b/src/NHibernate/Dialect/SybaseASA9Dialect.cs
index dfae7baa4..e994c85cc 100644
--- a/src/NHibernate/Dialect/SybaseASA9Dialect.cs
+++ b/src/NHibernate/Dialect/SybaseASA9Dialect.cs
@@ -7,11 +7,11 @@
 using NHibernate.SqlCommand;
 
 namespace NHibernate.Dialect
 {
 	/// <summary>
-	/// An SQL dialect for Sybase Adaptive Server Anywhere 9.0
+	/// An SQL dialect for Sybase Adaptive Server Anywhere 9.0. (Renamed SQL Anywhere from its version 10.)
 	/// </summary>
 	/// <remarks>
 	/// <p>
 	/// This dialect probably will not work with schema-export.  If anyone out there
 	/// can fill in the ctor with DbTypes to Strings that would be helpful.
@@ -186,7 +186,17 @@ private static int GetAfterSelectInsertPoint(SqlString sql)
 					return strSelect.Length;
 				}
 			}
 			return 0;
 		}
+
+		/// <inheritdoc />
+		/// <remarks><see langword="true" /> by default for SQL Anywhere,
+		/// <see href="https://help.sap.com/docs/SAP_SQL_Anywhere/93079d4ba8e44920ae63ffb4def91f5b/817a3ded6ce21014bd99f3e554573180.html?version=17.0" />.</remarks>
+		protected override bool EscapeBackslashInStrings { get; set; } = true;
+
+		/// <inheritdoc />
+		/// <remarks><see langword="true" /> by default for SQL Anywhere,
+		/// <see href="https://help.sap.com/docs/SAP_SQL_Anywhere/93079d4ba8e44920ae63ffb4def91f5b/817a2c5f6ce21014aceea962de72126c.html?version=17.0" />.</remarks>
+		protected override bool UseNPrefixForUnicodeStrings => true;
 	}
 }
diff --git a/src/NHibernate/Dialect/SybaseSQLAnywhere10Dialect.cs b/src/NHibernate/Dialect/SybaseSQLAnywhere10Dialect.cs
index 290e62667..ac5aa44f8 100644
--- a/src/NHibernate/Dialect/SybaseSQLAnywhere10Dialect.cs
+++ b/src/NHibernate/Dialect/SybaseSQLAnywhere10Dialect.cs
@@ -967,7 +967,17 @@ public override IDataBaseSchema GetDataBaseSchema(DbConnection connection)
 		}
 
 		/// <inheritdoc />
 		/// <remarks>SQL Anywhere has a micro-second resolution.</remarks>
 		public override long TimestampResolutionInTicks => 10L;
+
+		/// <inheritdoc />
+		/// <remarks><see langword="true" /> by default for SQL Anywhere,
+		/// <see href="https://help.sap.com/docs/SAP_SQL_Anywhere/93079d4ba8e44920ae63ffb4def91f5b/817a3ded6ce21014bd99f3e554573180.html?version=17.0" />.</remarks>
+		protected override bool EscapeBackslashInStrings { get; set; } = true;
+
+		/// <inheritdoc />
+		/// <remarks><see langword="true" /> by default for SQL Anywhere,
+		/// <see href="https://help.sap.com/docs/SAP_SQL_Anywhere/93079d4ba8e44920ae63ffb4def91f5b/817a2c5f6ce21014aceea962de72126c.html?version=17.0" />.</remarks>
+		protected override bool UseNPrefixForUnicodeStrings => true;
 	}
 }
diff --git a/src/NHibernate/Type/AbstractCharType.cs b/src/NHibernate/Type/AbstractCharType.cs
index 5efb630c1..d47e3c5b2 100644
--- a/src/NHibernate/Type/AbstractCharType.cs
+++ b/src/NHibernate/Type/AbstractCharType.cs
@@ -49,13 +49,11 @@ public override void Set(DbCommand cmd, object value, int index, ISessionImpleme
 		{
 			cmd.Parameters[index].Value = Convert.ToChar(value);
 		}
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
-		{
-			return '\'' + value.ToString() + '\'';
-		}
+			=> dialect.ToStringLiteral(value.ToString(), SqlType);
 
 		// 6.0 TODO: rename "xml" parameter as "value": it is not a xml string. The fact it generally comes from a xml
 		// attribute value is irrelevant to the method behavior.
 		/// <inheritdoc />
 		public virtual object StringToObject(string xml)
diff --git a/src/NHibernate/Type/AbstractDateTimeType.cs b/src/NHibernate/Type/AbstractDateTimeType.cs
index 8f95323cb..07c32a7ba 100644
--- a/src/NHibernate/Type/AbstractDateTimeType.cs
+++ b/src/NHibernate/Type/AbstractDateTimeType.cs
@@ -174,9 +174,9 @@ public override object FromStringValue(string xml)
 
 		/// <inheritdoc />
 		public override object DefaultValue => BaseDateValue;
 
 		/// <inheritdoc />
-		public override string ObjectToSQLString(object value, Dialect.Dialect dialect) =>
-			"'" + (DateTime) value + "'";
+		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
+			=> dialect.ToStringLiteral(((DateTime) value).ToString(), SqlTypeFactory.GetAnsiString(50));
 	}
 }
diff --git a/src/NHibernate/Type/AbstractStringType.cs b/src/NHibernate/Type/AbstractStringType.cs
index 9d47aaf07..72949c109 100644
--- a/src/NHibernate/Type/AbstractStringType.cs
+++ b/src/NHibernate/Type/AbstractStringType.cs
@@ -132,14 +132,13 @@ public object StringToObject(string xml)
 
 		#endregion
 
 		#region ILiteralType Members
 
+		/// <inheritdoc />
 		public string ObjectToSQLString(object value, Dialect.Dialect dialect)
-		{
-			return "'" + (string)value + "'";
-		}
+			=> dialect.ToStringLiteral((string)value, SqlType);
 
 		#endregion
 
 		#region IParameterizedType Members
 
diff --git a/src/NHibernate/Type/ByteType.cs b/src/NHibernate/Type/ByteType.cs
index 7c90bd673..c780427b2 100644
--- a/src/NHibernate/Type/ByteType.cs
+++ b/src/NHibernate/Type/ByteType.cs
@@ -1,9 +1,10 @@
 using System;
 using System.Collections;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 
 namespace NHibernate.Type
 {
@@ -52,11 +53,11 @@ public override string Name
 			get { return "Byte"; }
 		}
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return value.ToString();
+			return ((byte)value).ToString(CultureInfo.InvariantCulture);
 		}
 
 		// 6.0 TODO: rename "xml" parameter as "value": it is not a xml string. The fact it generally comes from a xml
 		// attribute value is irrelevant to the method behavior.
 		/// <inheritdoc />
diff --git a/src/NHibernate/Type/CharBooleanType.cs b/src/NHibernate/Type/CharBooleanType.cs
index 9cde87c66..c721640ef 100644
--- a/src/NHibernate/Type/CharBooleanType.cs
+++ b/src/NHibernate/Type/CharBooleanType.cs
@@ -1,11 +1,10 @@
 using System;
 using System.Data;
 using System.Data.Common;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
-using NHibernate.Util;
 
 namespace NHibernate.Type
 {
 	/// <summary>
 	/// Maps a <see cref="System.Boolean"/> Property 
@@ -55,13 +54,11 @@ private string ToCharacter(object value)
 		{
 			return ((bool) value) ? TrueString : FalseString;
 		}
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
-		{
-			return "'" + ToCharacter(value) + "'";
-		}
+			=> dialect.ToStringLiteral(ToCharacter(value), SqlType);
 
 		// 6.0 TODO: rename "xml" parameter as "value": it is not a xml string. The fact it generally comes from a xml
 		// attribute value is irrelevant to the method behavior.
 		/// <inheritdoc />
 		public override object StringToObject(String xml)
diff --git a/src/NHibernate/Type/DateTimeOffSetType.cs b/src/NHibernate/Type/DateTimeOffSetType.cs
index 37837aec2..78a9c8066 100644
--- a/src/NHibernate/Type/DateTimeOffSetType.cs
+++ b/src/NHibernate/Type/DateTimeOffSetType.cs
@@ -153,10 +153,8 @@ public override object FromStringValue(string xml)
 		{
 			return DateTimeOffset.Parse(xml);
 		}
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
-		{
-			return "'" + ((DateTimeOffset) value) + "'";
-		}
+			=> dialect.ToStringLiteral(((DateTimeOffset) value).ToString(), SqlTypeFactory.GetAnsiString(50));
 	}
 }
diff --git a/src/NHibernate/Type/DateType.cs b/src/NHibernate/Type/DateType.cs
index 76d3fbb99..1a3bac85c 100644
--- a/src/NHibernate/Type/DateType.cs
+++ b/src/NHibernate/Type/DateType.cs
@@ -91,12 +91,12 @@ public override string ToLoggableString(object value, ISessionFactoryImplementor
 
 		/// <inheritdoc />
 		public override object DefaultValue => customBaseDate;
 
 		/// <inheritdoc />
-		public override string ObjectToSQLString(object value, Dialect.Dialect dialect) =>
-			"\'" + ((DateTime)value).ToShortDateString() + "\'";
+		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
+			=> dialect.ToStringLiteral(((DateTime) value).ToShortDateString(), SqlTypeFactory.GetAnsiString(50));
 
 		// Since v5
 		[Obsolete("Its only parameter, BaseValue, is obsolete.")]
 		public void SetParameterValues(IDictionary<string, string> parameters)
 		{
diff --git a/src/NHibernate/Type/DecimalType.cs b/src/NHibernate/Type/DecimalType.cs
index 158fa028f..df6f0b60f 100644
--- a/src/NHibernate/Type/DecimalType.cs
+++ b/src/NHibernate/Type/DecimalType.cs
@@ -1,8 +1,9 @@
 using System;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 
 namespace NHibernate.Type
 {
@@ -64,11 +65,11 @@ public override object FromStringValue(string xml)
 			return Decimal.Parse(xml);
 		}
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return value.ToString();
+			return ((decimal)value).ToString(CultureInfo.InvariantCulture);
 		}
 
 		// 6.0 TODO: rename "xml" parameter as "value": it is not a xml string. The fact it generally comes from a xml
 		// attribute value is irrelevant to the method behavior.
 		/// <inheritdoc />
diff --git a/src/NHibernate/Type/DoubleType.cs b/src/NHibernate/Type/DoubleType.cs
index 4a8cf3406..b7df9359b 100644
--- a/src/NHibernate/Type/DoubleType.cs
+++ b/src/NHibernate/Type/DoubleType.cs
@@ -1,8 +1,9 @@
 using System;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 
 namespace NHibernate.Type
 {
@@ -64,9 +65,9 @@ public override object DefaultValue
 			get { return 0D; }
 		}
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return value.ToString();
+			return ((double)value).ToString(CultureInfo.InvariantCulture);
 		}
 	}
 }
diff --git a/src/NHibernate/Type/EnumCharType.cs b/src/NHibernate/Type/EnumCharType.cs
index ece3684ff..d73490ff1 100644
--- a/src/NHibernate/Type/EnumCharType.cs
+++ b/src/NHibernate/Type/EnumCharType.cs
@@ -169,10 +169,8 @@ public override object FromStringValue(string xml)
 		{
 			return GetInstance(xml);
 		}
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
-		{
-			return '\'' + GetValue(value).ToString() + '\'';
-		}
+			=> dialect.ToStringLiteral(GetValue(value).ToString(), SqlType);
 	}
 }
diff --git a/src/NHibernate/Type/GuidType.cs b/src/NHibernate/Type/GuidType.cs
index a883f3a6b..70d26f1d9 100644
--- a/src/NHibernate/Type/GuidType.cs
+++ b/src/NHibernate/Type/GuidType.cs
@@ -86,9 +86,9 @@ public override object DefaultValue
 			get { return Guid.Empty; }
 		}
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return "'" + value + "'";
+			return dialect.ToStringLiteral(value.ToString(), SqlTypeFactory.GetAnsiString(50));
 		}
 	}
 }
diff --git a/src/NHibernate/Type/Int16Type.cs b/src/NHibernate/Type/Int16Type.cs
index f517be934..af018c60a 100644
--- a/src/NHibernate/Type/Int16Type.cs
+++ b/src/NHibernate/Type/Int16Type.cs
@@ -1,12 +1,13 @@
 using System;
 using System.Collections;
+using System.Collections.Generic;
+using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
-using System.Collections.Generic;
-using System.Data;
 
 namespace NHibernate.Type
 {
 	/// <summary>
 	/// Maps a <see cref="System.Int16"/> Property 
@@ -112,9 +113,9 @@ public override object DefaultValue
 			get { return ZERO; }
 		}
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return value.ToString();
+			return ((short)value).ToString(CultureInfo.InvariantCulture);
 		}
 	}
 }
diff --git a/src/NHibernate/Type/Int32Type.cs b/src/NHibernate/Type/Int32Type.cs
index 9602842ae..2ec84eecf 100644
--- a/src/NHibernate/Type/Int32Type.cs
+++ b/src/NHibernate/Type/Int32Type.cs
@@ -1,12 +1,13 @@
 using System;
 using System.Collections;
+using System.Collections.Generic;
+using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
-using System.Collections.Generic;
-using System.Data;
 
 namespace NHibernate.Type
 {
 	/// <summary>
 	/// Maps a <see cref="System.Int32"/> Property 
@@ -112,9 +113,9 @@ public override object DefaultValue
 			get { return ZERO; }
 		}
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return value.ToString();
+			return ((int)value).ToString(CultureInfo.InvariantCulture);
 		}
 	}
 }
diff --git a/src/NHibernate/Type/Int64Type.cs b/src/NHibernate/Type/Int64Type.cs
index 5bad9a751..64a6cf37a 100644
--- a/src/NHibernate/Type/Int64Type.cs
+++ b/src/NHibernate/Type/Int64Type.cs
@@ -1,10 +1,11 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 
 namespace NHibernate.Type
 {
@@ -112,9 +113,9 @@ public override object DefaultValue
 			get { return ZERO; }
 		}
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return value.ToString();
+			return ((long)value).ToString(CultureInfo.InvariantCulture);
 		}
 	}
 }
diff --git a/src/NHibernate/Type/SByteType.cs b/src/NHibernate/Type/SByteType.cs
index 55021fb22..f3cbefa75 100644
--- a/src/NHibernate/Type/SByteType.cs
+++ b/src/NHibernate/Type/SByteType.cs
@@ -1,10 +1,11 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 
 namespace NHibernate.Type
 {
@@ -114,9 +115,9 @@ public override object DefaultValue
 			get { return ZERO; }
 		}
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return value.ToString();
+			return ((sbyte)value).ToString(CultureInfo.InvariantCulture);
 		}
 	}
 }
diff --git a/src/NHibernate/Type/SingleType.cs b/src/NHibernate/Type/SingleType.cs
index 70ca434e0..4d299bb1c 100644
--- a/src/NHibernate/Type/SingleType.cs
+++ b/src/NHibernate/Type/SingleType.cs
@@ -1,8 +1,9 @@
 using System;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 
 namespace NHibernate.Type
 {
@@ -89,9 +90,9 @@ public override object DefaultValue
 			get { return ZERO; }
 		}
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return value.ToString();
+			return ((float)value).ToString(CultureInfo.InvariantCulture);
 		}
 	}
 }
diff --git a/src/NHibernate/Type/TicksType.cs b/src/NHibernate/Type/TicksType.cs
index 4fc18a007..bc82d101d 100644
--- a/src/NHibernate/Type/TicksType.cs
+++ b/src/NHibernate/Type/TicksType.cs
@@ -1,8 +1,9 @@
 using System;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 
 namespace NHibernate.Type
 {
@@ -98,9 +99,9 @@ public new object StringToObject(string xml)
 		#endregion
 
 		/// <inheritdoc />
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return '\'' + ((DateTime)value).Ticks.ToString() + '\'';
+			return '\'' + ((DateTime)value).Ticks.ToString(CultureInfo.InvariantCulture) + '\'';
 		}
 	}
 }
diff --git a/src/NHibernate/Type/TimeAsTimeSpanType.cs b/src/NHibernate/Type/TimeAsTimeSpanType.cs
index e525ecfa5..fd7bd7058 100644
--- a/src/NHibernate/Type/TimeAsTimeSpanType.cs
+++ b/src/NHibernate/Type/TimeAsTimeSpanType.cs
@@ -1,12 +1,13 @@
 ﻿using System;
 using System.Collections;
+using System.Collections.Generic;
+using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
-using System.Collections.Generic;
-using System.Data;
 
 namespace NHibernate.Type
 {
 	/// <summary>
 	/// Maps a <see cref="System.TimeSpan" /> Property to an <see cref="DbType.Time" /> column 
@@ -139,9 +140,9 @@ public override object DefaultValue
 			get { return TimeSpan.Zero; }
 		}
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return '\'' + ((TimeSpan)value).Ticks.ToString() + '\'';
+			return '\'' + ((TimeSpan)value).Ticks.ToString(CultureInfo.InvariantCulture) + '\'';
 		}
 	}
 }
diff --git a/src/NHibernate/Type/TimeSpanType.cs b/src/NHibernate/Type/TimeSpanType.cs
index 5ca576454..1ce300af0 100644
--- a/src/NHibernate/Type/TimeSpanType.cs
+++ b/src/NHibernate/Type/TimeSpanType.cs
@@ -1,12 +1,13 @@
 using System;
 using System.Collections;
+using System.Collections.Generic;
+using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
-using System.Collections.Generic;
-using System.Data;
 
 namespace NHibernate.Type
 {
 	/// <summary>
 	/// Maps a <see cref="System.TimeSpan" /> Property to an <see cref="DbType.Int64" /> column 
@@ -126,9 +127,9 @@ public override object DefaultValue
 			get { return TimeSpan.Zero; }
 		}
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return '\'' + ((TimeSpan)value).Ticks.ToString() + '\'';
+			return '\'' + ((TimeSpan)value).Ticks.ToString(CultureInfo.InvariantCulture) + '\'';
 		}
 	}
 }
diff --git a/src/NHibernate/Type/TimeType.cs b/src/NHibernate/Type/TimeType.cs
index be487f81c..b808619ae 100644
--- a/src/NHibernate/Type/TimeType.cs
+++ b/src/NHibernate/Type/TimeType.cs
@@ -169,10 +169,8 @@ public override object DefaultValue
 		{
 			get { return BaseDateValue; }
 		}
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
-		{
-			return "'" + ((DateTime)value).ToShortTimeString() + "'";
-		}
+			=> dialect.ToStringLiteral(((DateTime) value).ToShortTimeString(), SqlTypeFactory.GetAnsiString(50));
 	}
 }
diff --git a/src/NHibernate/Type/UInt16Type.cs b/src/NHibernate/Type/UInt16Type.cs
index 10e9d15cd..90421392d 100644
--- a/src/NHibernate/Type/UInt16Type.cs
+++ b/src/NHibernate/Type/UInt16Type.cs
@@ -1,10 +1,11 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 
 namespace NHibernate.Type
 {
@@ -112,9 +113,9 @@ public override object DefaultValue
 			get { return ZERO; }
 		}
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return value.ToString();
+			return ((ushort)value).ToString(CultureInfo.InvariantCulture);
 		}
 	}
 }
diff --git a/src/NHibernate/Type/UInt32Type.cs b/src/NHibernate/Type/UInt32Type.cs
index 0590278ef..897dca99a 100644
--- a/src/NHibernate/Type/UInt32Type.cs
+++ b/src/NHibernate/Type/UInt32Type.cs
@@ -1,10 +1,11 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 
 namespace NHibernate.Type
 {
@@ -112,9 +113,9 @@ public override object DefaultValue
 			get { return ZERO; }
 		}
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return value.ToString();
+			return ((uint)value).ToString(CultureInfo.InvariantCulture);
 		}
 	}
 }
diff --git a/src/NHibernate/Type/UInt64Type.cs b/src/NHibernate/Type/UInt64Type.cs
index a902b6d46..5665a1077 100644
--- a/src/NHibernate/Type/UInt64Type.cs
+++ b/src/NHibernate/Type/UInt64Type.cs
@@ -1,10 +1,11 @@
 using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.Data;
 using System.Data.Common;
+using System.Globalization;
 using NHibernate.Engine;
 using NHibernate.SqlTypes;
 
 namespace NHibernate.Type
 {
@@ -111,9 +112,9 @@ public override object DefaultValue
 			get { return ZERO; }
 		}
 
 		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
 		{
-			return value.ToString();
+			return ((ulong)value).ToString(CultureInfo.InvariantCulture);
 		}
 	}
 }
diff --git a/src/NHibernate/Type/UriType.cs b/src/NHibernate/Type/UriType.cs
index 68f319606..100c3704a 100644
--- a/src/NHibernate/Type/UriType.cs
+++ b/src/NHibernate/Type/UriType.cs
@@ -82,13 +82,11 @@ public override object FromStringValue(string xml)
 		{
 			return StringToObject(xml);
 		}
 
 		public string ObjectToSQLString(object value, Dialect.Dialect dialect)
-		{
-			return "'" + ((Uri)value).OriginalString + "'";
-		}
+			=> dialect.ToStringLiteral(((Uri) value).OriginalString, SqlType);
 
 		/// <inheritdoc />
 		public override object Assemble(object cached, ISessionImplementor session, object owner)
 		{
 			var str = cached as string;
diff --git a/src/NHibernate/nhibernate-configuration.xsd b/src/NHibernate/nhibernate-configuration.xsd
index 08d922ad9..123b2c35e 100644
--- a/src/NHibernate/nhibernate-configuration.xsd
+++ b/src/NHibernate/nhibernate-configuration.xsd
@@ -174,10 +174,18 @@
 										<xs:enumeration value="batch_fetch_style" />
 										<xs:enumeration value="default_entity_mode" />
 										<xs:enumeration value="default_flush_mode" />
 										<xs:enumeration value="use_sql_comments" />
 										<xs:enumeration value="format_sql" />
+										<xs:enumeration value="escape_backslash_in_strings">
+											<xs:annotation>
+												<xs:documentation>
+													Indicates if the database needs to have backslash escaped in string literals. The default is
+													dialect dependent.
+												</xs:documentation>
+											</xs:annotation>
+										</xs:enumeration>
 										<xs:enumeration value="collectiontype.factory_class" />
 										<xs:enumeration value="order_inserts" />
 										<xs:enumeration value="order_updates" />
 										<xs:enumeration value="query.query_model_rewriter_factory" />
 										<xs:enumeration value="query.pre_transformer_registrar">

Vulnerable code:                         <row>
                            <entry>PostgreSQL</entry>
                            <entry><literal>NHibernate.Dialect.PostgreSQLDialect</literal></entry>
                            <entry>
                            </entry>
                        </row>
		<property name="dialect">NHibernate.Dialect.SybaseSQLAnywhere12Dialect</property>
			insert.AddColumn("stringColumn", "aSQLValue", (ILiteralType)NHibernateUtil.String);
		public bool SupportsSqlType(SqlType sqlType)
﻿using System.Runtime.InteropServices;
﻿namespace NHibernate.Test.TestDialects
		/// Does not support SELECT FOR UPDATE 
using System.Collections.Generic;
using System.Data;
using System.Collections.Generic;
using System.Data;
using System.Collections.Generic;
using System.Data;
using System.Collections.Generic;
using System.Data;
		/// <summary> The SQL literal value to which this database maps boolean values. </summary>
		/// <param name="value">The boolean value </param>
		/// <returns> The appropriate SQL literal. </returns>
		public virtual string ToBooleanValueString(bool value)
		{
			return value ? "1" : "0";
		}

	/// An SQL dialect for Sybase Adaptive Server Anywhere 9.0
		{
			return '\'' + value.ToString() + '\'';
		}
		public override string ObjectToSQLString(object value, Dialect.Dialect dialect) =>
			"'" + (DateTime) value + "'";
		{
			return "'" + (string)value + "'";
		}
			return value.ToString();
using NHibernate.Util;
		{
			return "'" + ToCharacter(value) + "'";
		}
		{
			return "'" + ((DateTimeOffset) value) + "'";
		}
		public override string ObjectToSQLString(object value, Dialect.Dialect dialect) =>
			"\'" + ((DateTime)value).ToShortDateString() + "\'";
			return value.ToString();
			return value.ToString();
		{
			return '\'' + GetValue(value).ToString() + '\'';
		}
			return "'" + value + "'";
using System.Collections.Generic;
using System.Data;
			return value.ToString();
using System.Collections.Generic;
using System.Data;
			return value.ToString();
			return value.ToString();
			return value.ToString();
			return value.ToString();
			return '\'' + ((DateTime)value).Ticks.ToString() + '\'';
using System.Collections.Generic;
using System.Data;
			return '\'' + ((TimeSpan)value).Ticks.ToString() + '\'';
using System.Collections.Generic;
using System.Data;
			return '\'' + ((TimeSpan)value).Ticks.ToString() + '\'';
		{
			return "'" + ((DateTime)value).ToShortTimeString() + "'";
		}
			return value.ToString();
			return value.ToString();
			return value.ToString();
		{
			return "'" + ((Uri)value).OriginalString + "'";
		}
Fixed code:                     <row>
                        <entry>
                            <literal>escape_backslash_in_strings</literal>
                        </entry>
                        <entry>
                            Indicates if the database needs to have backslash escaped in string literals.
                            The default value is dialect dependant. That is <literal>false</literal> for
                            most dialects.
                            <para>
                                <emphasis role="strong">eg.</emphasis> 
                                <literal>true</literal> | <literal>false</literal>
                            </para>
                        </entry>
                    </row>
		<property name="dialect">NHibernate.Dialect.SapSQLAnywhere17Dialect</property>
﻿//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by AsyncGenerator.
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------


using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using NHibernate.Cfg.MappingSchema;
using NHibernate.Mapping.ByCode;
using NHibernate.SqlTypes;
using NHibernate.Type;
using NUnit.Framework;
using NUnit.Framework.Internal;

namespace NHibernate.Test.NHSpecificTest.GH3516
{
	using System.Threading.Tasks;
	[TestFixture]
	public class FixtureByCodeAsync : TestCaseMappingByCode
	{

		private readonly HashSet<string> _unsupportedProperties = new();

		protected override HbmMapping GetMappings()
		{
			var mapper = new ModelMapper();
			mapper.Class<Entity>(rc =>
			{
				rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
				rc.Property(x => x.Name);
				rc.Property(x => x.FirstChar);
				rc.Property(x => x.CharacterEnum, m => m.Type<EnumCharType<CharEnum>>());
				rc.Property(x => x.UriProperty);

				rc.Property(x => x.ByteProperty);
				rc.Property(x => x.DecimalProperty);
				rc.Property(x => x.DoubleProperty);
				rc.Property(x => x.FloatProperty);
				rc.Property(x => x.ShortProperty);
				rc.Property(x => x.IntProperty);
				rc.Property(x => x.LongProperty);

				if (TestDialect.SupportsSqlType(SqlTypeFactory.SByte))
					rc.Property(x => x.SByteProperty);
				else
					_unsupportedProperties.Add(nameof(Entity.SByteProperty));

				if (TestDialect.SupportsSqlType(SqlTypeFactory.UInt16))
					rc.Property(x => x.UShortProperty);
				else
					_unsupportedProperties.Add(nameof(Entity.UShortProperty));

				if (TestDialect.SupportsSqlType(SqlTypeFactory.UInt32))
					rc.Property(x => x.UIntProperty);
				else
					_unsupportedProperties.Add(nameof(Entity.UIntProperty));

				if (TestDialect.SupportsSqlType(SqlTypeFactory.UInt64))
					rc.Property(x => x.ULongProperty);
				else
					_unsupportedProperties.Add(nameof(Entity.ULongProperty));

				rc.Property(x => x.DateTimeProperty);
				rc.Property(x => x.DateProperty, m => m.Type(NHibernateUtil.Date));
				if (TestDialect.SupportsSqlType(SqlTypeFactory.DateTimeOffSet))
					rc.Property(x => x.DateTimeOffsetProperty);
				else
					_unsupportedProperties.Add(nameof(Entity.DateTimeOffsetProperty));
				rc.Property(x => x.TimeProperty, m => m.Type(NHibernateUtil.Time));

				rc.Property(x => x.GuidProperty);
			});

			mapper.Class<BaseClass>(rc =>
			{
				rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
				rc.Discriminator(x => x.Column("StringDiscriminator"));
				rc.Property(x => x.Name);
				rc.Abstract(true);
			});
			mapper.Subclass<Subclass1>(rc => rc.DiscriminatorValue(Entity.NameWithSingleQuote));
			mapper.Subclass<Subclass2>(rc => rc.DiscriminatorValue(Entity.NameWithEscapedSingleQuote));

			mapper.Import<CharEnum>();

			return mapper.CompileMappingForAllExplicitlyAddedEntities();
		}

		private CultureInfo _backupCulture;
		private CultureInfo _backupUICulture;

		protected override void OnSetUp()
		{
			using var session = OpenSession();
			using var transaction = session.BeginTransaction();
			session.Save(
				new Entity
				{
					Name = Entity.NameWithSingleQuote,
					FirstChar = Entity.QuoteInitial,
					CharacterEnum = CharEnum.SingleQuote,
					UriProperty = Entity.UriWithSingleQuote
				});
			session.Save(
				new Entity
				{
					Name = Entity.NameWithEscapedSingleQuote,
					FirstChar = Entity.BackslashInitial,
					CharacterEnum = CharEnum.Backslash,
					UriProperty = Entity.UriWithEscapedSingleQuote
				});

			transaction.Commit();

			_backupCulture = CultureInfo.CurrentCulture;
			_backupUICulture = CultureInfo.CurrentUICulture;
		}

		protected override void OnTearDown()
		{
			if (_backupCulture != null)
			{
				CultureInfo.CurrentCulture = _backupCulture;
				CultureInfo.CurrentUICulture = _backupUICulture;
			}

			using var session = OpenSession();
			using var transaction = session.BeginTransaction();
			session.CreateQuery("delete from System.Object").ExecuteUpdate();

			transaction.Commit();
		}

		private static readonly string[] StringInjectionsProperties =
		{
			nameof(Entity.NameWithSingleQuote), nameof(Entity.NameWithEscapedSingleQuote)
		};

		[TestCaseSource(nameof(StringInjectionsProperties))]
		public void SqlInjectionInStringsAsync(string propertyName)
		{
			using var session = OpenSession();

			var query = session.CreateQuery($"from Entity e where e.Name = Entity.{propertyName}");
			IList<Entity> list = null;
			Assert.That(async () => list = await (query.ListAsync<Entity>()), Throws.Nothing);
			Assert.That(list, Has.Count.EqualTo(1), $"Unable to find entity with name {propertyName}");
		}

		private static readonly string[] SpecialNames =
		{
			"\0; drop table Entity; --",
			"\b; drop table Entity; --",
			"\n; drop table Entity; --",
			"\r; drop table Entity; --",
			"\t; drop table Entity; --",
			"\x1A; drop table Entity; --",
			"\"; drop table Entity; --",
			"\\; drop table Entity; --"
		};

		[TestCaseSource(nameof(SpecialNames))]
		public async Task StringsWithSpecialCharactersAsync(string name)
		{
			// We may not even be able to insert the entity.
			var wasInserted = false;
			try
			{
				using var s = OpenSession();
				using var t = s.BeginTransaction();
				var e = new Entity { Name = name };
				await (s.SaveAsync(e));
				await (t.CommitAsync());

				wasInserted = true;
			}
			catch (Exception e)
			{
				Assert.Warn($"The entity insertion failed with message {e}");
			}

			try
			{
				using var session = OpenSession();
				Entity.ArbitraryStringValue = name;
				var list = await (session.CreateQuery($"from Entity e where e.Name = Entity.{nameof(Entity.ArbitraryStringValue)}").ListAsync<Entity>());
				if (wasInserted && list.Count != 1)
					Assert.Warn($"Unable to find entity with name {nameof(Entity.ArbitraryStringValue)}");
			}
			catch (Exception e)
			{
				Assert.Warn($"The query has failed with message {e}");
			}

			// Check the db is not wrecked.
			if (wasInserted)
			{
				using var session = OpenSession();
				var list = await (session
					.CreateQuery("from Entity e where e.Name = :name")
					.SetString("name", name)
					.ListAsync<Entity>());
				Assert.That(list, Has.Count.EqualTo(1));
			}
			else
			{
				using var session = OpenSession();
				var all = await (session.CreateQuery("from Entity e").ListAsync<Entity>());
				Assert.That(all, Has.Count.GreaterThan(0));
			}
		}

		[Test]
		public async Task SqlInjectionInStringDiscriminatorAsync()
		{
			using var session = OpenSession();

			await (session.SaveAsync(new Subclass1 { Name = "Subclass1" }));
			await (session.SaveAsync(new Subclass2 { Name = "Subclass2" }));

			// ObjectToSQLString is used for generating the inserts.
			Assert.That(session.Flush, Throws.Nothing, "Unable to flush the subclasses");

			foreach (var entityName in new[] { nameof(Subclass1), nameof(Subclass2) })
			{
				var query = session.CreateQuery($"from {entityName}");
				IList list = null;
				Assert.That(async () => list = await (query.ListAsync()), Throws.Nothing, $"Unable to list entities of {entityName}");
				Assert.That(list, Has.Count.EqualTo(1), $"Unable to find the {entityName} entity");
			}
		}

		private static readonly string[] CharInjectionsProperties =
		{
			nameof(Entity.QuoteInitial), nameof(Entity.BackslashInitial)
		};

		[TestCaseSource(nameof(CharInjectionsProperties))]
		public void SqlInjectionInCharAsync(string propertyName)
		{
			using var session = OpenSession();
			var query = session.CreateQuery($"from Entity e where e.FirstChar = Entity.{propertyName}");
			IList<Entity> list = null;
			Assert.That(async () => list = await (query.ListAsync<Entity>()), Throws.Nothing);
			Assert.That(list, Is.Not.Null.And.Count.EqualTo(1), $"Unable to find entity with initial {propertyName}");
		}

		private static readonly string[] CharEnumInjections =
		{
			nameof(CharEnum.SingleQuote), nameof(CharEnum.Backslash)
		};

		[TestCaseSource(nameof(CharEnumInjections))]
		public void SqlInjectionWithCharEnumAsync(string enumName)
		{
			using var session = OpenSession();

			var query = session.CreateQuery($"from Entity e where e.CharacterEnum = CharEnum.{enumName}");
			IList<Entity> list = null;
			Assert.That(async () => list = await (query.ListAsync<Entity>()), Throws.Nothing);
			Assert.That(list, Has.Count.EqualTo(1), $"Unable to find entity with CharacterEnum {enumName}");
		}

		private static readonly string[] UriInjections =
		{
			nameof(Entity.UriWithSingleQuote), nameof(Entity.UriWithEscapedSingleQuote)
		};

		[TestCaseSource(nameof(UriInjections))]
		public void SqlInjectionWithUriAsync(string propertyName)
		{
			using var session = OpenSession();

			var query = session.CreateQuery($"from Entity e where e.UriProperty = Entity.{propertyName}");
			IList<Entity> list = null;
			Assert.That(async () => list = await (query.ListAsync<Entity>()), Throws.Nothing);
			Assert.That(list, Has.Count.EqualTo(1), $"Unable to find entity with UriProperty {propertyName}");
		}

		private static readonly string[] NumericalTypesInjections =
		{
			nameof(Entity.ByteProperty),
			nameof(Entity.DecimalProperty),
			nameof(Entity.DoubleProperty),
			nameof(Entity.FloatProperty),
			nameof(Entity.ShortProperty),
			nameof(Entity.IntProperty),
			nameof(Entity.LongProperty),
			nameof(Entity.SByteProperty),
			nameof(Entity.UShortProperty),
			nameof(Entity.UIntProperty),
			nameof(Entity.ULongProperty)
		};

		[TestCaseSource(nameof(NumericalTypesInjections))]
		public async Task SqlInjectionInNumericalTypeAsync(string propertyName)
		{
			Assume.That(_unsupportedProperties, Does.Not.Contains((object) propertyName), $"The {propertyName} property is unsupported by the dialect");

			Entity.ArbitraryStringValue = "0; drop table Entity; --";
			using (var session = OpenSession())
			{
				IQuery query;
				// Defining that query is invalid and should throw.
				try
				{
					query = session.CreateQuery($"from Entity e where e.{propertyName} = Entity.{nameof(Entity.ArbitraryStringValue)}");
				}
				catch (Exception ex)
				{
					// All good.
					Assert.Pass($"The wicked query creation has been rejected, as it should: {ex}");
					// Needed for the compiler who does not know "Pass" always throw.
					return;
				}

				// The query definition has been accepted, run it.
				try
				{
					await (query.ListAsync<Entity>());
				}
				catch (Exception ex)
				{
					// Expecting no exception at that point, but the test is to check if the injection succeeded.
					Assert.Warn($"The wicked query execution has failed: {ex}");
				}
			}

			// Check if we can still query Entity. If it succeeds, at least it means the injection failed.
			using (var session = OpenSession())
			{
				IList<Entity> list = null;
				Assert.That(async () => list = await (session.CreateQuery("from Entity e").ListAsync<Entity>()), Throws.Nothing);
				Assert.That(list, Has.Count.GreaterThan(0));
			}
		}

		private static readonly string[] DateTypesInjections =
		{
			nameof(Entity.DateTimeProperty),
			nameof(Entity.DateProperty),
			nameof(Entity.DateTimeOffsetProperty),
			nameof(Entity.TimeProperty)
		};

		[TestCaseSource(nameof(DateTypesInjections))]
		public void SqlInjectionWithDatetimeAsync(string propertyName)
		{
			Assume.That(_unsupportedProperties, Does.Not.Contains((object) propertyName), $"The {propertyName} property is unsupported by the dialect");

			var wickedCulture = new CultureInfo("en-US");
			if (propertyName == nameof(Entity.TimeProperty))
				wickedCulture.DateTimeFormat.ShortTimePattern = "HH:mm:ss\\'\"; drop table Entity; --\"";
			else
				wickedCulture.DateTimeFormat.ShortDatePattern = "yyyy-MM-ddTHH:mm:ss\\'\"; drop table Entity; --\"";
			CultureInfo.CurrentCulture = wickedCulture;
			CultureInfo.CurrentUICulture = wickedCulture;

			using var session = OpenSession();

			var staticPropertyName = propertyName == nameof(Entity.DateTimeOffsetProperty) ?
				nameof(Entity.StaticDateTimeOffsetProperty) : nameof(Entity.StaticDateProperty);
			var query = session.CreateQuery($"from Entity e where e.{propertyName} = Entity.{staticPropertyName}");
			IList<Entity> list = null;
			Assume.That(() => list = query.List<Entity>(), Throws.Nothing,
				"The first execution of the query failed, the injection has likely failed");
			// Execute again to check the table is still here.
			Assert.That(async () => list = await (query.ListAsync<Entity>()), Throws.Nothing,
				"The second execution of the query failed although the first one did not: the injection has succeeded");
		}

		private static readonly string[] GuidInjections =
		{
			Entity.NameWithSingleQuote, Entity.NameWithEscapedSingleQuote
		};

		[TestCaseSource(nameof(GuidInjections))]
		public void SqlInjectionWithGuidAsync(string injection)
		{
			Entity.ArbitraryStringValue = $"{Guid.NewGuid()}{injection}";
			using var session = OpenSession();

			var query = session.CreateQuery($"from Entity e where e.GuidProperty = Entity.{nameof(Entity.ArbitraryStringValue)}");
			IList<Entity> list = null;
			Assume.That(() => list = query.List<Entity>(), Throws.Nothing,
				"The first execution of the query failed, the injection has likely failed");
			// Execute again to check the table is still here.
			Assert.That(async () => list = await (query.ListAsync<Entity>()), Throws.Nothing,
				"The second execution of the query failed although the first one did not: the injection has succeeded");
		}
	}
}
﻿using System;

namespace NHibernate.Test.NHSpecificTest.GH3516
{
	public class Entity
	{
		public virtual Guid Id { get; set; }
		public virtual string Name { get; set; }
		public virtual char FirstChar { get; set; }
		public virtual CharEnum CharacterEnum { get; set; } = CharEnum.SimpleChar;
		public virtual Uri UriProperty { get; set; }

		public virtual byte ByteProperty { get; set; }
		public virtual decimal DecimalProperty { get; set; }
		public virtual double DoubleProperty { get; set; }
		public virtual float FloatProperty { get; set; }
		public virtual short ShortProperty { get; set; }
		public virtual int IntProperty { get; set; }
		public virtual long LongProperty { get; set; }
		public virtual sbyte SByteProperty { get; set; }
		public virtual ushort UShortProperty { get; set; }
		public virtual uint UIntProperty { get; set; }
		public virtual ulong ULongProperty { get; set; }

		public virtual DateTime DateTimeProperty { get; set; } = StaticDateProperty;
		public virtual DateTime DateProperty { get; set; } = StaticDateProperty;
		public virtual DateTimeOffset DateTimeOffsetProperty { get; set; } = StaticDateProperty;
		public virtual DateTime TimeProperty { get; set; } = StaticDateProperty;

		public virtual Guid GuidProperty { get; set; } = Guid.Empty;

		public const string NameWithSingleQuote = "'; drop table Entity; --";
		public const string NameWithEscapedSingleQuote = @"\'; drop table Entity; --";

		// Do not switch to property, the feature of referencing static fields in HQL does not work with properties.
		public static string ArbitraryStringValue;

		public const char QuoteInitial = '\'';
		public const char BackslashInitial = '\\';

		public static readonly Uri UriWithSingleQuote = new("https://somewhere/?sql='; drop table Entity; --");
		public static readonly Uri UriWithEscapedSingleQuote = new(@"https://somewhere/?sql=\'; drop table Entity; --");

		public static readonly DateTime StaticDateProperty = DateTime.Today;
		public static readonly DateTimeOffset StaticDateTimeOffsetProperty = DateTimeOffset.Now;
	}

	public enum CharEnum
	{
		SimpleChar = 'A',
		SingleQuote = '\'',
		Backslash = '\\'
	}
}
﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using NHibernate.Cfg.MappingSchema;
using NHibernate.Mapping.ByCode;
using NHibernate.SqlTypes;
using NHibernate.Type;
using NUnit.Framework;
using NUnit.Framework.Internal;

namespace NHibernate.Test.NHSpecificTest.GH3516
{
	[TestFixture]
	public class FixtureByCode : TestCaseMappingByCode
	{

		private readonly HashSet<string> _unsupportedProperties = new();

		protected override HbmMapping GetMappings()
		{
			var mapper = new ModelMapper();
			mapper.Class<Entity>(rc =>
			{
				rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
				rc.Property(x => x.Name);
				rc.Property(x => x.FirstChar);
				rc.Property(x => x.CharacterEnum, m => m.Type<EnumCharType<CharEnum>>());
				rc.Property(x => x.UriProperty);

				rc.Property(x => x.ByteProperty);
				rc.Property(x => x.DecimalProperty);
				rc.Property(x => x.DoubleProperty);
				rc.Property(x => x.FloatProperty);
				rc.Property(x => x.ShortProperty);
				rc.Property(x => x.IntProperty);
				rc.Property(x => x.LongProperty);

				if (TestDialect.SupportsSqlType(SqlTypeFactory.SByte))
					rc.Property(x => x.SByteProperty);
				else
					_unsupportedProperties.Add(nameof(Entity.SByteProperty));

				if (TestDialect.SupportsSqlType(SqlTypeFactory.UInt16))
					rc.Property(x => x.UShortProperty);
				else
					_unsupportedProperties.Add(nameof(Entity.UShortProperty));

				if (TestDialect.SupportsSqlType(SqlTypeFactory.UInt32))
					rc.Property(x => x.UIntProperty);
				else
					_unsupportedProperties.Add(nameof(Entity.UIntProperty));

				if (TestDialect.SupportsSqlType(SqlTypeFactory.UInt64))
					rc.Property(x => x.ULongProperty);
				else
					_unsupportedProperties.Add(nameof(Entity.ULongProperty));

				rc.Property(x => x.DateTimeProperty);
				rc.Property(x => x.DateProperty, m => m.Type(NHibernateUtil.Date));
				if (TestDialect.SupportsSqlType(SqlTypeFactory.DateTimeOffSet))
					rc.Property(x => x.DateTimeOffsetProperty);
				else
					_unsupportedProperties.Add(nameof(Entity.DateTimeOffsetProperty));
				rc.Property(x => x.TimeProperty, m => m.Type(NHibernateUtil.Time));

				rc.Property(x => x.GuidProperty);
			});

			mapper.Class<BaseClass>(rc =>
			{
				rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
				rc.Discriminator(x => x.Column("StringDiscriminator"));
				rc.Property(x => x.Name);
				rc.Abstract(true);
			});
			mapper.Subclass<Subclass1>(rc => rc.DiscriminatorValue(Entity.NameWithSingleQuote));
			mapper.Subclass<Subclass2>(rc => rc.DiscriminatorValue(Entity.NameWithEscapedSingleQuote));

			mapper.Import<CharEnum>();

			return mapper.CompileMappingForAllExplicitlyAddedEntities();
		}

		private CultureInfo _backupCulture;
		private CultureInfo _backupUICulture;

		protected override void OnSetUp()
		{
			using var session = OpenSession();
			using var transaction = session.BeginTransaction();
			session.Save(
				new Entity
				{
					Name = Entity.NameWithSingleQuote,
					FirstChar = Entity.QuoteInitial,
					CharacterEnum = CharEnum.SingleQuote,
					UriProperty = Entity.UriWithSingleQuote
				});
			session.Save(
				new Entity
				{
					Name = Entity.NameWithEscapedSingleQuote,
					FirstChar = Entity.BackslashInitial,
					CharacterEnum = CharEnum.Backslash,
					UriProperty = Entity.UriWithEscapedSingleQuote
				});

			transaction.Commit();

			_backupCulture = CultureInfo.CurrentCulture;
			_backupUICulture = CultureInfo.CurrentUICulture;
		}

		protected override void OnTearDown()
		{
			if (_backupCulture != null)
			{
				CultureInfo.CurrentCulture = _backupCulture;
				CultureInfo.CurrentUICulture = _backupUICulture;
			}

			using var session = OpenSession();
			using var transaction = session.BeginTransaction();
			session.CreateQuery("delete from System.Object").ExecuteUpdate();

			transaction.Commit();
		}

		private static readonly string[] StringInjectionsProperties =
		{
			nameof(Entity.NameWithSingleQuote), nameof(Entity.NameWithEscapedSingleQuote)
		};

		[TestCaseSource(nameof(StringInjectionsProperties))]
		public void SqlInjectionInStrings(string propertyName)
		{
			using var session = OpenSession();

			var query = session.CreateQuery($"from Entity e where e.Name = Entity.{propertyName}");
			IList<Entity> list = null;
			Assert.That(() => list = query.List<Entity>(), Throws.Nothing);
			Assert.That(list, Has.Count.EqualTo(1), $"Unable to find entity with name {propertyName}");
		}

		private static readonly string[] SpecialNames =
		{
			"\0; drop table Entity; --",
			"\b; drop table Entity; --",
			"\n; drop table Entity; --",
			"\r; drop table Entity; --",
			"\t; drop table Entity; --",
			"\x1A; drop table Entity; --",
			"\"; drop table Entity; --",
			"\\; drop table Entity; --"
		};

		[TestCaseSource(nameof(SpecialNames))]
		public void StringsWithSpecialCharacters(string name)
		{
			// We may not even be able to insert the entity.
			var wasInserted = false;
			try
			{
				using var s = OpenSession();
				using var t = s.BeginTransaction();
				var e = new Entity { Name = name };
				s.Save(e);
				t.Commit();

				wasInserted = true;
			}
			catch (Exception e)
			{
				Assert.Warn($"The entity insertion failed with message {e}");
			}

			try
			{
				using var session = OpenSession();
				Entity.ArbitraryStringValue = name;
				var list = session.CreateQuery($"from Entity e where e.Name = Entity.{nameof(Entity.ArbitraryStringValue)}").List<Entity>();
				if (wasInserted && list.Count != 1)
					Assert.Warn($"Unable to find entity with name {nameof(Entity.ArbitraryStringValue)}");
			}
			catch (Exception e)
			{
				Assert.Warn($"The query has failed with message {e}");
			}

			// Check the db is not wrecked.
			if (wasInserted)
			{
				using var session = OpenSession();
				var list = session
					.CreateQuery("from Entity e where e.Name = :name")
					.SetString("name", name)
					.List<Entity>();
				Assert.That(list, Has.Count.EqualTo(1));
			}
			else
			{
				using var session = OpenSession();
				var all = session.CreateQuery("from Entity e").List<Entity>();
				Assert.That(all, Has.Count.GreaterThan(0));
			}
		}

		[Test]
		public void SqlInjectionInStringDiscriminator()
		{
			using var session = OpenSession();

			session.Save(new Subclass1 { Name = "Subclass1" });
			session.Save(new Subclass2 { Name = "Subclass2" });

			// ObjectToSQLString is used for generating the inserts.
			Assert.That(session.Flush, Throws.Nothing, "Unable to flush the subclasses");

			foreach (var entityName in new[] { nameof(Subclass1), nameof(Subclass2) })
			{
				var query = session.CreateQuery($"from {entityName}");
				IList list = null;
				Assert.That(() => list = query.List(), Throws.Nothing, $"Unable to list entities of {entityName}");
				Assert.That(list, Has.Count.EqualTo(1), $"Unable to find the {entityName} entity");
			}
		}

		private static readonly string[] CharInjectionsProperties =
		{
			nameof(Entity.QuoteInitial), nameof(Entity.BackslashInitial)
		};

		[TestCaseSource(nameof(CharInjectionsProperties))]
		public void SqlInjectionInChar(string propertyName)
		{
			using var session = OpenSession();
			var query = session.CreateQuery($"from Entity e where e.FirstChar = Entity.{propertyName}");
			IList<Entity> list = null;
			Assert.That(() => list = query.List<Entity>(), Throws.Nothing);
			Assert.That(list, Is.Not.Null.And.Count.EqualTo(1), $"Unable to find entity with initial {propertyName}");
		}

		private static readonly string[] CharEnumInjections =
		{
			nameof(CharEnum.SingleQuote), nameof(CharEnum.Backslash)
		};

		[TestCaseSource(nameof(CharEnumInjections))]
		public void SqlInjectionWithCharEnum(string enumName)
		{
			using var session = OpenSession();

			var query = session.CreateQuery($"from Entity e where e.CharacterEnum = CharEnum.{enumName}");
			IList<Entity> list = null;
			Assert.That(() => list = query.List<Entity>(), Throws.Nothing);
			Assert.That(list, Has.Count.EqualTo(1), $"Unable to find entity with CharacterEnum {enumName}");
		}

		private static readonly string[] UriInjections =
		{
			nameof(Entity.UriWithSingleQuote), nameof(Entity.UriWithEscapedSingleQuote)
		};

		[TestCaseSource(nameof(UriInjections))]
		public void SqlInjectionWithUri(string propertyName)
		{
			using var session = OpenSession();

			var query = session.CreateQuery($"from Entity e where e.UriProperty = Entity.{propertyName}");
			IList<Entity> list = null;
			Assert.That(() => list = query.List<Entity>(), Throws.Nothing);
			Assert.That(list, Has.Count.EqualTo(1), $"Unable to find entity with UriProperty {propertyName}");
		}

		private static readonly string[] NumericalTypesInjections =
		{
			nameof(Entity.ByteProperty),
			nameof(Entity.DecimalProperty),
			nameof(Entity.DoubleProperty),
			nameof(Entity.FloatProperty),
			nameof(Entity.ShortProperty),
			nameof(Entity.IntProperty),
			nameof(Entity.LongProperty),
			nameof(Entity.SByteProperty),
			nameof(Entity.UShortProperty),
			nameof(Entity.UIntProperty),
			nameof(Entity.ULongProperty)
		};

		[TestCaseSource(nameof(NumericalTypesInjections))]
		public void SqlInjectionInNumericalType(string propertyName)
		{
			Assume.That(_unsupportedProperties, Does.Not.Contains((object) propertyName), $"The {propertyName} property is unsupported by the dialect");

			Entity.ArbitraryStringValue = "0; drop table Entity; --";
			using (var session = OpenSession())
			{
				IQuery query;
				// Defining that query is invalid and should throw.
				try
				{
					query = session.CreateQuery($"from Entity e where e.{propertyName} = Entity.{nameof(Entity.ArbitraryStringValue)}");
				}
				catch (Exception ex)
				{
					// All good.
					Assert.Pass($"The wicked query creation has been rejected, as it should: {ex}");
					// Needed for the compiler who does not know "Pass" always throw.
					return;
				}

				// The query definition has been accepted, run it.
				try
				{
					query.List<Entity>();
				}
				catch (Exception ex)
				{
					// Expecting no exception at that point, but the test is to check if the injection succeeded.
					Assert.Warn($"The wicked query execution has failed: {ex}");
				}
			}

			// Check if we can still query Entity. If it succeeds, at least it means the injection failed.
			using (var session = OpenSession())
			{
				IList<Entity> list = null;
				Assert.That(() => list = session.CreateQuery("from Entity e").List<Entity>(), Throws.Nothing);
				Assert.That(list, Has.Count.GreaterThan(0));
			}
		}

		private static readonly string[] DateTypesInjections =
		{
			nameof(Entity.DateTimeProperty),
			nameof(Entity.DateProperty),
			nameof(Entity.DateTimeOffsetProperty),
			nameof(Entity.TimeProperty)
		};

		[TestCaseSource(nameof(DateTypesInjections))]
		public void SqlInjectionWithDatetime(string propertyName)
		{
			Assume.That(_unsupportedProperties, Does.Not.Contains((object) propertyName), $"The {propertyName} property is unsupported by the dialect");

			var wickedCulture = new CultureInfo("en-US");
			if (propertyName == nameof(Entity.TimeProperty))
				wickedCulture.DateTimeFormat.ShortTimePattern = "HH:mm:ss\\'\"; drop table Entity; --\"";
			else
				wickedCulture.DateTimeFormat.ShortDatePattern = "yyyy-MM-ddTHH:mm:ss\\'\"; drop table Entity; --\"";
			CultureInfo.CurrentCulture = wickedCulture;
			CultureInfo.CurrentUICulture = wickedCulture;

			using var session = OpenSession();

			var staticPropertyName = propertyName == nameof(Entity.DateTimeOffsetProperty) ?
				nameof(Entity.StaticDateTimeOffsetProperty) : nameof(Entity.StaticDateProperty);
			var query = session.CreateQuery($"from Entity e where e.{propertyName} = Entity.{staticPropertyName}");
			IList<Entity> list = null;
			Assume.That(() => list = query.List<Entity>(), Throws.Nothing,
				"The first execution of the query failed, the injection has likely failed");
			// Execute again to check the table is still here.
			Assert.That(() => list = query.List<Entity>(), Throws.Nothing,
				"The second execution of the query failed although the first one did not: the injection has succeeded");
		}

		private static readonly string[] GuidInjections =
		{
			Entity.NameWithSingleQuote, Entity.NameWithEscapedSingleQuote
		};

		[TestCaseSource(nameof(GuidInjections))]
		public void SqlInjectionWithGuid(string injection)
		{
			Entity.ArbitraryStringValue = $"{Guid.NewGuid()}{injection}";
			using var session = OpenSession();

			var query = session.CreateQuery($"from Entity e where e.GuidProperty = Entity.{nameof(Entity.ArbitraryStringValue)}");
			IList<Entity> list = null;
			Assume.That(() => list = query.List<Entity>(), Throws.Nothing,
				"The first execution of the query failed, the injection has likely failed");
			// Execute again to check the table is still here.
			Assert.That(() => list = query.List<Entity>(), Throws.Nothing,
				"The second execution of the query failed although the first one did not: the injection has succeeded");
		}
	}
}
﻿using System;

namespace NHibernate.Test.NHSpecificTest.GH3516
{
	public class BaseClass
	{
		public virtual Guid Id { get; set; }
		public virtual string Name { get; set; }
	}

	public class Subclass1 : BaseClass
	{
	}

	public class Subclass2 : BaseClass
	{
	}
}
			insert.AddColumn("stringColumn", "aSQLValue", (ILiteralType)NHibernateUtil.AnsiString);
		public virtual bool SupportsSqlType(SqlType sqlType)
﻿using System.Data;
using System.Runtime.InteropServices;
using NHibernate.SqlTypes;
		public override bool SupportsSqlType(SqlType sqlType)
		{
			// The Oracle dialects define types for DbType the Oracle driver does not support.
			return sqlType.DbType switch
			{
				DbType.UInt16 or DbType.UInt32 or DbType.UInt64 => false,
				_ => base.SupportsSqlType(sqlType)
			};
		}

﻿using System.Data;
using NHibernate.SqlTypes;

namespace NHibernate.Test.TestDialects
		/// Does not support SELECT FOR UPDATE.

		public override bool SupportsSqlType(SqlType sqlType)
		{
			// The Anywhere dialects define types for DbType the Anywhere driver does not support.
			return sqlType.DbType switch
			{
				DbType.SByte => false,
				_ => base.SupportsSqlType(sqlType)
			};
		}
using System.Globalization;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Globalization;
using System.Globalization;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Globalization;
using System.Globalization;
using System.Globalization;
		/// <summary>
		/// Indicates if the database needs to have backslash escaped in string literals.
		/// </summary>
		/// <remarks>The default value is dialect dependent.</remarks>
		public const string EscapeBackslashInStrings = "escape_backslash_in_strings";

using NHibernate.SqlTypes;
		/// <inheritdoc />
		public override string ToStringLiteral(string value, SqlType type)
		{
			if (value == null)
				throw new System.ArgumentNullException(nameof(value));
			if (type == null)
				throw new System.ArgumentNullException(nameof(value));

			// See https://www.ibm.com/docs/en/db2/11.5?topic=elements-constants#r0000731__title__7
			var literal = new StringBuilder(value);
			var isUnicode = type.DbType == DbType.String || type.DbType == DbType.StringFixedLength;
			if (isUnicode)
				literal.Replace(@"\", @"\\");

			literal
				.Replace("'", "''")
				.Insert(0, '\'')
				.Append('\'');

			if (isUnicode)
				literal.Insert(0, "U&");
			return literal.ToString();
		}

			EscapeBackslashInStrings = PropertiesHelper.GetBoolean(Environment.EscapeBackslashInStrings, settings, EscapeBackslashInStrings);
		#region Literals support

		/// <summary>The SQL literal value to which this database maps boolean values.</summary>
		/// <param name="value">The boolean value.</param>
		/// <returns>The appropriate SQL literal.</returns>
		public virtual string ToBooleanValueString(bool value)
			=> value ? "1" : "0";

		/// <summary>
		/// <see langword="true" /> if the database needs to have backslash escaped in string literals.
		/// </summary>
		/// <remarks><see langword="false" /> by default in the base dialect, to conform to SQL standard.</remarks>
		protected virtual bool EscapeBackslashInStrings { get; set; }

		/// <summary>
		/// <see langword="true" /> if the database needs to have Unicode literals prefixed by <c>N</c>.
		/// </summary>
		/// <remarks><see langword="false" /> by default in the base dialect.</remarks>
		protected virtual bool UseNPrefixForUnicodeStrings => false;

		/// <summary>The SQL string literal value to which this database maps string values.</summary>
		/// <param name="value">The string value.</param>
		/// <param name="type">The SQL type of the string value.</param>
		/// <returns>The appropriate SQL string literal.</returns>
		/// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> or
		/// <paramref name="type"/> is <see langword="null" />.</exception>
		public virtual string ToStringLiteral(string value, SqlType type)
		{
			if (value == null)
				throw new ArgumentNullException(nameof(value));
			if (type == null)
				throw new ArgumentNullException(nameof(type));

			var literal = new StringBuilder(value);
			if (EscapeBackslashInStrings)
				literal.Replace(@"\", @"\\");

			literal
				.Replace("'", "''")
				.Insert(0, '\'')
				.Append('\'');

			if (UseNPrefixForUnicodeStrings && (type.DbType == DbType.String || type.DbType == DbType.StringFixedLength))
				literal.Insert(0, 'N');
			return literal.ToString();
		}

		#endregion

		/// <inheritdoc />
		/// <remarks><see langword="true" /> by default for Ingres,
		/// <see href="https://docs.actian.com/ingres/11.0/index.html#page/SQLRef/String_Literals.htm#ww110572" />.</remarks>
		protected override bool UseNPrefixForUnicodeStrings => true;

		/// <inheritdoc />
		/// <remarks><see langword="true" /> by default for SQL Server.</remarks>
		protected override bool UseNPrefixForUnicodeStrings => true;

		/// <inheritdoc />
		/// <remarks><see langword="true" /> by default for MySQL,
		/// <see href="https://dev.mysql.com/doc/refman/8.0/en/string-literals.html" />.</remarks>
		protected override bool EscapeBackslashInStrings { get; set; } = true;

		/// <inheritdoc />
		/// <remarks><see langword="true" /> by default for MySQL,
		/// <see href="https://dev.mysql.com/doc/refman/8.0/en/string-literals.html" />.</remarks>
		protected override bool UseNPrefixForUnicodeStrings => true;


		/// <inheritdoc />
		/// <remarks>Returns the same value as <see cref="UseNPrefixedTypesForUnicode" />.</remarks>
		protected override bool UseNPrefixForUnicodeStrings => UseNPrefixedTypesForUnicode;

	/// An SQL dialect for Sybase Adaptive Server Anywhere 9.0. (Renamed SQL Anywhere from its version 10.)

		/// <inheritdoc />
		/// <remarks><see langword="true" /> by default for SQL Anywhere,
		/// <see href="https://help.sap.com/docs/SAP_SQL_Anywhere/93079d4ba8e44920ae63ffb4def91f5b/817a3ded6ce21014bd99f3e554573180.html?version=17.0" />.</remarks>
		protected override bool EscapeBackslashInStrings { get; set; } = true;

		/// <inheritdoc />
		/// <remarks><see langword="true" /> by default for SQL Anywhere,
		/// <see href="https://help.sap.com/docs/SAP_SQL_Anywhere/93079d4ba8e44920ae63ffb4def91f5b/817a2c5f6ce21014aceea962de72126c.html?version=17.0" />.</remarks>
		protected override bool UseNPrefixForUnicodeStrings => true;

		/// <inheritdoc />
		/// <remarks><see langword="true" /> by default for SQL Anywhere,
		/// <see href="https://help.sap.com/docs/SAP_SQL_Anywhere/93079d4ba8e44920ae63ffb4def91f5b/817a3ded6ce21014bd99f3e554573180.html?version=17.0" />.</remarks>
		protected override bool EscapeBackslashInStrings { get; set; } = true;

		/// <inheritdoc />
		/// <remarks><see langword="true" /> by default for SQL Anywhere,
		/// <see href="https://help.sap.com/docs/SAP_SQL_Anywhere/93079d4ba8e44920ae63ffb4def91f5b/817a2c5f6ce21014aceea962de72126c.html?version=17.0" />.</remarks>
		protected override bool UseNPrefixForUnicodeStrings => true;
			=> dialect.ToStringLiteral(value.ToString(), SqlType);
		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
			=> dialect.ToStringLiteral(((DateTime) value).ToString(), SqlTypeFactory.GetAnsiString(50));
		/// <inheritdoc />
			=> dialect.ToStringLiteral((string)value, SqlType);
using System.Globalization;
			return ((byte)value).ToString(CultureInfo.InvariantCulture);
			=> dialect.ToStringLiteral(ToCharacter(value), SqlType);
			=> dialect.ToStringLiteral(((DateTimeOffset) value).ToString(), SqlTypeFactory.GetAnsiString(50));
		public override string ObjectToSQLString(object value, Dialect.Dialect dialect)
			=> dialect.ToStringLiteral(((DateTime) value).ToShortDateString(), SqlTypeFactory.GetAnsiString(50));
using System.Globalization;
			return ((decimal)value).ToString(CultureInfo.InvariantCulture);
using System.Globalization;
			return ((double)value).ToString(CultureInfo.InvariantCulture);
			=> dialect.ToStringLiteral(GetValue(value).ToString(), SqlType);
			return dialect.ToStringLiteral(value.ToString(), SqlTypeFactory.GetAnsiString(50));
using System.Collections.Generic;
using System.Data;
using System.Globalization;
			return ((short)value).ToString(CultureInfo.InvariantCulture);
using System.Collections.Generic;
using System.Data;
using System.Globalization;
			return ((int)value).ToString(CultureInfo.InvariantCulture);
using System.Globalization;
			return ((long)value).ToString(CultureInfo.InvariantCulture);
using System.Globalization;
			return ((sbyte)value).ToString(CultureInfo.InvariantCulture);
using System.Globalization;
			return ((float)value).ToString(CultureInfo.InvariantCulture);
using System.Globalization;
			return '\'' + ((DateTime)value).Ticks.ToString(CultureInfo.InvariantCulture) + '\'';
using System.Collections.Generic;
using System.Data;
using System.Globalization;
			return '\'' + ((TimeSpan)value).Ticks.ToString(CultureInfo.InvariantCulture) + '\'';
using System.Collections.Generic;
using System.Data;
using System.Globalization;
			return '\'' + ((TimeSpan)value).Ticks.ToString(CultureInfo.InvariantCulture) + '\'';
			=> dialect.ToStringLiteral(((DateTime) value).ToShortTimeString(), SqlTypeFactory.GetAnsiString(50));
using System.Globalization;
			return ((ushort)value).ToString(CultureInfo.InvariantCulture);
using System.Globalization;
			return ((uint)value).ToString(CultureInfo.InvariantCulture);
using System.Globalization;
			return ((ulong)value).ToString(CultureInfo.InvariantCulture);
			=> dialect.ToStringLiteral(((Uri) value).OriginalString, SqlType);
										<xs:enumeration value="escape_backslash_in_strings">
											<xs:annotation>
												<xs:documentation>
													Indicates if the database needs to have backslash escaped in string literals. The default is
													dialect dependent.
												</xs:documentation>
											</xs:annotation>
										</xs:enumeration>