傳統 ADO.NET 新增/修改資料時,需手動建立 `SqlParameter` 陣列並逐一對應模型屬性:
// 繁瑣且易出錯
parameters[0].Value = model.Name;
parameters[1].Value = model.Phone;
parameters[2].Value = model.Title;
// ... 幾十個欄位
每新增欄位就要修改兩處(SQL 參數宣告 + 賦值),維護成本高、易拼錯名稱。
利用 `System.Reflection` 在執行期列舉模型屬性,自動填入參數集合。
using System;
using System.Data;
using System.Data.SqlClient;
using System.Reflection;
public static class SqlHelper
{
///
/// 依模型屬性順序自動填入 SqlParameter 值(屬性名稱需與參數名稱對應,去掉 @)
///
public static void FillParametersFromModel(SqlParameter[] parameters, object model, int startIndex = 0)
{
if (model == null) throw new ArgumentNullException(nameof(model));
PropertyInfo[] props = model.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
for (int i = 0; i < props.Length && (startIndex + i) < parameters.Length; i++)
{
var prop = props[i];
var value = prop.GetValue(model, null);
parameters[startIndex + i].Value = value ?? DBNull.Value; // null 轉 DBNull
}
}
///
/// 依參數名稱匹配模型屬性(不依賴順序,較安全)
///
public static void FillParametersByName(SqlParameter[] parameters, object model)
{
if (model == null) throw new ArgumentNullException(nameof(model));
var props = model.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
var propDict = props.ToDictionary(p => p.Name, p => p, StringComparer.OrdinalIgnoreCase);
foreach (var param in parameters)
{
string propName = param.ParameterName.TrimStart('@');
if (propDict.TryGetValue(propName, out var prop))
{
var value = prop.GetValue(model, null);
param.Value = value ?? DBNull.Value;
}
}
}
}
public class Liuyan
{
public string Name { get; set; }
public string Phone { get; set; }
public string Title { get; set; }
public string Company { get; set; }
public string Address { get; set; }
public string Country { get; set; }
public string Email { get; set; }
public string Content { get; set; }
public DateTime AddDate { get; set; }
// 若有 ID 為自增,插入時不需參數,可用 [IgnoreMapping] 屬性標記排除
}
public bool Add(Liuyan model)
{
string sql = @"INSERT INTO liuyan (name, phone, title, company, address, country, email, content, adddate)
VALUES (@name, @phone, @title, @company, @address, @country, @email, @content, @adddate)";
var parameters = new[]
{
new SqlParameter("@name", SqlDbType.NVarChar, 50),
new SqlParameter("@phone", SqlDbType.NVarChar, 20),
new SqlParameter("@title", SqlDbType.NVarChar, 50),
new SqlParameter("@company", SqlDbType.NVarChar, 100),
new SqlParameter("@address", SqlDbType.NVarChar, 200),
new SqlParameter("@country", SqlDbType.NVarChar, 50),
new SqlParameter("@email", SqlDbType.NVarChar, 100),
new SqlParameter("@content", SqlDbType.NVarChar, -1), // MAX
new SqlParameter("@adddate", SqlDbType.DateTime)
};
// 一鍵自動映射(依參數名稱匹配,順序無關)
SqlHelper.FillParametersByName(parameters, model);
return SqlHelper.ExecuteNonQuery(sql, parameters) > 0;
}
[AttributeUsage(AttributeTargets.Property)]
public class ColumnMappingAttribute : Attribute
{
public string ParameterName { get; set; } // 對應 @參數名
public bool Ignore { get; set; } = false; // 是否忽略
public object DefaultValue { get; set; } // null 時預設值
}
public class Liuyan
{
[ColumnMapping(ParameterName = "@name")]
public string Name { get; set; }
[ColumnMapping(Ignore = true)] // 自增 ID 不參與 INSERT
public int Id { get; set; }
[ColumnMapping(DefaultValue = "N/A")]
public string Phone { get; set; }
}
public static void FillParametersAdvanced(SqlParameter[] parameters, object model)
{
var props = model.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in props)
{
var attr = prop.GetCustomAttribute();
if (attr?.Ignore == true) continue;
string paramName = attr?.ParameterName ?? "@" + prop.Name;
var param = Array.Find(parameters, p => p.ParameterName.Equals(paramName, StringComparison.OrdinalIgnoreCase));
if (param == null) continue;
var value = prop.GetValue(model);
if (value == null && attr?.DefaultValue != null) value = attr.DefaultValue;
param.Value = value ?? DBNull.Value;
}
}
| 方案 | 效能 | 適用場景 |
|---|---|---|
| 每次反射 GetProperties | ~微秒級,高頻呼叫可感知 | 低頻管理功能、後台作業 |
| 緩存 PropertyInfo[] (static Dictionary<Type, PropertyInfo[]>) | 首次反射後極快 | 高頻 API、核心業務 |
| Expression Tree 編譯委派 | 接近手寫程式碼速度 | 極高效能需求、ORM 核心 |
| Dapper / Entity Framework Core | 成熟優化、功能完整 | 新專案首選,避免造輪子 |
private static readonly ConcurrentDictionary _propCache = new();
public static PropertyInfo[] GetCachedProperties(Type type)
{
return _propCache.GetOrAdd(type, t => t.GetProperties(BindingFlags.Public | BindingFlags.Instance));
}
現代專案建議直接採用 Dapper (`connection.Execute(sql, model)`) 或 EF Core,內建高效映射、參數化、追蹤變更,專注業務邏輯而非基礎設施。