Skip to content

Commit 2161518

Browse files
NH-3884 - Default DateTime type can not be overridden (#702)
1 parent a46b2d5 commit 2161518

File tree

5 files changed

+131
-3
lines changed

5 files changed

+131
-3
lines changed

doc/reference/modules/basic_mapping.xml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2633,7 +2633,7 @@
26332633
types, <literal>System.Object</literal> types, and <literal>System.Object</literal> types for large objects. Just like
26342634
Columns for System.ValueType types can handle <literal>null</literal> values only if the entity property is properly
26352635
typed with a <literal>Nullable&lt;T&gt;</literal>. Otherwise <literal>null</literal> will be replaced by the default
2636-
value for the type when reading, and when be overwritten by it when persisting the entity, potentially leading to
2636+
value for the type when reading, and then will be overwritten by it when persisting the entity, potentially leading to
26372637
phantom updates.
26382638
</para>
26392639
<table>
@@ -3025,6 +3025,12 @@
30253025
<literal>NHibernate.Type.TypeFactory</literal>.
30263026
</para>
30273027

3028+
<para>
3029+
Default NHibernate types used when no <literal>type</literal> attribute is specified can be overridden by using
3030+
the <literal>NHibernate.Type.TypeFactory.RegisterType</literal> static method before configuring and building
3031+
session factories.
3032+
</para>
3033+
30283034
</sect2>
30293035

30303036
<sect2 id="mapping-types-custom">
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System;
2+
3+
namespace NHibernate.Test.TypesTest
4+
{
5+
public class ChangeDefaultTypeClass
6+
{
7+
public int Id { get; set; }
8+
9+
public DateTime NormalDateTimeValue { get; set; } = DateTime.Today;
10+
}
11+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
3+
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-lazy="false">
4+
5+
<class
6+
name="NHibernate.Test.TypesTest.ChangeDefaultTypeClass, NHibernate.Test"
7+
table="bc_datetime"
8+
>
9+
10+
<id name="Id" column="id">
11+
<generator class="assigned"/>
12+
</id>
13+
14+
<property name="NormalDateTimeValue"/>
15+
</class>
16+
</hibernate-mapping>
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Reflection;
4+
using NHibernate.Cfg;
5+
using NHibernate.Engine;
6+
using NHibernate.Impl;
7+
using NHibernate.Type;
8+
using NUnit.Framework;
9+
10+
namespace NHibernate.Test.TypesTest
11+
{
12+
/// <summary>
13+
/// TestFixtures for changing a default .Net type.
14+
/// </summary>
15+
[TestFixture]
16+
public class ChangeDefaultTypeFixture : TypeFixtureBase
17+
{
18+
protected override string TypeName => "ChangeDefaultType";
19+
20+
private IType _originalDefaultDateTimeType;
21+
private IType _testDefaultDateTimeType;
22+
23+
protected override void Configure(Configuration configuration)
24+
{
25+
_originalDefaultDateTimeType = TypeFactory.GetDefaultTypeFor(typeof(DateTime));
26+
Assert.That(_originalDefaultDateTimeType, Is.Not.Null);
27+
_testDefaultDateTimeType = NHibernateUtil.DateTime.Equals(_originalDefaultDateTimeType)
28+
? (IType) NHibernateUtil.DateTimeNoMs
29+
: NHibernateUtil.DateTime;
30+
TypeFactory.RegisterType(typeof(DateTime), _testDefaultDateTimeType, TypeFactory.EmptyAliases);
31+
base.Configure(configuration);
32+
}
33+
34+
protected override void DropSchema()
35+
{
36+
base.DropSchema();
37+
if (_originalDefaultDateTimeType != null)
38+
TypeFactory.RegisterType(typeof(DateTime), _originalDefaultDateTimeType, TypeFactory.EmptyAliases);
39+
}
40+
41+
[Test]
42+
public void DefaultType()
43+
{
44+
Assert.That(TypeFactory.GetDefaultTypeFor(typeof(DateTime)), Is.EqualTo(_testDefaultDateTimeType));
45+
}
46+
47+
[Test]
48+
public void PropertyType()
49+
{
50+
Assert.That(
51+
Sfi.GetClassMetadata(typeof(ChangeDefaultTypeClass))
52+
.GetPropertyType(nameof(ChangeDefaultTypeClass.NormalDateTimeValue)),
53+
Is.EqualTo(_testDefaultDateTimeType));
54+
}
55+
56+
[Test]
57+
public void GuessType()
58+
{
59+
Assert.That(NHibernateUtil.GuessType(typeof(DateTime)), Is.EqualTo(_testDefaultDateTimeType));
60+
}
61+
62+
[Test]
63+
public void ParameterType()
64+
{
65+
var namedParametersField = typeof(AbstractQueryImpl)
66+
.GetField("namedParameters", BindingFlags.Instance | BindingFlags.NonPublic);
67+
Assert.That(namedParametersField, Is.Not.Null, "Missing internal field");
68+
69+
using (var s = OpenSession())
70+
{
71+
// Query where the parameter type cannot be deducted from compared entity property
72+
var q = s.CreateQuery($"from {nameof(ChangeDefaultTypeClass)} where :date1 = :date2 or :date1 = :date3")
73+
.SetParameter("date1", DateTime.Now)
74+
.SetDateTime("date2", DateTime.Now)
75+
.SetDateTimeNoMs("date3", DateTime.Now);
76+
77+
var namedParameters = namedParametersField.GetValue(q) as Dictionary<string, TypedValue>;
78+
Assert.That(namedParameters, Is.Not.Null, "Unable to retrieve parameters internal field");
79+
Assert.That(namedParameters["date1"].Type, Is.EqualTo(_testDefaultDateTimeType));
80+
Assert.That(namedParameters["date2"].Type, Is.EqualTo(NHibernateUtil.DateTime));
81+
Assert.That(namedParameters["date3"].Type, Is.EqualTo(NHibernateUtil.DateTimeNoMs));
82+
}
83+
}
84+
}
85+
}

src/NHibernate/Type/TypeFactory.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Xml;
77
using System.Xml.Linq;
88
using NHibernate.Bytecode;
9+
using NHibernate.Cfg;
910
using NHibernate.Classic;
1011
using NHibernate.SqlTypes;
1112
using NHibernate.UserTypes;
@@ -33,8 +34,9 @@ private enum TypeClassification
3334
PrecisionScale
3435
}
3536

37+
public static readonly string[] EmptyAliases = System.Array.Empty<string>();
38+
3639
private static readonly INHibernateLogger _log = NHibernateLogger.For(typeof(TypeFactory));
37-
private static readonly string[] EmptyAliases= System.Array.Empty<string>();
3840
private static readonly char[] PrecisionScaleSplit = { '(', ')', ',' };
3941
private static readonly char[] LengthSplit = { '(', ')' };
4042

@@ -95,7 +97,15 @@ private enum TypeClassification
9597

9698
private delegate NullableType NullableTypeCreatorDelegate(SqlType sqlType);
9799

98-
private static void RegisterType(System.Type systemType, IType nhibernateType, IEnumerable<string> aliases)
100+
/// <summary>
101+
/// <para>Defines which NHibernate type should be chosen by default for handling a given .Net type.</para>
102+
/// <para>This must be done before any operation on NHibernate, including building its
103+
/// <see cref="Configuration" /> and building session factory. Otherwise the behavior will be undefined.</para>
104+
/// </summary>
105+
/// <param name="systemType">The .Net type.</param>
106+
/// <param name="nhibernateType">The NHibernate type.</param>
107+
/// <param name="aliases">The additional aliases to map to the type. Use <see cref="EmptyAliases"/> if none.</param>
108+
public static void RegisterType(System.Type systemType, IType nhibernateType, IEnumerable<string> aliases)
99109
{
100110
var typeAliases = new List<string>(aliases);
101111
typeAliases.AddRange(GetClrTypeAliases(systemType));

0 commit comments

Comments
 (0)