DateTime 是 C# 與 SQL Server 交互中最常見也最容易出錯的類型。本文系統梳理 SQL 寫法、C# 格式化、參數化查詢最佳實踐,並揭示字符串拼接 SQL 的致命陷阱。
-- 僅日期 (隱含 00:00:00)
INSERT INTO Customer (Birthday) VALUES ('1985-09-16'); -- 推薦 ISO 8601 格式 yyyy-MM-dd
-- 日期時間 (精確到秒)
INSERT INTO Customer (CreateTime) VALUES ('1985-09-16 12:16:31');
-- 日期時間 (精確到毫秒)
INSERT INTO Customer (LogTime) VALUES ('2023-10-01 14:30:45.123');
⚠️ 必須加單引號。無引號
1985-09-16會被解析為數學運算1985 - 9 - 16 = 1960(整數)。帶時間部分時無引號直接語法錯誤。
-- 當前系統時間 (含毫秒,精度 ~3.33ms)
GETDATE() -- 返回 datetime, 如 2023-10-01 14:30:45.123
-- 當前 UTC 時間
GETUTCDATE() -- 返回 datetime
-- 高精度 (100ns),含時區偏移 (SQL 2008+)
SYSDATETIME() -- 返回 datetime2(7)
SYSUTCDATETIME() -- 返回 datetime2(7)
SYSDATETIMEOFFSET()-- 返回 datetimeoffset(7)
-- 更新示例
UPDATE CustomerInfo
SET LastLogin = GETDATE()
WHERE CustomerId = 13;
DateTime now = DateTime.Now;
now.Year; // 2023
now.Month; // 10
now.Day; // 1
now.Hour; // 14
now.Minute; // 30
now.Second; // 45
now.Millisecond; // 123
now.DayOfWeek; // Monday
now.Ticks; // 638345678901234567 (100ns 刻度)
now.Kind; // Local / Utc / Unspecified (極重要!)
| 代碼 | 輸出示例 | 說明 |
|---|---|---|
now.ToString("yyyy-MM-dd") | 2023-10-01 | 標準日期 |
now.ToString("yyyy-MM-dd HH:mm:ss") | 2023-10-01 14:30:45 | 標準日期時間 (24小時制) |
now.ToString("yyyy-MM-dd HH:mm:ss.fff") | 2023-10-01 14:30:45.123 | 含毫秒 (SQL datetime 精度) |
now.ToString("o") / now.ToString("yyyy-MM-ddTHH:mm:ss.fffffffK") | 2023-10-01T14:30:45.1234567+08:00 | ISO 8601 標準格式 (含時區),推薦用於序列化/日志/參數傳遞 |
now.ToShortDateString() | 2023/10/01 | 當前文化短日期 |
now.ToShortTimeString() | 14:30 | 當前文化短時間 |
錯誤代碼 (原文截圖所示錯誤):
// ❌ 極度危險:SQL 注入 + 格式依賴當前線程文化
string sql = $"INSERT INTO Orders (OrderTime) VALUES ({DateTime.Now})";
// 生成 SQL 可能為: INSERT ... VALUES (2023-10-01 14:30:45) -- 缺單引號,語法錯
// 或: INSERT ... VALUES (2023/10/01 14:30:45) -- 斜杠分隔,SQL Server 可能報錯
// 或: INSERT ... VALUES (10/01/2023 2:30:45 PM) -- 美式格式,日月顛倒導致數據錯誤
參數化查詢完全避開格式化、單引號、SQL 注入、文化差異所有問題。
string sql = "INSERT INTO Orders (CustomerId, OrderTime, Amount) VALUES (@CustId, @OrderTime, @Amt)";
using (var conn = new SqlConnection(connStr))
using (var cmd = new SqlCommand(sql, conn))
{
cmd.Parameters.Add("@CustId", SqlDbType.Int).Value = 1001;
cmd.Parameters.Add("@OrderTime", SqlDbType.DateTime2).Value = DateTime.Now; // 直接傳 DateTime 對象!
cmd.Parameters.Add("@Amt", SqlDbType.Decimal).Value = 123.45m;
conn.Open();
cmd.ExecuteNonQuery();
}
// Dapper
conn.Execute("INSERT INTO Orders (CustomerId, OrderTime) VALUES (@CustId, @Time)",
new { CustId = 1001, Time = DateTime.Now });
// EF Core
var order = new Order { CustomerId = 1001, OrderTime = DateTime.Now };
context.Orders.Add(order);
context.SaveChanges();
// 無需關心 SQL 格式,ORM 自動生成參數化 SQL
SQL Server datetime/datetime2 不存儲時區信息。最佳實踐:
// 寫入 UTC
cmd.Parameters.Add("@CreatedUtc", SqlDbType.DateTime2).Value = DateTime.UtcNow;
// 讀出並標記
DateTime utcFromDb = reader.GetDateTime(0);
DateTime utcAware = DateTime.SpecifyKind(utcFromDb, DateTimeKind.Utc);
// 轉為用戶本地時間 (假設用戶在中國標準時間)
TimeZoneInfo cst = TimeZoneInfo.FindSystemTimeZoneById("China Standard Time");
DateTime localTime = TimeZoneInfo.ConvertTimeFromUtc(utcAware, cst);
| 類型 | 精度 | 範圍 | 存儲 | 建議 |
|---|---|---|---|---|
datetime | ~3.33ms | 1753-9999 | 8 bytes | 舊系統兼容 |
datetime2(n) | 100ns (n=7) | 0001-9999 | 6-8 bytes | 新項目首選,精度高、範圍廣、符合標準 |
datetimeoffset(n) | 100ns | 0001-9999 | 8-10 bytes | 需保留時區偏移時使用 |
date | 1 天 | 0001-9999 | 3 bytes | 僅需日期 (生日、證件有效期) |
time(n) | 100ns | 00:00-23:59 | 3-5 bytes | 僅需時間 (班次、鬧鐘) |
DateTime 對象 給參數,不要 .ToString()。datetime2,避開 datetime 精度與範圍限制。