Skip to content

NH-3919 - Clean up and harmonize datetime types with regards to different dialects #703

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Oct 4, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 83 additions & 20 deletions doc/reference/modules/basic_mapping.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2638,14 +2638,30 @@
<row>
<entry><literal>DateTime</literal></entry>
<entry><literal>System.DateTime</literal></entry>
<entry><literal>DbType.DateTime</literal> - ignores the milliseconds</entry>
<entry>Default when no <literal>type</literal> attribute specified.</entry>
<entry>
<literal>DbType.DateTime</literal> / <literal>DbType.DateTime2</literal><co id="basic_mapping.datetime-co" linkends="basic_mapping.datetime"/>
</entry>
<entry>
Default when no <literal>type</literal> attribute specified. Does no
more ignore milliseconds since NHibernate v5.0.
</entry>
</row>
<row>
<entry><literal>DateTimeNoMs</literal></entry>
<entry><literal>System.DateTime</literal></entry>
<entry>
<literal>DbType.DateTime</literal> / <literal>DbType.DateTime2</literal><coref linkend="basic_mapping.datetime-co" />
</entry>
<entry><literal>type="DateTimeNoMs"</literal> must be specified. Ignores milliseconds.</entry>
</row>
<row>
<entry><literal>DateTime2</literal></entry>
<entry><literal>System.DateTime</literal></entry>
<entry><literal>DbType.DateTime2</literal></entry>
<entry><literal>type="DateTime2"</literal> must be specified.</entry>
<entry>
<literal>type="DateTime2"</literal> must be specified. Obsolete since
NHibernate v5.0, use <literal>DateTime</literal> instead.
</entry>
</row>
<row>
<entry><literal>DateTimeOffset</literal></entry>
Expand All @@ -2656,9 +2672,14 @@
<row>
<entry><literal>DbTimestamp</literal></entry>
<entry><literal>System.DateTime</literal></entry>
<entry><literal>DbType.DateTime</literal> - as specific as database supports.</entry>
<entry><literal>type="DbTimestamp"</literal> must be specified. When used as a <literal>version</literal>
field, uses the database's current time rather than the client's current time.</entry>
<entry>
<literal>DbType.DateTime</literal> / <literal>DbType.DateTime2</literal><coref linkend="basic_mapping.datetime-co" />
</entry>
<entry>
<literal>type="DbTimestamp"</literal> must be specified. When used as a
<literal>version</literal> field, uses the database's current time retrieved
in dedicated queries, rather than the client's current time.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Further hinting at the performance consequences it may have by mentioning it does that in dedicated queries.

</entry>
</row>
<row>
<entry><literal>Decimal</literal></entry>
Expand Down Expand Up @@ -2699,9 +2720,26 @@
<row>
<entry><literal>LocalDateTime</literal></entry>
<entry><literal>System.DateTime</literal></entry>
<entry><literal>DbType.DateTime</literal> - ignores the milliseconds</entry>
<entry><literal>type="LocalDateTime"</literal> must be specified. Ensures the
<literal>DateTimeKind</literal> is set to <literal>DateTimeKind.Local</literal></entry>
<entry>
<literal>DbType.DateTime</literal> / <literal>DbType.DateTime2</literal><coref linkend="basic_mapping.datetime-co" />
</entry>
<entry>
<literal>type="LocalDateTime"</literal> must be specified. Ensures the
<literal>DateTimeKind</literal> is set to <literal>DateTimeKind.Local</literal>.
Throws if set with a date having another kind.
Does no more ignore milliseconds since NHibernate v5.0.
</entry>
</row>
<row>
<entry><literal>LocalDateTimeNoMs</literal></entry>
<entry><literal>System.DateTime</literal></entry>
<entry>
<literal>DbType.DateTime</literal> / <literal>DbType.DateTime2</literal><coref linkend="basic_mapping.datetime-co" />
</entry>
<entry>
<literal>type="LocalDateTimeNoMs"</literal> must be specified. Similar to
<literal>type="LocalDateTime"</literal> but ignores milliseconds.
</entry>
</row>
<row>
<entry><literal>PersistentEnum</literal></entry>
Expand Down Expand Up @@ -2750,15 +2788,13 @@
<row>
<entry><literal>Timestamp</literal></entry>
<entry><literal>System.DateTime</literal></entry>
<entry><literal>DbType.DateTime</literal> - as specific as database supports.</entry>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as specific as database supports

This was a lie. It just does not cut milliseconds as the old DateTime was doing. But by example with Oracle, it will use a timestamp(4) while Oracle supports timestamp(9) (at least since 10g).

<entry><literal>type="Timestamp"</literal> must be specified.</entry>
</row>
<row>
<entry><literal>TimestampUtc</literal></entry>
<entry><literal>System.DateTime</literal></entry>
<entry><literal>DbType.DateTime</literal> - as specific as database supports.</entry>
<entry><literal>type="TimestampUtc"</literal> must be specified. Ensures the
<literal>DateTimeKind</literal> is set to <literal>DateTimeKind.Utc</literal></entry>
<entry>
<literal>DbType.DateTime</literal> / <literal>DbType.DateTime2</literal><coref linkend="basic_mapping.datetime-co" />
</entry>
<entry>
Obsolete, its <literal>Timestamp</literal> alias will be remapped to
<literal>DateTime</literal> in a future version.
</entry>
</row>
<row>
<entry><literal>TrueFalse</literal></entry>
Expand Down Expand Up @@ -2787,8 +2823,25 @@
<row>
<entry><literal>UtcDateTime</literal></entry>
<entry><literal>System.DateTime</literal></entry>
<entry><literal>DbType.DateTime</literal> - ignores the milliseconds</entry>
<entry>Ensures the <literal>DateTimeKind</literal> is set to <literal>DateTimeKind.Utc</literal></entry>
<entry>
<literal>DbType.DateTime</literal> / <literal>DbType.DateTime2</literal><coref linkend="basic_mapping.datetime-co" />
</entry>
<entry>
Ensures the <literal>DateTimeKind</literal> is set to <literal>DateTimeKind.Utc</literal>.
Throws if set with a date having another kind.
Does no more ignore milliseconds since NHibernate v5.0.
</entry>
</row>
<row>
<entry><literal>UtcDateTimeNoMs</literal></entry>
<entry><literal>System.DateTime</literal></entry>
<entry>
<literal>DbType.DateTime</literal> / <literal>DbType.DateTime2</literal><coref linkend="basic_mapping.datetime-co" />
</entry>
<entry>
<literal>type="UtcDateTimeNoMs"</literal> must be specified. Similar to
<literal>type="LocalDateTime"</literal> but ignores milliseconds.
</entry>
</row>
<row>
<entry><literal>YesNo</literal></entry>
Expand All @@ -2800,6 +2853,16 @@
</tgroup>
</table>

<calloutlist>
<callout arearefs="basic_mapping.datetime-co" id="basic_mapping.datetime">
<para>
Since NHibernate v5.0 and if the dialect supports it, <literal>DbType.DateTime2</literal>
is used instead of <literal>DbType.DateTime</literal>. This may be disabled by setting
<literal>sql_types.keep_datetime</literal> to <literal>true</literal>.
</para>
</callout>
</calloutlist>

<table>
<title>System.Object Mapping Types</title>
<tgroup cols="4">
Expand Down
26 changes: 13 additions & 13 deletions src/NHibernate.Test/Async/Legacy/CriteriaTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@ public async Task SimpleSelectTestAsync()
long simple1Key = 15;
Simple simple1 = new Simple();
simple1.Address = "Street 12";
simple1.Date = DateTime.Now;
simple1.Date = RoundForDialect(DateTime.Now);
simple1.Name = "For Criteria Test";
simple1.Count = 16;

long notSimple1Key = 17;
Simple notSimple1 = new Simple();
notSimple1.Address = "Street 123";
notSimple1.Date = DateTime.Now;
notSimple1.Date = RoundForDialect(DateTime.Now);
notSimple1.Name = "Don't be found";
notSimple1.Count = 18;

Expand All @@ -62,19 +62,19 @@ public async Task SimpleSelectTestAsync()
using (ISession s2 = OpenSession())
using (ITransaction t2 = s2.BeginTransaction())
{
IList results2 = await (s2.CreateCriteria(typeof(Simple))
.Add(Expression.Eq("Address", "Street 12"))
.ListAsync());
var results2 = await (s2.CreateCriteria<Simple>()
.Add(Restrictions.Eq("Address", "Street 12"))
.ListAsync<Simple>());

Assert.AreEqual(1, results2.Count);
Assert.That(results2.Count, Is.EqualTo(1), "Unexpected result count");

Simple simple2 = (Simple) results2[0];
var simple2 = results2[0];

Assert.IsNotNull(simple2, "Unable to load object");
Assert.AreEqual(simple1.Count, simple2.Count, "Load failed");
Assert.AreEqual(simple1.Name, simple2.Name, "Load failed");
Assert.AreEqual(simple1.Address, simple2.Address, "Load failed");
Assert.AreEqual(simple1.Date.ToString(), simple2.Date.ToString(), "Load failed");
Assert.That(simple2, Is.Not.Null, "Unable to load object");
Assert.That(simple2.Count, Is.EqualTo(simple1.Count), "Unexpected Count property value");
Assert.That(simple2.Name, Is.EqualTo(simple1.Name), "Unexpected name");
Assert.That(simple2.Address, Is.EqualTo(simple1.Address), "Unexpected address");
Assert.That(simple2.Date, Is.EqualTo(simple1.Date), "Unexpected date");

await (s2.DeleteAsync("from Simple"));

Expand Down Expand Up @@ -181,4 +181,4 @@ public async Task CriteriaLeftOuterJoinAsync()
}

}
}
}
6 changes: 3 additions & 3 deletions src/NHibernate.Test/Async/Legacy/SQLLoaderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public async Task TSAsync()
await (session.SaveAsync(sim, 1L));
IQuery q = session.CreateSQLQuery("select {sim.*} from Simple {sim} where {sim}.date_ = ?")
.AddEntity("sim", typeof(Simple));
q.SetTimestamp(0, sim.Date);
q.SetDateTime(0, sim.Date);
Assert.AreEqual(1, (await (q.ListAsync())).Count, "q.List.Count");
await (session.DeleteAsync(sim));
await (txn.CommitAsync());
Expand All @@ -84,7 +84,7 @@ public async Task TSNamedAsync()
IQuery q =
session.CreateSQLQuery("select {sim.*} from Simple {sim} where {sim}.date_ = :fred")
.AddEntity("sim", typeof(Simple));
q.SetTimestamp("fred", sim.Date);
q.SetDateTime("fred", sim.Date);
Assert.AreEqual(1, (await (q.ListAsync())).Count, "q.List.Count");
await (session.DeleteAsync(sim));
await (txn.CommitAsync());
Expand Down Expand Up @@ -660,4 +660,4 @@ public async Task NamedSQLQueryAsync()
s.Close();
}
}
}
}
10 changes: 5 additions & 5 deletions src/NHibernate.Test/Async/NHSpecificTest/NH1756/Fixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,19 @@ protected override bool AppliesTo(Dialect.Dialect dialect)
protected override bool AppliesTo(ISessionFactoryImplementor factory)
{
// ODBC driver DateTime handling with SQL Server 2008+ Client is broken and forbids using it as a time stamp
// generated on db-side.
// custom generated on db-side.
// Due to NH-3895, we have to force the scale on date-time parameters to 3 (3 significant fractional seconds digits)
// when using ODBC + SQL Server 2008+, otherwise DateTime values having milliseconds will be rejected. But the SQL
// Server DateTime does not have actually a one millisecond resolution (it has 3.333), causing ODBC to convert the
// parameter to DateTime2. A DateTime value ending by 3ms (indeed 3.333) or 7ms (indeed 6.666) is
// parameter to DateTime2 (yes it supports it, only its .Net classes do not).
// A DateTime value ending by 3ms (indeed 3.333) or 7ms (indeed 6.666) is
// to be transmitted as having 3ms or 7ms and will match if transmitted as a DateTime. But when transmitted as
// DateTime2, it will no more be considered equal, causing the test to be flaky and failing two thirds of tries.
// Example failing update captured with profiler:
// exec sp_executesql N'UPDATE book SET name_column = @P1 WHERE id = @P2 AND version_column = @P3',
// N'@P1 nvarchar(18),@P2 int,@P3 datetime2',N'modified test book',1,'2017-08-02 16:37:16.0630000'
// Setting the scale to 2 still causes failure for two thirds of tries, due to 3ms/7ms being truncated in such case
// with ODBC and SQL Server 2008+ Client, which is rejected bu ODBC.
// (Affects DbVersionFixture too.)
// with ODBC and SQL Server 2008+ Client, which is rejected by ODBC.
return !(factory.ConnectionProvider.Driver is OdbcDriver);
}

Expand Down Expand Up @@ -115,4 +115,4 @@ public async Task SaveTransient_Then_Update_BugAsync()
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ protected override HbmMapping GetMappings()
rc.Property(x => x.Name);
rc.Property(x => x.DateOfBirth, pm =>
{
pm.Type(NHibernateUtil.Timestamp);
pm.Type(NHibernateUtil.DateTime);
});
});

Expand Down
31 changes: 14 additions & 17 deletions src/NHibernate.Test/Async/NHSpecificTest/NH3818/Fixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,19 @@

using System;
using System.Linq;
using System.Linq.Dynamic;
using NHibernate.Linq;
using NUnit.Framework;
using NHibernate.Linq;

namespace NHibernate.Test.NHSpecificTest.NH3818
{
using System.Threading.Tasks;
[TestFixture]
public class FixtureAsync : BugTestCase
{
public override string BugNumber
{
get { return "NH3818"; }
}

using System.Threading.Tasks;
[TestFixture]
public class FixtureAsync : BugTestCase
{
[Test]
public async Task SelectConditionalValuesTestAsync()
{
var now = RoundForDialect(DateTime.Now);
using (var spy = new SqlLogSpy())
using (var session = OpenSession())
using (session.BeginTransaction())
Expand All @@ -37,7 +32,7 @@ public async Task SelectConditionalValuesTestAsync()
var cat = new MyLovelyCat
{
GUID = Guid.NewGuid(),
Birthdate = DateTime.Now.AddDays(-days),
Birthdate = now.AddDays(-days),
Color = "Black",
Name = "Kitty",
Price = 0
Expand All @@ -51,31 +46,33 @@ public async Task SelectConditionalValuesTestAsync()
.Select(o => new
{
o.Color,
AliveDays = (int)(DateTime.Now - o.Birthdate).TotalDays,
AliveDays = (now - o.Birthdate).TotalDays,
o.Name,
o.Price,
})
.SingleAsync());

//Console.WriteLine(spy.ToString());
Assert.That(catInfo.AliveDays == days);
// We need a tolerance: we are diffing dates as a timespan instead of counting days boundaries,
// yielding a float. TimeSpan.Days yould always truncate a resulting partial day, so do not use it.
Assert.That(catInfo.AliveDays, Is.EqualTo(days).Within(0.1));

var catInfo2 =
await (session.Query<MyLovelyCat>()
.Select(o => new
{
o.Color,
AliveDays = o.Price > 0 ? (DateTime.Now - o.Birthdate).TotalDays : 0,
AliveDays = o.Price > 0 ? (now - o.Birthdate).TotalDays : 0,
o.Name,
o.Price,
})
.SingleAsync());

//Console.WriteLine(spy.ToString());
Assert.That(catInfo2.AliveDays == 0);
Assert.That(catInfo2.AliveDays, Is.EqualTo(0));

}
}

}
}
}
8 changes: 4 additions & 4 deletions src/NHibernate.Test/Async/NHSpecificTest/NH392/Fixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ public async Task UnsavedMinusOneNoNullReferenceExceptionAsync()
}
}

protected override void OnTearDown()
protected override void OnTearDown()
{
using (ISession s = Sfi.OpenSession())
{
// s.Delete("from UnsavedValueMinusOne") loads then delete entities one by one, checking the version.
// This fails with ODBC & Sql Server 2008+, see NH-1756 test case or DbVersionFixture for more details.
// This fails with ODBC & Sql Server 2008+, see NH-1756 test case for more details.
// Use an in-db query instead.
s.CreateQuery("delete from UnsavedValueMinusOne").ExecuteUpdate();
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ public async Task NonNullableMappedAsDateTimeShouldBeCultureAgnosticAsync()

// Non-reg test case
[Test]
[Obsolete]
public async Task NonNullableMappedAsTimestampShouldBeCultureAgnosticAsync()
{
using (ISession session = OpenSession())
Expand Down Expand Up @@ -227,6 +228,7 @@ public async Task NullableMappedAsDateTimeShouldBeCultureAgnosticAsync()

// Failing test case till NH-3961 is fixed
[Test]
[Obsolete]
public async Task NullableMappedAsTimestampShouldBeCultureAgnosticAsync()
{
using (ISession session = OpenSession())
Expand Down Expand Up @@ -290,4 +292,4 @@ public async Task NullableShouldBeCultureAgnosticAsync()
}
}
}
}
}
Loading