From 6dfa0b747dcd8abfb27bc5c3c85309e54b830055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= Date: Fri, 22 Sep 2017 19:24:40 +0200 Subject: [PATCH 1/3] NH-3884 - Allow redefining the NHibernate default type for a .Net type. --- .../TypesTest/ChangeDefaultTypeClass.cs | 11 +++ .../TypesTest/ChangeDefaultTypeClass.hbm.xml | 16 ++++ .../TypesTest/ChangeDefaultTypeFixture.cs | 85 +++++++++++++++++++ src/NHibernate/Type/TypeFactory.cs | 20 +++++ 4 files changed, 132 insertions(+) create mode 100644 src/NHibernate.Test/TypesTest/ChangeDefaultTypeClass.cs create mode 100644 src/NHibernate.Test/TypesTest/ChangeDefaultTypeClass.hbm.xml create mode 100644 src/NHibernate.Test/TypesTest/ChangeDefaultTypeFixture.cs diff --git a/src/NHibernate.Test/TypesTest/ChangeDefaultTypeClass.cs b/src/NHibernate.Test/TypesTest/ChangeDefaultTypeClass.cs new file mode 100644 index 00000000000..1dca978091e --- /dev/null +++ b/src/NHibernate.Test/TypesTest/ChangeDefaultTypeClass.cs @@ -0,0 +1,11 @@ +using System; + +namespace NHibernate.Test.TypesTest +{ + public class ChangeDefaultTypeClass + { + public int Id { get; set; } + + public DateTime NormalDateTimeValue { get; set; } = DateTime.Today; + } +} diff --git a/src/NHibernate.Test/TypesTest/ChangeDefaultTypeClass.hbm.xml b/src/NHibernate.Test/TypesTest/ChangeDefaultTypeClass.hbm.xml new file mode 100644 index 00000000000..f2cfaf24dec --- /dev/null +++ b/src/NHibernate.Test/TypesTest/ChangeDefaultTypeClass.hbm.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/src/NHibernate.Test/TypesTest/ChangeDefaultTypeFixture.cs b/src/NHibernate.Test/TypesTest/ChangeDefaultTypeFixture.cs new file mode 100644 index 00000000000..75b3fe95236 --- /dev/null +++ b/src/NHibernate.Test/TypesTest/ChangeDefaultTypeFixture.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using NHibernate.Cfg; +using NHibernate.Engine; +using NHibernate.Impl; +using NHibernate.Type; +using NUnit.Framework; + +namespace NHibernate.Test.TypesTest +{ + /// + /// TestFixtures for changing a default .Net type. + /// + [TestFixture] + public class ChangeDefaultTypeFixture : TypeFixtureBase + { + protected override string TypeName => "ChangeDefaultType"; + + private IType _originalDefaultDateTimeType; + private IType _testDefaultDateTimeType; + + protected override void Configure(Configuration configuration) + { + _originalDefaultDateTimeType = TypeFactory.GetDefaultTypeFor(typeof(DateTime)); + Assert.That(_originalDefaultDateTimeType, Is.Not.Null); + _testDefaultDateTimeType = NHibernateUtil.DateTime.Equals(_originalDefaultDateTimeType) + ? (IType) NHibernateUtil.Timestamp + : NHibernateUtil.DateTime; + TypeFactory.SetDefaultType(_testDefaultDateTimeType); + base.Configure(configuration); + } + + protected override void DropSchema() + { + base.DropSchema(); + if (_originalDefaultDateTimeType != null) + TypeFactory.SetDefaultType(_originalDefaultDateTimeType); + } + + [Test] + public void DefaultType() + { + Assert.That(TypeFactory.GetDefaultTypeFor(typeof(DateTime)), Is.EqualTo(_testDefaultDateTimeType)); + } + + [Test] + public void PropertyType() + { + Assert.That( + Sfi.GetClassMetadata(typeof(ChangeDefaultTypeClass)) + .GetPropertyType(nameof(ChangeDefaultTypeClass.NormalDateTimeValue)), + Is.EqualTo(_testDefaultDateTimeType)); + } + + [Test] + public void GuessType() + { + Assert.That(NHibernateUtil.GuessType(typeof(DateTime)), Is.EqualTo(_testDefaultDateTimeType)); + } + + [Test] + public void ParameterType() + { + var namedParametersField = typeof(AbstractQueryImpl) + .GetField("namedParameters", BindingFlags.Instance | BindingFlags.NonPublic); + Assert.That(namedParametersField, Is.Not.Null, "Missing internal field"); + + using (var s = OpenSession()) + { + // Query where the parameter type cannot be deducted from compared entity property + var q = s.CreateQuery($"from {nameof(ChangeDefaultTypeClass)} where :date1 = :date2 or :date1 = :date3") + .SetParameter("date1", DateTime.Now) + .SetDateTime("date2", DateTime.Now) + .SetTimestamp("date3", DateTime.Now); + + var namedParameters = namedParametersField.GetValue(q) as Dictionary; + Assert.That(namedParameters, Is.Not.Null, "Unable to retrieve parameters internal field"); + Assert.That(namedParameters["date1"].Type, Is.EqualTo(_testDefaultDateTimeType)); + Assert.That(namedParameters["date2"].Type, Is.EqualTo(NHibernateUtil.DateTime)); + Assert.That(namedParameters["date3"].Type, Is.EqualTo(NHibernateUtil.Timestamp)); + } + } + } +} diff --git a/src/NHibernate/Type/TypeFactory.cs b/src/NHibernate/Type/TypeFactory.cs index 9d14ce6812f..fbdf701c3a2 100644 --- a/src/NHibernate/Type/TypeFactory.cs +++ b/src/NHibernate/Type/TypeFactory.cs @@ -6,6 +6,7 @@ using System.Xml; using System.Xml.Linq; using NHibernate.Bytecode; +using NHibernate.Cfg; using NHibernate.Classic; using NHibernate.SqlTypes; using NHibernate.UserTypes; @@ -313,6 +314,25 @@ private static void RegisterBuiltInTypes() len => new SerializableType(typeof (object), SqlTypeFactory.GetBinary(len)))); } + /// + /// Defines which NHibernate type should be chosen by default for handling a given .Net type. + /// This must be done before any operation on NHibernate, including building its + /// and building session factory. Otherwise the behavior will be undefined. + /// + /// The NHibernate type. + /// The .Net type. + public static void SetDefaultType(IType targetType) + { + if (targetType == null) + throw new ArgumentNullException(nameof(targetType)); + + var type = typeof(T); + foreach (var alias in GetClrTypeAliases(type)) + { + typeByTypeOfName[alias] = targetType; + } + } + private static ICollectionTypeFactory CollectionTypeFactory => Environment.BytecodeProvider.CollectionTypeFactory; From 6d02db33163ce48410118b6460c8b038aa1277ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= Date: Thu, 28 Sep 2017 18:10:45 +0200 Subject: [PATCH 2/3] NH-3884 - Alternate implementation without a new method. --- .../TypesTest/ChangeDefaultTypeFixture.cs | 10 +++--- src/NHibernate/Type/TypeFactory.cs | 32 +++++++------------ 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/src/NHibernate.Test/TypesTest/ChangeDefaultTypeFixture.cs b/src/NHibernate.Test/TypesTest/ChangeDefaultTypeFixture.cs index 75b3fe95236..d54f3876f33 100644 --- a/src/NHibernate.Test/TypesTest/ChangeDefaultTypeFixture.cs +++ b/src/NHibernate.Test/TypesTest/ChangeDefaultTypeFixture.cs @@ -25,9 +25,9 @@ protected override void Configure(Configuration configuration) _originalDefaultDateTimeType = TypeFactory.GetDefaultTypeFor(typeof(DateTime)); Assert.That(_originalDefaultDateTimeType, Is.Not.Null); _testDefaultDateTimeType = NHibernateUtil.DateTime.Equals(_originalDefaultDateTimeType) - ? (IType) NHibernateUtil.Timestamp + ? (IType) NHibernateUtil.DateTimeNoMs : NHibernateUtil.DateTime; - TypeFactory.SetDefaultType(_testDefaultDateTimeType); + TypeFactory.RegisterType(typeof(DateTime), _testDefaultDateTimeType, TypeFactory.EmptyAliases); base.Configure(configuration); } @@ -35,7 +35,7 @@ protected override void DropSchema() { base.DropSchema(); if (_originalDefaultDateTimeType != null) - TypeFactory.SetDefaultType(_originalDefaultDateTimeType); + TypeFactory.RegisterType(typeof(DateTime), _originalDefaultDateTimeType, TypeFactory.EmptyAliases); } [Test] @@ -72,13 +72,13 @@ public void ParameterType() var q = s.CreateQuery($"from {nameof(ChangeDefaultTypeClass)} where :date1 = :date2 or :date1 = :date3") .SetParameter("date1", DateTime.Now) .SetDateTime("date2", DateTime.Now) - .SetTimestamp("date3", DateTime.Now); + .SetDateTimeNoMs("date3", DateTime.Now); var namedParameters = namedParametersField.GetValue(q) as Dictionary; Assert.That(namedParameters, Is.Not.Null, "Unable to retrieve parameters internal field"); Assert.That(namedParameters["date1"].Type, Is.EqualTo(_testDefaultDateTimeType)); Assert.That(namedParameters["date2"].Type, Is.EqualTo(NHibernateUtil.DateTime)); - Assert.That(namedParameters["date3"].Type, Is.EqualTo(NHibernateUtil.Timestamp)); + Assert.That(namedParameters["date3"].Type, Is.EqualTo(NHibernateUtil.DateTimeNoMs)); } } } diff --git a/src/NHibernate/Type/TypeFactory.cs b/src/NHibernate/Type/TypeFactory.cs index fbdf701c3a2..cfa5e405e47 100644 --- a/src/NHibernate/Type/TypeFactory.cs +++ b/src/NHibernate/Type/TypeFactory.cs @@ -34,8 +34,9 @@ private enum TypeClassification PrecisionScale } + public static readonly string[] EmptyAliases = System.Array.Empty(); + private static readonly INHibernateLogger _log = NHibernateLogger.For(typeof(TypeFactory)); - private static readonly string[] EmptyAliases= System.Array.Empty(); private static readonly char[] PrecisionScaleSplit = { '(', ')', ',' }; private static readonly char[] LengthSplit = { '(', ')' }; @@ -96,7 +97,15 @@ private enum TypeClassification private delegate NullableType NullableTypeCreatorDelegate(SqlType sqlType); - private static void RegisterType(System.Type systemType, IType nhibernateType, IEnumerable aliases) + /// + /// Defines which NHibernate type should be chosen by default for handling a given .Net type. + /// This must be done before any operation on NHibernate, including building its + /// and building session factory. Otherwise the behavior will be undefined. + /// + /// The .Net type. + /// The NHibernate type. + /// The additional aliases to map to the type. Use if none. + public static void RegisterType(System.Type systemType, IType nhibernateType, IEnumerable aliases) { var typeAliases = new List(aliases); typeAliases.AddRange(GetClrTypeAliases(systemType)); @@ -314,25 +323,6 @@ private static void RegisterBuiltInTypes() len => new SerializableType(typeof (object), SqlTypeFactory.GetBinary(len)))); } - /// - /// Defines which NHibernate type should be chosen by default for handling a given .Net type. - /// This must be done before any operation on NHibernate, including building its - /// and building session factory. Otherwise the behavior will be undefined. - /// - /// The NHibernate type. - /// The .Net type. - public static void SetDefaultType(IType targetType) - { - if (targetType == null) - throw new ArgumentNullException(nameof(targetType)); - - var type = typeof(T); - foreach (var alias in GetClrTypeAliases(type)) - { - typeByTypeOfName[alias] = targetType; - } - } - private static ICollectionTypeFactory CollectionTypeFactory => Environment.BytecodeProvider.CollectionTypeFactory; From 44b566a8a4afbba820fff9e6876c0e5613792f2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Delaporte?= Date: Tue, 16 Jan 2018 16:19:09 +0100 Subject: [PATCH 3/3] Document default type override --- doc/reference/modules/basic_mapping.xml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/reference/modules/basic_mapping.xml b/doc/reference/modules/basic_mapping.xml index 839a3880ff5..b2407efade5 100644 --- a/doc/reference/modules/basic_mapping.xml +++ b/doc/reference/modules/basic_mapping.xml @@ -2633,7 +2633,7 @@ types, System.Object types, and System.Object types for large objects. Just like Columns for System.ValueType types can handle null values only if the entity property is properly typed with a Nullable<T>. Otherwise null will be replaced by the default - value for the type when reading, and when be overwritten by it when persisting the entity, potentially leading to + value for the type when reading, and then will be overwritten by it when persisting the entity, potentially leading to phantom updates. @@ -3025,6 +3025,12 @@ NHibernate.Type.TypeFactory. + + Default NHibernate types used when no type attribute is specified can be overridden by using + the NHibernate.Type.TypeFactory.RegisterType static method before configuring and building + session factories. + +