首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> ASP.NET 基礎

ASP.NET 反射自動映射模型屬性至 SqlParameter 減少重複代碼

     編輯:ASP.NET 基礎

痛點:手動逐一賦值參數

傳統 ADO.NET 新增/修改資料時,需手動建立 `SqlParameter` 陣列並逐一對應模型屬性:


// 繁瑣且易出錯
parameters[0].Value = model.Name;
parameters[1].Value = model.Phone;
parameters[2].Value = model.Title;
// ... 幾十個欄位

每新增欄位就要修改兩處(SQL 參數宣告 + 賦值),維護成本高、易拼錯名稱。

解決方案:反射自動映射

利用 `System.Reflection` 在執行期列舉模型屬性,自動填入參數集合。

核心 Helper 方法


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,內建高效映射、參數化、追蹤變更,專注業務邏輯而非基礎設施。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved