Skip to content
This repository was archived by the owner on Dec 24, 2022. It is now read-only.

Commit 2c8a597

Browse files
committed
datetime2 support for MS SQL Server. Modified table creation, and Insert / Update behavior.
Usage: SqlServerOrmLiteDialectProvider.UseDatetime2(true); For example set it in global.asax, factory or IOC): (SqlServerOrmLiteDialectProvider.Instance as SqlServerOrmLiteDialectProvider).UseDatetime2(true); Datetime2Tests.cs contains tests that verify that datetime2's range and precision work as expected. Updated InertParam<T> and UpdateParam<T> so those use datetime2 too.
1 parent 4df783a commit 2c8a597

File tree

5 files changed

+156
-28
lines changed

5 files changed

+156
-28
lines changed

src/ServiceStack.OrmLite.SqlServer/SqlServerOrmLiteDialectProvider.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,31 @@ public override string GetQuotedValue(object value, Type fieldType)
143143
return base.GetQuotedValue(value, fieldType);
144144

145145

146+
}
147+
148+
protected bool _useDateTime2;
149+
public void UseDatetime2(bool shouldUseDatetime2)
150+
{
151+
_useDateTime2 = shouldUseDatetime2;
152+
DateTimeColumnDefinition = shouldUseDatetime2 ? "datetime2" : "datetime";
153+
InitColumnTypeMap();
154+
}
155+
156+
protected override void AddParameterForFieldToCommand(IDbCommand command, FieldDefinition fieldDef, object objWithProperties)
157+
{
158+
//have to override, because DbTypeMap.Set<T> expects DbType, and SqlDbType is not a DbType...
159+
if(_useDateTime2 && (fieldDef.FieldType == typeof(DateTime) || fieldDef.FieldType == typeof(DateTime?))) {
160+
var sqlCmd = (SqlCommand)command;//should be SqlCommand...
161+
var p = sqlCmd.CreateParameter();
162+
p.ParameterName = string.Format("{0}{1}", ParamString, fieldDef.FieldName);
163+
164+
p.SqlDbType = SqlDbType.DateTime2;
165+
p.Value = GetValueOrDbNull(fieldDef, objWithProperties);
166+
167+
command.Parameters.Add(p);
168+
} else {
169+
base.AddParameterForFieldToCommand(command, fieldDef, objWithProperties);
170+
}
146171
}
147172

148173
public override long GetLastInsertId(IDbCommand dbCmd)
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Data.SqlClient;
4+
using System.Linq;
5+
using System.Text;
6+
using NUnit.Framework;
7+
using ServiceStack.DataAnnotations;
8+
using ServiceStack.OrmLite.SqlServer;
9+
10+
namespace ServiceStack.OrmLite.SqlServerTests
11+
{
12+
public class Datetime2Tests : SqlServerTests.OrmLiteTestBase
13+
{
14+
[Test]
15+
public void datetime_tests__can_use_datetime2()
16+
{
17+
var dbFactory = new OrmLiteConnectionFactory(base.ConnectionString, SqlServerOrmLiteDialectProvider.Instance);
18+
19+
//change to datetime2 - check for higher range and precision
20+
(SqlServerOrmLiteDialectProvider.Instance as SqlServerOrmLiteDialectProvider).UseDatetime2(true);
21+
22+
using(var conn = dbFactory.OpenDbConnection()) {
23+
var test_object_ValidForDatetime2 = Table_for_datetime2_tests.get_test_object_ValidForDatetime2();
24+
var test_object_ValidForNormalDatetime = Table_for_datetime2_tests.get_test_object_ValidForNormalDatetime();
25+
26+
conn.CreateTable<Table_for_datetime2_tests>(true);
27+
28+
//normal insert
29+
conn.Insert(test_object_ValidForDatetime2);
30+
var insertedId = (int)conn.GetLastInsertId();
31+
32+
//read back, and verify precision
33+
var fromDb = conn.GetById<Table_for_datetime2_tests>(insertedId);
34+
Assert.AreEqual(test_object_ValidForDatetime2.ToVerifyPrecision, fromDb.ToVerifyPrecision);
35+
36+
//update
37+
fromDb.ToVerifyPrecision = test_object_ValidForDatetime2.ToVerifyPrecision.Value.AddYears(1);
38+
conn.UpdateParam(fromDb);
39+
var fromDb2 = conn.GetById<Table_for_datetime2_tests>(insertedId);
40+
Assert.AreEqual(test_object_ValidForDatetime2.ToVerifyPrecision.Value.AddYears(1), fromDb2.ToVerifyPrecision);
41+
42+
43+
//check InsertParam
44+
conn.InsertParam(test_object_ValidForDatetime2);
45+
}
46+
}
47+
[Test]
48+
public void datetime_tests__check_default_behaviour()
49+
{
50+
var dbFactory = new OrmLiteConnectionFactory(base.ConnectionString, SqlServerOrmLiteDialectProvider.Instance);
51+
52+
//default behaviour: normal datetime can't hold DateTime values of year 1.
53+
(SqlServerOrmLiteDialectProvider.Instance as SqlServerOrmLiteDialectProvider).UseDatetime2(false);
54+
55+
using(var conn = dbFactory.OpenDbConnection()) {
56+
var test_object_ValidForDatetime2 = Table_for_datetime2_tests.get_test_object_ValidForDatetime2();
57+
var test_object_ValidForNormalDatetime = Table_for_datetime2_tests.get_test_object_ValidForNormalDatetime();
58+
59+
conn.CreateTable<Table_for_datetime2_tests>(true);
60+
61+
//normal insert
62+
conn.Insert(test_object_ValidForNormalDatetime);
63+
var insertedId = conn.GetLastInsertId();
64+
65+
//insert works, but can't regular datetime's precision is not great enough.
66+
var fromDb = conn.GetById<Table_for_datetime2_tests>(insertedId);
67+
Assert.AreNotEqual(test_object_ValidForNormalDatetime.ToVerifyPrecision, fromDb.ToVerifyPrecision);
68+
69+
var thrown = Assert.Throws<SqlException>(() => {
70+
conn.Insert(test_object_ValidForDatetime2);
71+
});
72+
Assert.That(thrown.Message.Contains("The conversion of a varchar data type to a datetime data type resulted in an out-of-range value."));
73+
74+
75+
//check InsertParam
76+
conn.InsertParam(test_object_ValidForNormalDatetime);
77+
//InsertParam fails differently:
78+
var insertParamException = Assert.Throws<System.Data.SqlTypes.SqlTypeException>(() => {
79+
conn.InsertParam(test_object_ValidForDatetime2);
80+
});
81+
Assert.That(insertParamException.Message.Contains("SqlDateTime overflow"));
82+
}
83+
}
84+
85+
private class Table_for_datetime2_tests
86+
{
87+
[AutoIncrement]
88+
public int Id { get; set; }
89+
public DateTime SomeDateTime { get; set; }
90+
public DateTime? ToVerifyPrecision { get; set; }
91+
public DateTime? NullableDateTimeLeaveItNull { get; set; }
92+
93+
/// <summary>
94+
/// to check datetime(2)'s precision. A regular 'datetime' is not precise enough
95+
/// </summary>
96+
public static readonly DateTime regular_datetime_field_cant_hold_this_exact_moment = new DateTime(2013, 3, 17, 21, 29, 1, 678);
97+
98+
public static Table_for_datetime2_tests get_test_object_ValidForDatetime2() { return new Table_for_datetime2_tests { SomeDateTime = new DateTime(1, 1, 1), ToVerifyPrecision = regular_datetime_field_cant_hold_this_exact_moment }; }
99+
100+
public static Table_for_datetime2_tests get_test_object_ValidForNormalDatetime() { return new Table_for_datetime2_tests { SomeDateTime = new DateTime(2001, 1, 1), ToVerifyPrecision = regular_datetime_field_cant_hold_this_exact_moment }; }
101+
102+
}
103+
}
104+
}

src/ServiceStack.OrmLite.SqlServerTests/ServiceStack.OrmLite.SqlServerTests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
<Reference Include="System.Xml" />
5454
</ItemGroup>
5555
<ItemGroup>
56+
<Compile Include="Datetime2Tests.cs" />
5657
<Compile Include="InsertParam_GetLastInsertId.cs" />
5758
<Compile Include="NestedTransactions.cs" />
5859
<Compile Include="EnumTests.cs" />

src/ServiceStack.OrmLite/OrmLiteDialectProviderBase.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ public void ReParameterizeInsertStatement(object objWithProperties, IList<string
580580
}
581581
}
582582

583-
protected void AddParameterForFieldToCommand(IDbCommand command, FieldDefinition fieldDef, object objWithProperties)
583+
protected virtual void AddParameterForFieldToCommand(IDbCommand command, FieldDefinition fieldDef, object objWithProperties)
584584
{
585585
var p = command.CreateParameter();
586586
p.ParameterName = string.Format("{0}{1}", ParamString, fieldDef.FieldName);
@@ -600,7 +600,7 @@ protected void AddParameterForFieldToCommand(IDbCommand command, FieldDefinition
600600
command.Parameters.Add(p);
601601
}
602602

603-
private object GetValueOrDbNull(FieldDefinition fieldDef, object objWithProperties)
603+
protected object GetValueOrDbNull(FieldDefinition fieldDef, object objWithProperties)
604604
{
605605
return fieldDef.GetValue(objWithProperties) ?? DBNull.Value;
606606
}

src/ServiceStack.OrmLite/OrmLiteWriteConnectionExtensions.cs

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -110,22 +110,13 @@ public static void UpdateAll<T>(this IDbConnection dbConn, IEnumerable<T> objs)
110110
/// <summary>
111111
/// Performs an Update<T>() except arguments are passed as parameters to the generated SQL
112112
/// </summary>
113-
public static void UpdateParam<T>(this IDbConnection dbConn, T obj)
114-
where T : new()
113+
public static void UpdateParam<T>(this IDbConnection dbConn, T obj) where T : new()
115114
{
116115
dbConn.Exec(dbCmd =>
117-
{
118-
var updateStmt = dbConn.CreateUpdateStatement(obj);
119-
120-
dbCmd.CommandText = updateStmt.CommandText;
121-
foreach (IDbDataParameter genParam in updateStmt.Parameters)
122-
{
123-
var newParam = dbCmd.CreateParameter();
124-
newParam.ParameterName = genParam.ParameterName;
125-
newParam.Value = genParam.Value;
126-
dbCmd.Parameters.Add(newParam);
116+
{
117+
using(var updateStmt = dbConn.CreateUpdateStatement(obj)) {
118+
copyCommandtextAndParametersForParameterizedStatements(dbCmd, updateStmt);
127119
}
128-
129120
dbCmd.ExecuteNonQuery();
130121
});
131122
}
@@ -205,24 +196,31 @@ public static void InsertAll<T>(this IDbConnection dbConn, IEnumerable<T> objs)
205196
/// <summary>
206197
/// Performs an Insert() except arguments are passed as parameters to the generated SQL
207198
/// </summary>
208-
public static void InsertParam<T>(this IDbConnection dbConn, T obj)
209-
where T : new()
199+
public static void InsertParam<T>(this IDbConnection dbConn, T obj) where T : new()
210200
{
211201
dbConn.Exec(dbCmd =>
212-
{
213-
var insertStmt = dbConn.CreateInsertStatement(obj);
214-
215-
dbCmd.CommandText = insertStmt.CommandText;
216-
foreach (IDbDataParameter genParam in insertStmt.Parameters)
217-
{
218-
var newParam = dbCmd.CreateParameter();
219-
newParam.ParameterName = genParam.ParameterName;
220-
newParam.Value = genParam.Value;
221-
dbCmd.Parameters.Add(newParam);
202+
{
203+
using(var insertStmt = dbConn.CreateInsertStatement(obj)) {
204+
copyCommandtextAndParametersForParameterizedStatements(dbCmd, insertStmt);
222205
}
223-
224206
dbCmd.ExecuteNonQuery();
225207
});
208+
}
209+
210+
private static void copyCommandtextAndParametersForParameterizedStatements(IDbCommand dbCmd, IDbCommand tempParameterizedStatement)
211+
{
212+
dbCmd.CommandText = tempParameterizedStatement.CommandText;
213+
214+
//instead of creating new generic DbParameters, copy them from the "dummy" IDbCommand, so it can keep provider specific information. for example: SqlServer "datetime2" dbtype
215+
//first must create a temporary list, because DbParam can't belong to two DbCommands...
216+
List<IDbDataParameter> tmpParams = new List<IDbDataParameter>(tempParameterizedStatement.Parameters.Count);
217+
218+
foreach(IDbDataParameter genParam in tempParameterizedStatement.Parameters) {
219+
tmpParams.Add(genParam);
220+
}
221+
tempParameterizedStatement.Parameters.Clear();
222+
223+
tmpParams.ForEach(x => dbCmd.Parameters.Add(x));
226224
}
227225

228226
public static void Save<T>(this IDbConnection dbConn, params T[] objs)

0 commit comments

Comments
 (0)