在 DateTime.Now 上应用 CRUD 操作的正确方法是什么?
What is the proper way to apply CRUD operation on DateTime.Now?
我有一个 SQL 服务器 table 如下所示:
CREATE TABLE [dbo].[TimePeriod](
[ID] [int] NOT NULL,
[Time] [datetime2](7) NOT NULL,
[Description] [varchar](max) NULL,
CONSTRAINT [PK_TimePeriod] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
我有如下单元测试:
[TestMethod]
public void Save()
{
OrmEngine.Initalize();
DateTime dateTime = DateTime.Now;
TimePeriod item = new TimePeriod();
item.Time = dateTime;
item.Description = item.Time.Millisecond.ToString() + "=Description";
TimePeriodBllManually bll = new TimePeriodBllManually();
int newId = bll.Save(item);
TimePeriod returns = bll.Get(newId);
Assert.IsNotNull(returns);
Assert.AreEqual(item.Time, returns.Time);
Assert.AreEqual(item.Description, returns.Description);
}
SQL 服务器数据:
似乎在Insert 操作过程中更改了时间。无论 SQL 数据类型是 datetime
还是 datetime2
.
,结果都是相同的
从 C# 的角度来看,我认为,DateTime
是一种 immutable 类型。但是,在这里我看到 dateTime
即使在赋值之后也在改变它的值。
我该如何解决这个问题?
滴答数和毫秒数将不同 - 因此断言很可能总是失败。
如果可以忽略毫秒数,那么我们查看总秒数以进行断言:
int x = (int)DateTime.Now.TimeOfDay.TotalSeconds;
int y = (int)DateTime.Now.TimeOfDay.TotalSeconds;
Assert.AreEqual(x, y);
编辑
根据 Icepickle 评论和 user366312 额外信息 - 我做了更多挖掘,我可以确认该问题与 DateTime 无关。
这里的问题是将日期时间类型存储到数据库时会丢失精度。
根据 user366312 的示例,如果我们使用以下日期时间值 DateTime(2020, 7, 25, 15, 10, 20, 30);
,则在存储到 db
时不会丢失精度
但是,如果我们使用来自 DateTime.Now
的值
item.Time
{25-Jul-20 12:31:11 AM}
Date: {25-Jul-20 12:00:00 AM}
Day: 25
DayOfWeek: Saturday
DayOfYear: 207
Hour: 0
Kind: Local
Millisecond: 368
Minute: 31
Month: 7
Second: 11
Ticks: 637312338713680636
TimeOfDay: {00:31:11.3680636}
Year: 2020
我们会损失精度 - 导致 returns
项提供的内容:
returns.Time
{25-Jul-20 12:31:11 AM}
Date: {25-Jul-20 12:00:00 AM}
Day: 25
DayOfWeek: Saturday
DayOfYear: 207
Hour: 0
Kind: Unspecified
Millisecond: 367
Minute: 31
Month: 7
Second: 11
Ticks: 637312338713670000
TimeOfDay: {00:31:11.3670000}
Year: 2020
目前的问题 would/could 可以通过更改数据库数据类型的设计来解决。
user366312 提供的解决方案将起作用 - 但正如 Icepickle 所指出的,我们是 reducing/losing 精度。
更稳健的实施方式是将数据库中的时间列更改为 datetime2(7)
EDIT 2 CRUD操作实现:
我创建了一个小应用程序,以便在数据库中添加 1000 个条目。这是实现。
我的环境:
Microsoft SQL Server Management Studio 11.0.5058.
Microsoft Visual Studio Professional 2019
Version 16.6.3
VisualStudio.16.Release/16.6.3+30225.117
Microsoft .NET Framework
Version 4.8.03761
Installed Version: Professional
数据库table实现:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[TimePeriod](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Description] [varchar](50) NULL,
[Time] [datetime2](7) NULL,
CONSTRAINT [PK_TimePeriod] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
TimePeriodEntity class
#region Usings
using System;
#endregion
public class TimePeriodEntity
{
#region Properties
public int ID { get; set; }
public DateTime Time { get; set; }
public string Description { get; set; }
#endregion
}
CRUDO操作class
#region Usings
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
#endregion
public static class CRUDOperation
{
#region Fields
private const string ConnectionString = @"Data Source=.;Database=TestDB;Integrated Security=SSPI";
static DataTable DataTable;
#endregion
static CRUDOperation()
{
DataTable = new DataTable();
}
public static List<TimePeriodEntity> Read()
{
List<TimePeriodEntity> list = new List<TimePeriodEntity>();
string query = "SELECT * FROM TimePeriod";
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
SqlCommand command = new SqlCommand(query, connection);
connection.Open();
using (SqlDataAdapter adaptor = new SqlDataAdapter(command))
{
adaptor.Fill(DataTable);
foreach (DataRow row in DataTable.Rows)
{
TimePeriodEntity tp = new TimePeriodEntity();
tp.ID = (int)row[0];
tp.Description = row[1].ToString();
tp.Time = Convert.ToDateTime(row[2]);
list.Add(tp);
}
}
}
return list;
}
public static void Create(DateTime time)
{
string query = "INSERT INTO TimePeriod(Description,Time) VALUES(@param2,@param3)";
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
SqlCommand command = new SqlCommand(query, connection);
connection.Open();
command.Parameters.Add("@param2", SqlDbType.VarChar).Value = $"{time.Millisecond} = Description";
command.Parameters.Add("@param3", SqlDbType.DateTime2).Value = time;
command.CommandType = CommandType.Text;
command.ExecuteNonQuery();
}
}
}
添加到数据库中的行格式如下:
ID Description Time
606 142 = Description 2020-07-29 08:37:38.1420528
这是我的结果的一个子集:
SQL服务器中的datetime
数据类型精确到3.33毫秒。如果您检查类型上的 documentation,它会显示:
Accuracy - Rounded to increments of .000, .003, or .007 seconds
根据设计,您示例中的 .936 将存储为 .937。
切换到 datetime2
应该可以克服这种行为,因为它精确到 100 纳秒,但是您已经声明使用 datetime2
.
得到相同的结果
你能分享数据层的代码吗?可能有一部分仍在使用 datetime
作为数据类型并导致即使字段设置为 datetime2
.
也会发生舍入
我有一个 SQL 服务器 table 如下所示:
CREATE TABLE [dbo].[TimePeriod](
[ID] [int] NOT NULL,
[Time] [datetime2](7) NOT NULL,
[Description] [varchar](max) NULL,
CONSTRAINT [PK_TimePeriod] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
我有如下单元测试:
[TestMethod]
public void Save()
{
OrmEngine.Initalize();
DateTime dateTime = DateTime.Now;
TimePeriod item = new TimePeriod();
item.Time = dateTime;
item.Description = item.Time.Millisecond.ToString() + "=Description";
TimePeriodBllManually bll = new TimePeriodBllManually();
int newId = bll.Save(item);
TimePeriod returns = bll.Get(newId);
Assert.IsNotNull(returns);
Assert.AreEqual(item.Time, returns.Time);
Assert.AreEqual(item.Description, returns.Description);
}
SQL 服务器数据:
似乎在Insert 操作过程中更改了时间。无论 SQL 数据类型是 datetime
还是 datetime2
.
从 C# 的角度来看,我认为,DateTime
是一种 immutable 类型。但是,在这里我看到 dateTime
即使在赋值之后也在改变它的值。
我该如何解决这个问题?
滴答数和毫秒数将不同 - 因此断言很可能总是失败。
如果可以忽略毫秒数,那么我们查看总秒数以进行断言:
int x = (int)DateTime.Now.TimeOfDay.TotalSeconds;
int y = (int)DateTime.Now.TimeOfDay.TotalSeconds;
Assert.AreEqual(x, y);
编辑
根据 Icepickle 评论和 user366312 额外信息 - 我做了更多挖掘,我可以确认该问题与 DateTime 无关。
这里的问题是将日期时间类型存储到数据库时会丢失精度。
根据 user366312 的示例,如果我们使用以下日期时间值 DateTime(2020, 7, 25, 15, 10, 20, 30);
,则在存储到 db
但是,如果我们使用来自 DateTime.Now
的值item.Time
{25-Jul-20 12:31:11 AM}
Date: {25-Jul-20 12:00:00 AM}
Day: 25
DayOfWeek: Saturday
DayOfYear: 207
Hour: 0
Kind: Local
Millisecond: 368
Minute: 31
Month: 7
Second: 11
Ticks: 637312338713680636
TimeOfDay: {00:31:11.3680636}
Year: 2020
我们会损失精度 - 导致 returns
项提供的内容:
returns.Time
{25-Jul-20 12:31:11 AM}
Date: {25-Jul-20 12:00:00 AM}
Day: 25
DayOfWeek: Saturday
DayOfYear: 207
Hour: 0
Kind: Unspecified
Millisecond: 367
Minute: 31
Month: 7
Second: 11
Ticks: 637312338713670000
TimeOfDay: {00:31:11.3670000}
Year: 2020
目前的问题 would/could 可以通过更改数据库数据类型的设计来解决。
user366312 提供的解决方案将起作用 - 但正如 Icepickle 所指出的,我们是 reducing/losing 精度。
更稳健的实施方式是将数据库中的时间列更改为 datetime2(7)
EDIT 2 CRUD操作实现:
我创建了一个小应用程序,以便在数据库中添加 1000 个条目。这是实现。
我的环境:
Microsoft SQL Server Management Studio 11.0.5058.
Microsoft Visual Studio Professional 2019
Version 16.6.3
VisualStudio.16.Release/16.6.3+30225.117
Microsoft .NET Framework
Version 4.8.03761
Installed Version: Professional
数据库table实现:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[TimePeriod](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Description] [varchar](50) NULL,
[Time] [datetime2](7) NULL,
CONSTRAINT [PK_TimePeriod] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
TimePeriodEntity class
#region Usings
using System;
#endregion
public class TimePeriodEntity
{
#region Properties
public int ID { get; set; }
public DateTime Time { get; set; }
public string Description { get; set; }
#endregion
}
CRUDO操作class
#region Usings
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
#endregion
public static class CRUDOperation
{
#region Fields
private const string ConnectionString = @"Data Source=.;Database=TestDB;Integrated Security=SSPI";
static DataTable DataTable;
#endregion
static CRUDOperation()
{
DataTable = new DataTable();
}
public static List<TimePeriodEntity> Read()
{
List<TimePeriodEntity> list = new List<TimePeriodEntity>();
string query = "SELECT * FROM TimePeriod";
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
SqlCommand command = new SqlCommand(query, connection);
connection.Open();
using (SqlDataAdapter adaptor = new SqlDataAdapter(command))
{
adaptor.Fill(DataTable);
foreach (DataRow row in DataTable.Rows)
{
TimePeriodEntity tp = new TimePeriodEntity();
tp.ID = (int)row[0];
tp.Description = row[1].ToString();
tp.Time = Convert.ToDateTime(row[2]);
list.Add(tp);
}
}
}
return list;
}
public static void Create(DateTime time)
{
string query = "INSERT INTO TimePeriod(Description,Time) VALUES(@param2,@param3)";
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
SqlCommand command = new SqlCommand(query, connection);
connection.Open();
command.Parameters.Add("@param2", SqlDbType.VarChar).Value = $"{time.Millisecond} = Description";
command.Parameters.Add("@param3", SqlDbType.DateTime2).Value = time;
command.CommandType = CommandType.Text;
command.ExecuteNonQuery();
}
}
}
添加到数据库中的行格式如下:
ID Description Time
606 142 = Description 2020-07-29 08:37:38.1420528
这是我的结果的一个子集:
SQL服务器中的datetime
数据类型精确到3.33毫秒。如果您检查类型上的 documentation,它会显示:
Accuracy - Rounded to increments of .000, .003, or .007 seconds
根据设计,您示例中的 .936 将存储为 .937。
切换到 datetime2
应该可以克服这种行为,因为它精确到 100 纳秒,但是您已经声明使用 datetime2
.
你能分享数据层的代码吗?可能有一部分仍在使用 datetime
作为数据类型并导致即使字段设置为 datetime2
.