C# 反射詳解二

一些常用的ORM大家都應該知道,像微軟的EF、國內的SqlSugar等......

其實他們的底層也都是基於ado.net,只不過在ado.net上加以封裝。一般大公司都有自己的一套ORM,可以說這個東西咱不能不學,必須得造造輪子。😀

傳統的ado.net查詢數據,如下:根據id查詢數據

public class Products
{
    public Guid Id { get; set; }
    public string ProductName { get; set; }
    public float ProductPrice { get; set; }
    public string Period { get; set; }
    public DateTime CreateDate { get; set; }
}
public static class config
{
    public const string SqlConnStr = "Data Source=DESKTOP-4TU9A6M;Initial Catalog=CoreFrame;User ID=sa;Password=123456";
}
public async Task<Products> FindProducts(Guid id)
{
    string sql = $@"SELECT [Id]
                   ,[ProductName]
                   ,[ProductPrice]
                   ,[Period]
                   ,[CreateDate]
               FROM [CoreFrame].[dbo].[Products]
               Where Id='{id}'";
    using (SqlConnection conn = new SqlConnection(config.SqlConnStr))
    {
        SqlCommand command = new SqlCommand(sql, conn);
        conn.Open();
        var reader = command.ExecuteReader();
        if (reader.Read())
        {
            Products products = new Products()
            {
                Id = (Guid)reader["Id"],
                ProductName = reader["ProductName"].ToString(),
                ProductPrice = (float)reader["ProductPrice"],
                Period = reader["Period"].ToString(),
                CreateDate = (DateTime)reader["CreateDate"]
            };
            return products;
        }
        else
        {
            return null;
        }
    }
}

可以加以封裝,一個方法滿足所有表的主鍵查詢。

public async Task<T> Find<T>(Guid id)
{
    //不同的T代表不同的sql--反射拼裝sql
    Type type = typeof(T);
    //將查詢到的(數組列)每一列以逗號隔開拼成字符串
    string columnString = string.Join(",", type.GetProperties().Select(m => $"[{m.Name}]"));
    string sql = $@"SELECT {columnString} FROM [{type.Name}] Where Id='{id}'";
    using (SqlConnection conn = new SqlConnection(config.SqlConnStr))
    {
        SqlCommand command = new SqlCommand(sql, conn);
        conn.Open();
        var reader = command.ExecuteReader();
        if (reader.Read())
        {
            //創建對象
            T t = (T)Activator.CreateInstance(type);
            foreach (var item in type.GetProperties())
            {
                //給實體(t)的這個屬性(item)設置爲這個值reader[item.Name]
                //爲nul就給null值,不爲null就給查到的值
                item.SetValue(t, reader[item.Name] is DBNull ? null : reader[item.Name]);
            }
            return (T)t;
        }
        else
        {
            return default(T);
        }
    }
}

如果數據庫表名稱與後臺對應的實體類名不一樣就需要名稱映射

[Table("Shops")]
public class Shops
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string Remarks { get; set; }
    public DateTime Date { get; set; }
}
/// <summary>
/// 數據庫映射特性
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class TableAttribute : Attribute
{
    private string _Name = string.Empty;
    public TableAttribute(string name)
    {
        this._Name = name;
    }

    public string GetName()
    {
        return _Name;
    }
}
public static class DBAttributeExtend
{
    public static string GetMappingName(this Type type)
    {
        //是否有這個特性(TableAttribute)標識
        if (type.IsDefined(typeof(TableAttribute), true))
        {
            //用反射獲取這個特性的實例對象
            TableAttribute attribute = (TableAttribute)type.GetCustomAttribute(typeof(TableAttribute), true);
            //調用特性中的方法
            return attribute.GetName();
        }
        else
            return type.Name;
    }
}

修改數據庫查詢字符串

//不同的T代表不同的sql--反射拼裝sql
Type type = typeof(T);
//將查詢到的(數組列)每一列以逗號隔開拼成字符串
string columnString = string.Join(",", type.GetProperties().Select(m => $"[{m.Name}]"));
//type.GetMappingName()=>得到特性上的參數
string sql = $@"SELECT {columnString} FROM [{type.GetMappingName()}] Where Id='{id}'";
using (SqlConnection conn = new SqlConnection(config.SqlConnStr))
{
//... ...
}
//... ...

屬性也可以映射

/// <summary>
/// 實體類中的屬性的映射
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static string GetMappingName(this PropertyInfo prop)
{
    //是否有這個特性(ColumnAttribute)標識
    if (prop.IsDefined(typeof(ColumnAttribute), true))
    {
        //用反射獲取這個特性的實例對象
        ColumnAttribute attribute = (ColumnAttribute)prop.GetCustomAttribute(typeof(ColumnAttribute), true);
        //調用特性中的方法
        return attribute.GetName();
    }
    else
        return prop.Name;
}
/// <summary>
/// 數據庫映射特性
/// </summary>
[AttributeUsage(AttributeTargets.All)]
public class ColumnAttribute : Attribute
{
    private string _Name = string.Empty;
    public ColumnAttribute(string name)
    {
        this._Name = name;
    }

    public string GetName()
    {
        return _Name;
    }
}
[Table("Shops")]
public class Shops
{
    public Guid Id { get; set; }
    [Column("Name")]
    public string ShopName { get; set; }
    public string Remarks { get; set; }
    public DateTime Date { get; set; }
}
public async Task<T> Find<T>(Guid id)
{
    //不同的T代表不同的sql--反射拼裝sql
    Type type = typeof(T);
    //將查詢到的(數組列)每一列以逗號隔開拼成字符串
    string columnString = string.Join(",", type.GetProperties().Select(m => $"[{m.GetMappingName()}]"));
    //type.GetMappingName()=>得到特性上的參數
    string sql = $@"SELECT {columnString} FROM [{type.GetMappingName()}] Where Id='{id}'";
    using (SqlConnection conn = new SqlConnection(config.SqlConnStr))
    {
        SqlCommand command = new SqlCommand(sql, conn);
        conn.Open();
        var reader = command.ExecuteReader();
        if (reader.Read())
        {
            //創建對象
            T t = (T)Activator.CreateInstance(type);
            foreach (var item in type.GetProperties())
            {
                //給實體(t)的這個屬性(item)設置爲這個值reader[item.Name]
                //爲nul就給null值,不爲null就給查到的值
                item.SetValue(t, reader[item.GetMappingName()] is DBNull ? null : reader[item.GetMappingName()]);
            }
            return (T)t;
        }
        else
        {
            return default(T);
        }
    }
}

可以將GetMappingName方法整合爲一個,Type和PropertyInfo都繼承於MemberInfo,可以寫個泛型方法再加以約束。

 新建一個基類

/// <summary>
/// 數據庫映射的特性基類
/// </summary>
public class ORMBaseAttribute : Attribute
{
    private string _Name = string.Empty;
    public ORMBaseAttribute(string name)
    {
        this._Name = name;
    }

    public virtual string GetName()
    {
        return _Name;
    }
}

修改TableAttribute和ColumnAttribute類

/// <summary>
/// 數據庫字段映射特性
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class ColumnAttribute : ORMBaseAttribute
{
    public ColumnAttribute(string name) : base(name)
    {
    }

}
/// <summary>
/// 數據庫表映射特性
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class TableAttribute : ORMBaseAttribute
{
    public TableAttribute(string name) : base(name)
    {

    }
}
/// <summary>
/// 數據庫映射
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static string GetMappingName<T>(this T type) where T : MemberInfo
{
    //是否有這個特性(ORMBaseAttribute)標識
    if (type.IsDefined(typeof(ORMBaseAttribute), true))
    {
        //用反射獲取這個特性的實例對象
        ORMBaseAttribute attribute = (ORMBaseAttribute)type.GetCustomAttribute(typeof(ORMBaseAttribute), true);
        //調用特性中的方法
        return attribute.GetName();
    }
    else
        return type.Name;
}

 添加數據

public class ShopType
{
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }
    public string Remarks { get; set; }
    public DateTime Date { get; set; }
}
[AttributeUsage(AttributeTargets.Property)]
public class KeyAttribute : Attribute
{
    public KeyAttribute()
    {

    }
}
public static IEnumerable<PropertyInfo> GetPropertyWithoutKey(this Type type)
{
    //將類型傳進來,過濾掉屬性上有KeyAttribute的字段=>主鍵不能插入賦值
    return type.GetProperties().Where(m => !m.IsDefined(typeof(KeyAttribute), true));
}
/// <summary>
/// 數據插入
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
public async Task<bool> Insert<T>(T t)
{

    Type type = typeof(T);
    //將查詢到的(數組列)每一列以逗號隔開拼成字符串  主鍵是不能夠賦值插入的所以會過濾掉主鍵這個列=>type.GetPropertyWithoutKey()
    string columnString = string.Join(",", type.GetPropertyWithoutKey().Select(m => $"[{m.Name}]"));
    //獲取值,拼接爲字符串
    string valueString = string.Join(",", type.GetPropertyWithoutKey().Select(m => $"'{m.GetValue(t)}'"));
    string sql = @$"INSERT INTO [{type.Name}] ({columnString}) Values({valueString})";
    using (SqlConnection conn = new SqlConnection(config.SqlConnStr))
    {
        SqlCommand command = new SqlCommand(sql, conn);
        conn.Open();
        int result = command.ExecuteNonQuery();
        return result == 1;
    }
}

防止sql注入,參數化拼裝值。

public async Task<bool> Insert<T>(T t)
{

    Type type = typeof(T);
    //將查詢到的(數組列)每一列以逗號隔開拼成字符串  主鍵是不能夠賦值插入的所以會過濾掉主鍵這個列=>type.GetPropertyWithoutKey()  m.GetMappingName()=>獲取映射的值
    string columnString = string.Join(",", type.GetPropertyWithoutKey().Select(m => $"[{m.GetMappingName()}]"));
    //獲取值,拼接爲字符串
    string valueString = string.Join(",", type.GetPropertyWithoutKey().Select(m => $"@{m.GetMappingName()}"));
    string sql = @$"INSERT INTO [{type.GetMappingName()}] ({columnString}) Values({valueString})";

    //轉成參數列表  屬性名稱--值  Select=>Foreach
    IEnumerable<SqlParameter> parameters = type.GetPropertyWithoutKey().Select(m => new SqlParameter($"@{m.Name}", m.GetValue(t) ?? DBNull.Value));

    using (SqlConnection conn = new SqlConnection(config.SqlConnStr))
    {
        SqlCommand command = new SqlCommand(sql, conn);
        command.Parameters.AddRange(parameters.ToArray());
        conn.Open();
        int result = command.ExecuteNonQuery();
        return result == 1;
    }
}

性能調優:將拼接構造sql語句的過程,緩存下來。

泛型在運行時纔會確定類型,JIT會爲不同的T構造不同的類型副本,傳不同的T都會產生一個全新的類,裏面的字段也會重新初始化一份。

表字段新增、減少,程序會重新啓動,然後會再加載緩存數據。

/// <summary>
/// sql生成+緩存
/// </summary>
/// <typeparam name="T"></typeparam>
public class SqlBuilder<T>
{
    private static string FindOneSql = string.Empty;
    private static string InsertSql = string.Empty;
    static SqlBuilder()
    {
        #region 添加
        Type type = typeof(T);
        //將查詢到的(數組列)每一列以逗號隔開拼成字符串  主鍵是不能夠賦值插入的所以會過濾掉主鍵這個列=>type.GetPropertyWithoutKey()
        string columnString = string.Join(",", type.GetPropertyWithoutKey().Select(m => $"[{m.GetMappingName()}]"));
        //獲取值,拼接爲字符串
        string valueString = string.Join(",", type.GetPropertyWithoutKey().Select(m => $"@{m.GetMappingName()}"));
        InsertSql = @$"INSERT INTO [{type.GetMappingName()}] ({columnString}) Values({valueString})";
        #endregion

        #region 查詢
        //將查詢到的(數組列)每一列以逗號隔開拼成字符串
        string columnStrings = string.Join(",", type.GetProperties().Select(m => $"[{m.GetMappingName()}]"));
        //type.GetMappingName()=>得到特性上的參數
        FindOneSql = $@"SELECT {columnStrings} FROM [{type.GetMappingName()}] Where Id=";
        #endregion
    }

    public static string GetSql(SqlType sqlType)
    {
        switch (sqlType)
        {
            case SqlType.FindOneSql:
                return FindOneSql;
            case SqlType.InsertSql:
                return InsertSql;
            default:
                throw new Exception("wrong SqlType");
        }
    }

    public enum SqlType
    {
        FindOneSql,
        InsertSql
    }
}

修改查詢與新增的方法:

/// <summary>
/// 數據查詢
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="id"></param>
/// <returns></returns>
public async Task<T> Find<T>(Guid id)
{
    //不同的T代表不同的sql--反射拼裝sql
    Type type = typeof(T);
    //將查詢到的(數組列)每一列以逗號隔開拼成字符串
    //string columnString = string.Join(",", type.GetProperties().Select(m => $"[{m.GetMappingName()}]"));
    //type.GetMappingName()=>得到特性上的參數
    //string sql = $@"SELECT {columnString} FROM [{type.GetMappingName()}] Where Id='{id}'";

    string sql = $"{SqlBuilder<T>.GetSql(SqlBuilder<T>.SqlType.FindOneSql)}'{id}'";
    using (SqlConnection conn = new SqlConnection(config.SqlConnStr))
    {
        SqlCommand command = new SqlCommand(sql, conn);
        conn.Open();
        var reader = command.ExecuteReader();
        if (reader.Read())
        {
            //創建對象
            T t = (T)Activator.CreateInstance(type);
            foreach (var item in type.GetProperties())
            {
                //給實體(t)的這個屬性(item)設置爲這個值reader[item.Name]
                //爲nul就給null值,不爲null就給查到的值
                item.SetValue(t, reader[item.GetMappingName()] is DBNull ? null : reader[item.GetMappingName()]);
            }
            return (T)t;
        }
        else
        {
            return default(T);
        }
    }
}

/// <summary>
/// 數據插入
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
public async Task<bool> Insert<T>(T t)
{

    Type type = typeof(T);
    //將查詢到的(數組列)每一列以逗號隔開拼成字符串  主鍵是不能夠賦值插入的所以會過濾掉主鍵這個列=>type.GetPropertyWithoutKey()
    //string columnString = string.Join(",", type.GetPropertyWithoutKey().Select(m => $"[{m.GetMappingName()}]"));
    //獲取值,拼接爲字符串
    //string valueString = string.Join(",", type.GetPropertyWithoutKey().Select(m => $"@{m.GetMappingName()}"));
    //string sql = @$"INSERT INTO [{type.GetMappingName()}] ({columnString}) Values({valueString})";

    string sql = SqlBuilder<T>.GetSql(SqlBuilder<T>.SqlType.InsertSql);
    //轉成參數列表  屬性名稱--值  Select=>Foreach
    IEnumerable<SqlParameter> parameters = type.GetPropertyWithoutKey().Select(m => new SqlParameter($"@{m.Name}", m.GetValue(t) ?? DBNull.Value));

    using (SqlConnection conn = new SqlConnection(config.SqlConnStr))
    {
        SqlCommand command = new SqlCommand(sql, conn);
        command.Parameters.AddRange(parameters.ToArray());
        conn.Open();
        int result = command.ExecuteNonQuery();
        return result == 1;
    }
}

 數據更新

/// <summary>
/// 數據修改
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
public async Task<bool> Update<T>(T t) where T : BaseModel
{
    Type type = t.GetType();
    //type.GetPropertyWithoutKey()  過濾掉主鍵,主鍵不能更新,不然會報錯
    //m.GetMappingName() 映射--解決數據庫中名稱與程序中名稱不一致
    string updateStr = string.Join(",", type.GetPropertyWithoutKey().Select(m => $"{m.GetMappingName()}='{m.GetValue(t)}'"));
    string sql = @$"update [{type.GetMappingName()}] set {updateStr} where id='{t.Id}'";
    using (SqlConnection conn = new SqlConnection(config.SqlConnStr))
    {
        SqlCommand command = new SqlCommand(sql, conn);
        conn.Open();
        int result = command.ExecuteNonQuery();
        return result == 1;
    }
}

問題:在要更新的數據前面加一個單引號.

 可見,有sql注入的問題存在,對代碼進行修改:

/// <summary>
/// 數據修改
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
public async Task<bool> Update<T>(T t) where T : BaseModel
{
    Type type = t.GetType();
    //type.GetPropertyWithoutKey()  過濾掉主鍵,主鍵不能更新,不然會報錯
    //m.GetMappingName() 映射--解決數據庫中名稱與程序中名稱不一致
    string updateStr = string.Join(",", type.GetPropertyWithoutKey().Select(m => $"{m.GetMappingName()}=@{m.GetMappingName()}"));
    //參數名稱:m.GetMappingName()   值(null值需要換成DBNull.Value):m.GetValue(t) ?? DBNull.Value
    var sqlParameterList = type.GetPropertyWithoutKey().Select(m => new SqlParameter(m.GetMappingName(), m.GetValue(t) ?? DBNull.Value)).ToArray();
    string sql = @$"update [{type.GetMappingName()}] set {updateStr} where id='{t.Id}'";
    using (SqlConnection conn = new SqlConnection(config.SqlConnStr))
    {
        SqlCommand command = new SqlCommand(sql, conn);
        //添加參數
        command.Parameters.AddRange(sqlParameterList);
        conn.Open();
        int result = command.ExecuteNonQuery();
        return result == 1;
    }
}

反射拼裝sql影響性能,建緩存提升性能

/// <summary>
/// sql生成+緩存
/// </summary>
/// <typeparam name="T"></typeparam>
public class SqlBuilder<T>
{
    private static string FindOneSql = string.Empty;
    private static string InsertSql = string.Empty;
    private static string UpdateSql = string.Empty;
    static SqlBuilder()
    {
        #region 添加
        Type type = typeof(T);
        //將查詢到的(數組列)每一列以逗號隔開拼成字符串  主鍵是不能夠賦值插入的所以會過濾掉主鍵這個列=>type.GetPropertyWithoutKey()
        string columnString = string.Join(",", type.GetPropertyWithoutKey().Select(m => $"[{m.GetMappingName()}]"));
        //獲取值,拼接爲字符串
        string valueString = string.Join(",", type.GetPropertyWithoutKey().Select(m => $"@{m.GetMappingName()}"));
        InsertSql = @$"INSERT INTO [{type.GetMappingName()}] ({columnString}) Values({valueString})";
        #endregion

        #region 查詢
        //將查詢到的(數組列)每一列以逗號隔開拼成字符串
        string columnStrings = string.Join(",", type.GetProperties().Select(m => $"[{m.GetMappingName()}]"));
        //type.GetMappingName()=>得到特性上的參數
        FindOneSql = $@"SELECT {columnStrings} FROM [{type.GetMappingName()}] Where Id=";
        #endregion

        #region 修改
        //type.GetPropertyWithoutKey()  過濾掉主鍵,主鍵不能更新,不然會報錯
        //m.GetMappingName() 映射--解決數據庫中名稱與程序中名稱不一致
        string updateStr = string.Join(",", type.GetPropertyWithoutKey().Select(m => $"{m.GetMappingName()}=@{m.GetMappingName()}"));
        UpdateSql = @$"update [{type.GetMappingName()}] set {updateStr} where id=";
        #endregion
    }

    public static string GetSql(SqlType sqlType)
    {
        switch (sqlType)
        {
            case SqlType.FindOneSql:
                return FindOneSql;
            case SqlType.InsertSql:
                return InsertSql;
            case SqlType.UpdateSql:
                return UpdateSql;
            default:
                throw new Exception("wrong SqlType");
        }
    }

    public enum SqlType
    {
        FindOneSql,
        InsertSql,
        UpdateSql
    }
}

修改代碼:

/// <summary>
/// 數據修改
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
public async Task<bool> Update<T>(T t) where T : BaseModel
{
    Type type = t.GetType();
    string sql = $"{SqlBuilder<T>.GetSql(SqlBuilder<T>.SqlType.UpdateSql)}'{t.Id}'";
    //參數名稱:m.GetMappingName()   值:m.GetValue(t) ?? DBNull.Value
    var sqlParameterList = type.GetPropertyWithoutKey().Select(m => new SqlParameter(m.GetMappingName(), m.GetValue(t) ?? DBNull.Value)).ToArray();
    using (SqlConnection conn = new SqlConnection(config.SqlConnStr))
    {
        SqlCommand command = new SqlCommand(sql, conn);
        //添加參數
        command.Parameters.AddRange(sqlParameterList);
        conn.Open();
        int result = command.ExecuteNonQuery();
        return result == 1;
    }
}

數據刪除

/// <summary>
/// sql生成+緩存
/// </summary>
/// <typeparam name="T"></typeparam>
public class SqlBuilder<T>
{
    private static string FindOneSql = string.Empty;
    private static string InsertSql = string.Empty;
    private static string UpdateSql = string.Empty;
    private static string DeleteSql = string.Empty;
    static SqlBuilder()
    {
        #region 添加
        Type type = typeof(T);
        //將查詢到的(數組列)每一列以逗號隔開拼成字符串  主鍵是不能夠賦值插入的所以會過濾掉主鍵這個列=>type.GetPropertyWithoutKey()
        string columnString = string.Join(",", type.GetPropertyWithoutKey().Select(m => $"[{m.GetMappingName()}]"));
        //獲取值,拼接爲字符串
        string valueString = string.Join(",", type.GetPropertyWithoutKey().Select(m => $"@{m.GetMappingName()}"));
        InsertSql = @$"INSERT INTO [{type.GetMappingName()}] ({columnString}) Values({valueString})";
        #endregion

        #region 查詢
        //將查詢到的(數組列)每一列以逗號隔開拼成字符串
        string columnStrings = string.Join(",", type.GetProperties().Select(m => $"[{m.GetMappingName()}]"));
        //type.GetMappingName()=>得到特性上的參數
        FindOneSql = $@"SELECT {columnStrings} FROM [{type.GetMappingName()}] Where Id=";
        #endregion

        #region 修改
        //type.GetPropertyWithoutKey()  過濾掉主鍵,主鍵不能更新,不然會報錯
        //m.GetMappingName() 映射--解決數據庫中名稱與程序中名稱不一致
        string updateStr = string.Join(",", type.GetPropertyWithoutKey().Select(m => $"{m.GetMappingName()}=@{m.GetMappingName()}"));
        UpdateSql = @$"update [{type.GetMappingName()}] set {updateStr} where id=";
        #endregion

        #region 刪除
        DeleteSql = $"delete from {type.GetMappingName()} where id=";
        #endregion
    }

    public static string GetSql(SqlType sqlType)
    {
        switch (sqlType)
        {
            case SqlType.FindOneSql:
                return FindOneSql;
            case SqlType.InsertSql:
                return InsertSql;
            case SqlType.UpdateSql:
                return UpdateSql;
            case SqlType.DeleteSql:
                return DeleteSql;
            default:
                throw new Exception("wrong SqlType");
        }
    }

    public enum SqlType
    {
        FindOneSql,
        InsertSql,
        UpdateSql,
        DeleteSql
    }
}
/// <summary>
/// 數據刪除
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="id"></param>
/// <returns></returns>
public async Task<bool> Delete<T>(Guid id) where T : BaseModel
{
    Type type = typeof(T);
    string sql = $"{SqlBuilder<T>.GetSql(SqlBuilder<T>.SqlType.DeleteSql)}'{id}'";
    using (SqlConnection conn = new SqlConnection(config.SqlConnStr))
    {
        SqlCommand command = new SqlCommand(sql, conn);
        //添加參數
        //command.Parameters.AddRange(sqlParameterList);
        conn.Open();
        int result = command.ExecuteNonQuery();
        return result == 1;
    }
}

批量刪除

/// <summary>
/// 批量刪除
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list"></param>
/// <returns></returns>
public async Task<bool> Delete<T>(IEnumerable<T> list) where T : BaseModel
{
    Type type = typeof(T);
    string Ids = string.Join(",", list.Select(m => $"'{m.Id}'"));
    //一條sql,本來就帶事務性質(可以不用再寫批處理語句)
    string sql = $"delete from [{type.GetMappingName()}] where id in ({Ids})";
    using (SqlConnection conn = new SqlConnection(config.SqlConnStr))
    {
        SqlTransaction trans = null;
        try
        {
            conn.Open();
            //開啓事務
            trans = conn.BeginTransaction();
            SqlCommand command = new SqlCommand(sql, conn, trans);
            int iResult = command.ExecuteNonQuery();
            if (iResult == list.Count())
            {
                trans.Commit();
                return true;
            }
            else
                throw new Exception("刪除的數據量不對");
        }
        catch (Exception ex)
        {
            if (trans != null)
                trans.Rollback();
            Console.WriteLine(ex.Message);
            throw ex;
        }
    }
}

將ado.net代碼進行封裝進而可以重用

/// <summary>
/// 爲了代碼複用,可以用委託封裝
/// 不同的邏輯,用委託傳遞
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
private T ExecuteSql<T>(string sql, IEnumerable<SqlParameter> parameters, Func<SqlCommand, T> func)
{
    using (SqlConnection conn = new SqlConnection(config.SqlConnStr))
    {
        SqlCommand command = new SqlCommand(sql, conn);
        //添加參數
        command.Parameters.AddRange(parameters.ToArray());
        conn.Open();
        T t = func.Invoke(command);
        return t;
    }
}

更改增刪改查的代碼

/// <summary>
/// 數據查詢
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="id"></param>
/// <returns></returns>
public async Task<T> Find<T>(Guid id)
{
    //不同的T代表不同的sql--反射拼裝sql
    Type type = typeof(T);
    //將查詢到的(數組列)每一列以逗號隔開拼成字符串
    //string columnString = string.Join(",", type.GetProperties().Select(m => $"[{m.GetMappingName()}]"));
    //type.GetMappingName()=>得到特性上的參數
    //string sql = $@"SELECT {columnString} FROM [{type.GetMappingName()}] Where Id='{id}'";

    //string sql = $"{SqlBuilder<T>.GetSql(SqlBuilder<T>.SqlType.FindOneSql)}'{id}'";
    string sql = SqlBuilder<T>.GetSql(SqlBuilder<T>.SqlType.FindOneSql);
    IEnumerable<SqlParameter> parameters = new List<SqlParameter>()
    {
        new SqlParameter("@id",id)
    };
    T tResult = this.ExecuteSql<T>(sql, parameters, (command) =>
    {
        var reader = command.ExecuteReader();
        if (reader.Read())
        {
            //創建對象
            T t = (T)Activator.CreateInstance(type);
            foreach (var item in type.GetProperties())
            {
                //給實體(t)的這個屬性(item)設置爲這個值reader[item.Name]
                //爲nul就給null值,不爲null就給查到的值
                item.SetValue(t, reader[item.GetMappingName()] is DBNull ? null : reader[item.GetMappingName()]);
            }
            return t;
        }
        else
        {
            throw new Exception("主鍵查詢,沒有結果!");
        }
    });
    return tResult;
    //using (SqlConnection conn = new SqlConnection(config.SqlConnStr))
    //{
    //    SqlCommand command = new SqlCommand(sql, conn);
    //    conn.Open();
    //    var reader = command.ExecuteReader();
    //    if (reader.Read())
    //    {
    //        //創建對象
    //        T t = (T)Activator.CreateInstance(type);
    //        foreach (var item in type.GetProperties())
    //        {
    //            //給實體(t)的這個屬性(item)設置爲這個值reader[item.Name]
    //            //爲nul就給null值,不爲null就給查到的值
    //            item.SetValue(t, reader[item.GetMappingName()] is DBNull ? null : reader[item.GetMappingName()]);
    //        }
    //        return (T)t;
    //    }
    //    else
    //    {
    //        return default(T);
    //    }
    //}
}

/// <summary>
/// 數據插入
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
public async Task<bool> Insert<T>(T t)
{

    Type type = typeof(T);
    //將查詢到的(數組列)每一列以逗號隔開拼成字符串  主鍵是不能夠賦值插入的所以會過濾掉主鍵這個列=>type.GetPropertyWithoutKey()
    //string columnString = string.Join(",", type.GetPropertyWithoutKey().Select(m => $"[{m.GetMappingName()}]"));
    //獲取值,拼接爲字符串
    //string valueString = string.Join(",", type.GetPropertyWithoutKey().Select(m => $"@{m.GetMappingName()}"));
    //string sql = @$"INSERT INTO [{type.GetMappingName()}] ({columnString}) Values({valueString})";

    string sql = SqlBuilder<T>.GetSql(SqlBuilder<T>.SqlType.InsertSql);
    //轉成參數列表  屬性名稱--值  Select=>Foreach
    IEnumerable<SqlParameter> parameters = type.GetPropertyWithoutKey().Select(m => new SqlParameter($"@{m.Name}", m.GetValue(t) ?? DBNull.Value));

    //using (SqlConnection conn = new SqlConnection(config.SqlConnStr))
    //{
    //    SqlCommand command = new SqlCommand(sql, conn);
    //    command.Parameters.AddRange(parameters.ToArray());
    //    conn.Open();
    //    int result = command.ExecuteNonQuery();
    //    return result == 1;
    //}
    int iResult = this.ExecuteSql<int>(sql, parameters, (m) => m.ExecuteNonQuery());
    return iResult == 1;
}

/// <summary>
/// 數據修改
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
public async Task<bool> Update<T>(T t) where T : BaseModel
{
    Type type = t.GetType();
    string sql = $"{SqlBuilder<T>.GetSql(SqlBuilder<T>.SqlType.UpdateSql)}'{t.Id}'";
    //參數名稱:m.GetMappingName()   值:m.GetValue(t) ?? DBNull.Value
    var sqlParameterList = type.GetPropertyWithoutKey().Select(m => new SqlParameter(m.GetMappingName(), m.GetValue(t) ?? DBNull.Value)).ToArray();

    //using (SqlConnection conn = new SqlConnection(config.SqlConnStr))
    //{
    //    SqlCommand command = new SqlCommand(sql, conn);
    //    //添加參數
    //    command.Parameters.AddRange(sqlParameterList);
    //    conn.Open();
    //    int result = command.ExecuteNonQuery();
    //    return result == 1;
    //}

    int iResult = this.ExecuteSql<int>(sql, sqlParameterList, (m) => m.ExecuteNonQuery());
    return iResult == 1;
}


/// <summary>
/// 數據刪除
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="id"></param>
/// <returns></returns>
public async Task<bool> Delete<T>(Guid id) where T : BaseModel
{
    Type type = typeof(T);
    //string sql = $"{SqlBuilder<T>.GetSql(SqlBuilder<T>.SqlType.DeleteSql)}'{id}'";
    string sql = SqlBuilder<T>.GetSql(SqlBuilder<T>.SqlType.DeleteSql);
    //using (SqlConnection conn = new SqlConnection(config.SqlConnStr))
    //{
    //    SqlCommand command = new SqlCommand(sql, conn);
    //    //添加參數
    //    //command.Parameters.AddRange(sqlParameterList);
    //    conn.Open();
    //    int result = command.ExecuteNonQuery();
    //    return result == 1;
    //}
    IEnumerable<SqlParameter> parameters = new List<SqlParameter>()
    {
        new SqlParameter("@id",id)
    };
    int iResult = this.ExecuteSql<int>(sql, parameters, (m) => m.ExecuteNonQuery());
    return iResult == 1;
}


/// <summary>
/// 批量刪除
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list"></param>
/// <returns></returns>
public async Task<bool> Delete<T>(IEnumerable<T> list) where T : BaseModel
{
    Type type = typeof(T);
    string Ids = string.Join(",", list.Select(m => $"'{m.Id}'"));
    //一條sql,本來就帶事務性質(可以不用再寫批處理語句)
    string sql = $"delete from [{type.GetMappingName()}] where id in ({Ids})";
    using (SqlConnection conn = new SqlConnection(config.SqlConnStr))
    {
        SqlTransaction trans = null;
        try
        {
            conn.Open();
            //開啓事務
            trans = conn.BeginTransaction();
            SqlCommand command = new SqlCommand(sql, conn, trans);
            int iResult = command.ExecuteNonQuery();
            if (iResult == list.Count())
            {
                trans.Commit();
                return true;
            }
            else
                throw new Exception("刪除的數據量不對");
        }
        catch (Exception ex)
        {
            if (trans != null)
                trans.Rollback();
            Console.WriteLine(ex.Message);
            throw ex;
        }
    }
}
/// <summary>
/// sql生成+緩存
/// </summary>
/// <typeparam name="T"></typeparam>
public class SqlBuilder<T>
{
    private static string FindOneSql = string.Empty;
    private static string InsertSql = string.Empty;
    private static string UpdateSql = string.Empty;
    private static string DeleteSql = string.Empty;
    static SqlBuilder()
    {
        #region 添加
        Type type = typeof(T);
        //將查詢到的(數組列)每一列以逗號隔開拼成字符串  主鍵是不能夠賦值插入的所以會過濾掉主鍵這個列=>type.GetPropertyWithoutKey()
        string columnString = string.Join(",", type.GetPropertyWithoutKey().Select(m => $"[{m.GetMappingName()}]"));
        //獲取值,拼接爲字符串
        string valueString = string.Join(",", type.GetPropertyWithoutKey().Select(m => $"@{m.GetMappingName()}"));
        InsertSql = @$"INSERT INTO [{type.GetMappingName()}] ({columnString}) Values({valueString})";
        #endregion

        #region 查詢
        //將查詢到的(數組列)每一列以逗號隔開拼成字符串
        string columnStrings = string.Join(",", type.GetProperties().Select(m => $"[{m.GetMappingName()}]"));
        //type.GetMappingName()=>得到特性上的參數
        FindOneSql = $@"SELECT {columnStrings} FROM [{type.GetMappingName()}] Where Id=@id";
        #endregion

        #region 修改
        //type.GetPropertyWithoutKey()  過濾掉主鍵,主鍵不能更新,不然會報錯
        //m.GetMappingName() 映射--解決數據庫中名稱與程序中名稱不一致
        string updateStr = string.Join(",", type.GetPropertyWithoutKey().Select(m => $"{m.GetMappingName()}=@{m.GetMappingName()}"));
        UpdateSql = @$"update [{type.GetMappingName()}] set {updateStr} where id=";
        #endregion

        #region 刪除
        DeleteSql = $"delete from {type.GetMappingName()} where id=@id";
        #endregion
    }

    public static string GetSql(SqlType sqlType)
    {
        switch (sqlType)
        {
            case SqlType.FindOneSql:
                return FindOneSql;
            case SqlType.InsertSql:
                return InsertSql;
            case SqlType.UpdateSql:
                return UpdateSql;
            case SqlType.DeleteSql:
                return DeleteSql;
            default:
                throw new Exception("wrong SqlType");
        }
    }

    public enum SqlType
    {
        FindOneSql,
        InsertSql,
        UpdateSql,
        DeleteSql
    }
}

 有時間的話還可以進一步的擴展封裝😊

會用和知道原理完全是兩碼事兒,再接再厲!

本篇博客代碼:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章