SQL Server存储过程DateTime参数不匹配致C#程序崩溃问题
问题场景
我创建了一个用于插入学生数据的SQL Server存储过程:
CREATE PROCEDURE ThemHocSinh @TenHS NVARCHAR(255), @NgaySinh DATETIME, @TenChaMe NVARCHAR(255), @SDTChaMe VARCHAR(16), @DiaChi NVARCHAR(255), @LopHC VARCHAR(6) AS INSERT INTO dbo.HocSinh (MaHS, TenHS, NgaySinh, NgayNhapHoc, TenChaMe, SDTChaMe, DiaChi, LopHC) VALUES (DEFAULT, @TenHS, @NgaySinh, DEFAULT, @TenChaMe, @SDTChaMe, @DiaChi, @LopHC)
随后用C#结合Dapper编写了调用方法:
public void ThemHocSinh(string TenHS, DateTime NgaySinh, string TenChaMe, string SDT, string DiaChi, string LopHC) { using (MamNonBK context = new MamNonBK()) { using (IDbConnection db = new SqlConnection(context.Database.Connection.ConnectionString)) { var p = new DynamicParameters(); p.Add("@TenHS", TenHS); p.Add("@NgaySinh", NgaySinh); p.Add("@TenChaMe", TenChaMe); p.Add("@SDTChaMe", SDT); p.Add("@DiaChi", DiaChi); p.Add("@LopHC", LopHC); db.Execute("ThemHocSinh", p, commandType: CommandType.StoredProcedure); } } }
但程序运行时崩溃,抛出错误:
System.Data.SqlClient.SqlException: The conversion of a varchar data type to a datetime data type resulted in an out-of-range value. The statement has been terminated.
我的系统日期格式为dd/MM/yyyy,请问该如何解决这个问题?
解决方案
这个错误看起来有点矛盾——毕竟你用的是强类型DateTime参数,按道理Dapper会通过参数化查询直接传递二进制值,不会转成varchar字符串。不过结合错误信息和你的场景,我整理了几个排查和解决的方向:
1. 检查DateTime值是否在SQL Server DATETIME的有效范围内
SQL Server的DATETIME类型有严格的范围限制:1753年1月1日 00:00:00 到 9999年12月31日 23:59:59.997。如果你的代码中传递的是DateTime.MinValue(对应0001年1月1日),或者其他超出这个范围的日期,SQL Server就会抛出这个错误。
解决方法:在调用ThemHocSinh方法前先校验日期值:
if (NgaySinh < new DateTime(1753, 1, 1) || NgaySinh > new DateTime(9999, 12, 31)) { throw new ArgumentOutOfRangeException(nameof(NgaySinh), "日期超出SQL Server DATETIME类型的有效范围"); }
2. 显式指定参数的DbType,避免自动推断偏差
虽然Dapper通常能正确推断参数类型,但偶尔会因为环境或文化设置出现偏差。显式指定DbType.DateTime可以强制参数以正确的类型传递:
修改参数添加代码:
p.Add("@NgaySinh", NgaySinh, DbType.DateTime);
3. 确认数据库表的列类型是否正确
检查dbo.HocSinh表的NgaySinh列是否真的是DATETIME类型。如果该列被错误设置为VARCHAR或其他字符串类型,插入时就会发生隐式的varchar转datetime转换,很容易因为格式不匹配触发错误。
你可以用以下SQL语句确认列类型:
SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'HocSinh' AND COLUMN_NAME = 'NgaySinh';
如果结果不是datetime,需要修改表结构:
ALTER TABLE dbo.HocSinh ALTER COLUMN NgaySinh DATETIME;
4. 检查SQL Server的日期格式设置(可选)
如果以上方法都无效,可以检查SQL Server的默认日期格式设置。执行以下SQL查看当前服务器的日期格式:
SELECT DATEFORMAT, @@LANGUAGE;
如果服务器默认的日期格式是mdy(即MM/dd/yyyy),而你传递的日期中日期部分大于12(比如13/01/2024),就会被错误解析为13月,导致超出范围。不过这个问题通常不会出现在参数化查询中,但如果你的存储过程中存在其他隐式转换逻辑,就可能触发。
如果确实是这个原因,你可以在存储过程中显式指定日期格式(不过更推荐保持参数化的方式):
-- 在存储过程中显式转换(仅当必要时) INSERT INTO dbo.HocSinh (...) VALUES (... CONVERT(DATETIME, @NgaySinh, 103), ...)
其中103对应dd/MM/yyyy格式。
5. 确认DateTime的Kind属性
如果你的NgaySinh是本地时间,但SQL Server期望UTC时间,可能会因为时区转换导致日期超出范围。可以在传递前将日期转换为UTC时间:
var utcNgaySinh = NgaySinh.ToUniversalTime(); p.Add("@NgaySinh", utcNgaySinh, DbType.DateTime);
或者确保SQL Server的时区设置和你的应用一致。
内容的提问来源于stack exchange,提问作者DuckFterminal

