C# 现在,在DateTime上应用CRUD操作的正确方法是什么?

C# 现在,在DateTime上应用CRUD操作的正确方法是什么?,c#,unit-testing,visual-studio-2015,C#,Unit Testing,Visual Studio 2015,我有一个SQL Server表,如下所示: 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, STATI

我有一个SQL Server表,如下所示:

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);
    }
我有一个如下所示的单元测试:

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 Server数据:

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

在插入操作期间,时间似乎正在改变。无论SQL数据类型是
datetime
还是
datetime2
,结果都是相同的

从C#的角度来看,我认为,
DateTime
是一种不可变的类型。但是,在这里我看到,即使在赋值之后,
dateTime
也在改变它的值


如何解决这个问题?

滴答声的数量和毫秒的数量将会不同,因此断言很可能总是失败

如果可以忽略毫秒数,那么我们将查看总秒数以执行断言:

        int x = (int)DateTime.Now.TimeOfDay.TotalSeconds;

        int y = (int)DateTime.Now.TimeOfDay.TotalSeconds;


        Assert.AreEqual(x, y);
编辑

在Icepickle评论和用户366312额外信息之后-我已经做了更多的挖掘,我可以确认这个问题与DateTime无关

这里的问题是在将日期时间类型存储到数据库时会丢失精度

根据用户366312的示例,如果我们使用以下日期时间值
datetime(2020,7,25,15,10,20,30)存储到数据库时不会丢失精度

但是,如果我们使用来自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.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
目前,这个问题可以通过改变数据库数据类型的设计来解决

user366312提供的解决方案将起作用,但正如Icepickle所指出的,我们正在降低/失去精度

更健壮的实现是将数据库中的时间列更改为
datetime2(7)

编辑2个积垢操作实现:

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
我创建了一个小应用程序,以便在数据库中添加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
数据库表实现:

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类

#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
}
#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();
        }
    }

}
积垢操作等级

#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
}
#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();
        }
    }

}
以下是我的结果的子集:


滴答声的数量和毫秒的数量将会不同,因此断言很可能总是失败

如果可以忽略毫秒数,那么我们将查看总秒数以执行断言:

        int x = (int)DateTime.Now.TimeOfDay.TotalSeconds;

        int y = (int)DateTime.Now.TimeOfDay.TotalSeconds;


        Assert.AreEqual(x, y);
编辑

在Icepickle评论和用户366312额外信息之后-我已经做了更多的挖掘,我可以确认这个问题与DateTime无关

这里的问题是在将日期时间类型存储到数据库时会丢失精度

根据用户366312的示例,如果我们使用以下日期时间值
datetime(2020,7,25,15,10,20,30)存储到数据库时不会丢失精度

但是,如果我们使用来自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.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
目前,这个问题可以通过改变数据库数据类型的设计来解决

user366312提供的解决方案将起作用,但正如Icepickle所指出的,我们正在降低/失去精度

更健壮的实现是将数据库中的时间列更改为
datetime2(7)

编辑2个积垢操作实现:

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
我创建了一个小应用程序,以便在数据库中添加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
数据库表实现:

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类

#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
}
#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();
        }
    }

}
积垢操作等级

#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
}
#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();
        }
    }

}
以下是我的结果的子集:


SQL Server中的
datetime
数据类型精确到3.33毫秒。如果在类型上选中,则它会声明:

精度-四舍五入到.000、.003或.007秒的增量

根据设计,示例中的.936将存储为.937。 切换到
datetime2
应该可以克服这种行为,因为它精确到100纳秒,但是您已经声明使用
datetime2
可以得到相同的结果


您能从数据层共享代码吗?可能有一部分仍然使用
datetime
作为数据类型,并导致舍入发生,即使字段设置为
datetime2

SQL Server中的
datetime
数据类型精确到3.33毫秒。如果在类型上选中,则它会声明:

精度-四舍五入到.000、.003或.007秒的增量

根据设计,示例中的.936将存储为.937。 切换到
datetime2
应该可以克服这种行为,因为它精确到100纳秒,但是您已经声明使用
datetime2
可以得到相同的结果


您能从数据层共享代码吗?可能有一部分仍然使用
datetime
作为数据类型,并导致在字段设置为
datetime2

的情况下出现舍入。您是否调试了测试,这些值是否具有完全相同的值?因为
datetime。现在
不是纯粹的(其返回值取决于参数以外的内容,而参数作为属性为none),很难测试任何使用它的东西。你需要一个可模拟的时间源,比如
NodaTime
IClock
接口。你可以注入一个实现,给你一个确定的时间。@madreflection:
DateTime。现在
返回一个
DateTime
对象。这样检索到的对象是一个稳定的“值”毫秒是不同的(items=368,returns=367)。我看到很多人编写的单元测试都没有提供证明值;所以,是的,我想