一些常用的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 } }
有時間的話還可以進一步的擴展封裝😊
會用和知道原理完全是兩碼事兒,再接再厲!
本篇博客代碼:
https://github.com/Davenever/ORMBegin