前面講過ORM的前世此生,對ORM框架不瞭解的朋友能夠參考博文:http://www.javashuo.com/article/p-oupygolv-eq.htmlhtml
今天,咱們主要經過設計一款輕量級的ORM框架來介紹:"如何實現一個ORM框架"mysql
文末給出了GitHub源碼地址~git
既然是ORM框架,那麼必不可或缺的三點:github
甚至能夠在此三點的基礎上擴展出更多的:web
基於以上幾點,那麼咱們逐步開始咱們的設計:sql
爲了功能抽象和細化的職責劃分,咱們將各個功能點拆分紅爲各個組件,靈活進行裝配。數據庫
數據存儲核心:調用底層數據庫驅動執行Sql語句,將數據持久化api
表映射描述器:描述表和實體的映射關係緩存
Sql語句轉化器:將封裝的數據操做Api轉化成對應數據庫的Sql語句性能優化
數據操做上下文:用戶數據操做信息傳遞,包裝,數據庫鏈接管理等,緩存核心配置信息的承載
緩存核心:用戶ORM框架的緩存支持(一級緩存/二級緩存)
咱們抽象出核心功能組件後,對各個功能組件進行詳細設計:
數據存儲核心主要包括對多種數據庫驅動的封裝調用,讀寫分離的簡單策略,查詢數據集合與強類型實體的映射(性能優化點,目前採用Expression 表達式樹緩存委託方式)。
這裏以封裝的支持多種關係型數據庫的DbHelper形式呈現
1 /********************************************************* 2 * CopyRight: 7TINY CODE BUILDER. 3 * Version: 5.0.0 4 * Author: 7tiny 5 * Address: Earth 6 * Create: 2018-04-19 21:34:01 7 * Modify: 2018-04-19 21:34:01 8 * E-mail: dong@7tiny.com | sevenTiny@foxmail.com 9 * GitHub: https://github.com/sevenTiny 10 * Personal web site: http://www.7tiny.com 11 * Technical WebSit: http://www.cnblogs.com/7tiny/ 12 * Description: 13 * Thx , Best Regards ~ 14 *********************************************************/ 15 using MySql.Data.MySqlClient; 16 using System; 17 using System.Collections.Generic; 18 using System.ComponentModel; 19 using System.Data; 20 using System.Data.Common; 21 using System.Data.SqlClient; 22 using System.Linq; 23 using System.Linq.Expressions; 24 using System.Reflection; 25 using System.Threading.Tasks; 26 27 namespace SevenTiny.Bantina.Bankinate 28 { 29 public enum DataBaseType 30 { 31 SqlServer, 32 MySql, 33 Oracle, 34 MongoDB 35 } 36 public abstract class DbHelper 37 { 38 #region ConnString 連接字符串聲明 39 40 /// <summary> 41 /// 鏈接字符串 ConnString_Default 默認,且賦值時會直接覆蓋掉讀寫 42 /// </summary> 43 private static string _connString; 44 public static string ConnString_Default 45 { 46 get { return _connString; } 47 set 48 { 49 _connString = value; 50 ConnString_RW = _connString; 51 ConnString_R = _connString; 52 } 53 } 54 /// <summary> 55 /// 鏈接字符串 ConnString_RW 讀寫數據庫使用 56 /// </summary> 57 public static string ConnString_RW { get; set; } = _connString; 58 /// <summary> 59 /// 鏈接字符串 ConnString_R 讀數據庫使用 60 /// </summary> 61 public static string ConnString_R { get; set; } = _connString; 62 /// <summary> 63 /// DataBaseType Select default:mysql 64 /// </summary> 65 public static DataBaseType DbType { get; set; } = DataBaseType.MySql; 66 67 #endregion 68 69 #region ExcuteNonQuery 執行sql語句或者存儲過程,返回影響的行數---ExcuteNonQuery 70 public static int ExecuteNonQuery(string commandTextOrSpName, CommandType commandType = CommandType.Text) 71 { 72 using (SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_RW)) 73 { 74 using (DbCommandCommon cmd = new DbCommandCommon(DbType)) 75 { 76 PreparCommand(conn.DbConnection, cmd.DbCommand, commandTextOrSpName, commandType); 77 return cmd.DbCommand.ExecuteNonQuery(); 78 } 79 } 80 } 81 public static int ExecuteNonQuery(string commandTextOrSpName, CommandType commandType, IDictionary<string, object> dictionary) 82 { 83 using (SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_RW)) 84 { 85 using (DbCommandCommon cmd = new DbCommandCommon(DbType)) 86 { 87 PreparCommand(conn.DbConnection, cmd.DbCommand, commandTextOrSpName, commandType, dictionary);//參數增長了commandType 能夠本身編輯執行方式 88 return cmd.DbCommand.ExecuteNonQuery(); 89 } 90 } 91 } 92 public static void BatchExecuteNonQuery(IEnumerable<BatchExecuteModel> batchExecuteModels) 93 { 94 using (SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_RW)) 95 { 96 using (DbCommandCommon cmd = new DbCommandCommon(DbType)) 97 { 98 foreach (var item in batchExecuteModels) 99 { 100 PreparCommand(conn.DbConnection, cmd.DbCommand, item.CommandTextOrSpName, item.CommandType, item.ParamsDic); 101 cmd.DbCommand.ExecuteNonQuery(); 102 } 103 } 104 } 105 } 106 public static Task<int> ExecuteNonQueryAsync(string commandTextOrSpName, CommandType commandType = CommandType.Text) 107 { 108 using (SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_RW)) 109 { 110 using (DbCommandCommon cmd = new DbCommandCommon(DbType)) 111 { 112 PreparCommand(conn.DbConnection, cmd.DbCommand, commandTextOrSpName, commandType); 113 return cmd.DbCommand.ExecuteNonQueryAsync(); 114 } 115 } 116 } 117 public static Task<int> ExecuteNonQueryAsync(string commandTextOrSpName, CommandType commandType, IDictionary<string, object> dictionary) 118 { 119 using (SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_RW)) 120 { 121 using (DbCommandCommon cmd = new DbCommandCommon(DbType)) 122 { 123 PreparCommand(conn.DbConnection, cmd.DbCommand, commandTextOrSpName, commandType, dictionary);//參數增長了commandType 能夠本身編輯執行方式 124 return cmd.DbCommand.ExecuteNonQueryAsync(); 125 } 126 } 127 } 128 public static void BatchExecuteNonQueryAsync(IEnumerable<BatchExecuteModel> batchExecuteModels) 129 { 130 using (SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_RW)) 131 { 132 using (DbCommandCommon cmd = new DbCommandCommon(DbType)) 133 { 134 foreach (var item in batchExecuteModels) 135 { 136 PreparCommand(conn.DbConnection, cmd.DbCommand, item.CommandTextOrSpName, item.CommandType, item.ParamsDic); 137 cmd.DbCommand.ExecuteNonQueryAsync(); 138 } 139 } 140 } 141 } 142 #endregion 143 144 #region ExecuteScalar 執行sql語句或者存儲過程,執行單條語句,返回單個結果---ScalarExecuteScalar 145 public static object ExecuteScalar(string commandTextOrSpName, CommandType commandType = CommandType.Text) 146 { 147 using (SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_R, ConnString_RW)) 148 { 149 using (DbCommandCommon cmd = new DbCommandCommon(DbType)) 150 { 151 PreparCommand(conn.DbConnection, cmd.DbCommand, commandTextOrSpName, commandType); 152 return cmd.DbCommand.ExecuteScalar(); 153 } 154 } 155 } 156 public static object ExecuteScalar(string commandTextOrSpName, CommandType commandType, IDictionary<string, object> dictionary) 157 { 158 using (SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_R, ConnString_RW)) 159 { 160 using (DbCommandCommon cmd = new DbCommandCommon(DbType)) 161 { 162 PreparCommand(conn.DbConnection, cmd.DbCommand, commandTextOrSpName, commandType, dictionary); 163 return cmd.DbCommand.ExecuteScalar(); 164 } 165 166 } 167 } 168 public static Task<object> ExecuteScalarAsync(string commandTextOrSpName, CommandType commandType = CommandType.Text) 169 { 170 using (SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_R, ConnString_RW)) 171 { 172 using (DbCommandCommon cmd = new DbCommandCommon(DbType)) 173 { 174 PreparCommand(conn.DbConnection, cmd.DbCommand, commandTextOrSpName, commandType); 175 return cmd.DbCommand.ExecuteScalarAsync(); 176 } 177 } 178 } 179 public static Task<object> ExecuteScalarAsync(string commandTextOrSpName, CommandType commandType, IDictionary<string, object> dictionary) 180 { 181 using (SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_R, ConnString_RW)) 182 { 183 using (DbCommandCommon cmd = new DbCommandCommon(DbType)) 184 { 185 PreparCommand(conn.DbConnection, cmd.DbCommand, commandTextOrSpName, commandType, dictionary); 186 return cmd.DbCommand.ExecuteScalarAsync(); 187 } 188 189 } 190 } 191 #endregion 192 193 #region ExecuteReader 執行sql語句或者存儲過程,返回DataReader---DataReader 194 public static DbDataReader ExecuteReader(string commandTextOrSpName, CommandType commandType = CommandType.Text) 195 { 196 //sqlDataReader不能用using 會關閉conn 致使不能獲取到返回值。注意:DataReader獲取值時必須保持鏈接狀態 197 SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_R, ConnString_RW); 198 DbCommandCommon cmd = new DbCommandCommon(DbType); 199 PreparCommand(conn.DbConnection, cmd.DbCommand, commandTextOrSpName, commandType); 200 return cmd.DbCommand.ExecuteReader(CommandBehavior.CloseConnection); 201 } 202 public static DbDataReader ExecuteReader(string commandTextOrSpName, CommandType commandType, IDictionary<string, object> dictionary) 203 { 204 //sqlDataReader不能用using 會關閉conn 致使不能獲取到返回值。注意:DataReader獲取值時必須保持鏈接狀態 205 SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_R, ConnString_RW); 206 DbCommandCommon cmd = new DbCommandCommon(DbType); 207 PreparCommand(conn.DbConnection, cmd.DbCommand, commandTextOrSpName, commandType, dictionary); 208 return cmd.DbCommand.ExecuteReader(CommandBehavior.CloseConnection); 209 } 210 #endregion 211 212 #region ExecuteDataTable 執行sql語句或者存儲過程,返回一個DataTable---DataTable 213 214 /** 215 * Update At 2017-3-2 14:58:45 216 * Add the ExecuteDataTable Method into Sql_Helper_DG 217 **/ 218 public static DataTable ExecuteDataTable(string commandTextOrSpName, CommandType commandType = CommandType.Text) 219 { 220 using (SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_R, ConnString_RW)) 221 { 222 using (DbCommandCommon cmd = new DbCommandCommon(DbType)) 223 { 224 PreparCommand(conn.DbConnection, cmd.DbCommand, commandTextOrSpName, commandType); 225 using (DbDataAdapterCommon da = new DbDataAdapterCommon(DbType, cmd.DbCommand)) 226 { 227 DataSet ds = new DataSet(); 228 da.Fill(ds); 229 if (ds.Tables.Count > 0) 230 { 231 return ds.Tables[0]; 232 } 233 return default(DataTable); 234 } 235 } 236 } 237 } 238 public static DataTable ExecuteDataTable(string commandTextOrSpName, CommandType commandType, IDictionary<string, object> dictionary) 239 { 240 using (SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_R, ConnString_RW)) 241 { 242 using (DbCommandCommon cmd = new DbCommandCommon(DbType)) 243 { 244 PreparCommand(conn.DbConnection, cmd.DbCommand, commandTextOrSpName, commandType, dictionary); 245 using (DbDataAdapterCommon da = new DbDataAdapterCommon(DbType, cmd.DbCommand)) 246 { 247 DataSet ds = new DataSet(); 248 da.Fill(ds); 249 if (ds.Tables.Count > 0) 250 { 251 return ds.Tables[0]; 252 } 253 return default(DataTable); 254 } 255 } 256 } 257 } 258 #endregion 259 260 #region ExecuteDataSet 執行sql語句或者存儲過程,返回一個DataSet---DataSet 261 public static DataSet ExecuteDataSet(string commandTextOrSpName, CommandType commandType = CommandType.Text) 262 { 263 using (SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_R, ConnString_RW)) 264 { 265 using (DbCommandCommon cmd = new DbCommandCommon(DbType)) 266 { 267 PreparCommand(conn.DbConnection, cmd.DbCommand, commandTextOrSpName, commandType); 268 using (DbDataAdapterCommon da = new DbDataAdapterCommon(DbType, cmd.DbCommand)) 269 { 270 DataSet ds = new DataSet(); 271 da.Fill(ds); 272 return ds; 273 } 274 } 275 } 276 } 277 public static DataSet ExecuteDataSet(string commandTextOrSpName, CommandType commandType, IDictionary<string, object> dictionary) 278 { 279 using (SqlConnection_RW conn = new SqlConnection_RW(DbType, ConnString_R, ConnString_RW)) 280 { 281 using (DbCommandCommon cmd = new DbCommandCommon(DbType)) 282 { 283 PreparCommand(conn.DbConnection, cmd.DbCommand, commandTextOrSpName, commandType, dictionary); 284 using (DbDataAdapterCommon da = new DbDataAdapterCommon(DbType, cmd.DbCommand)) 285 { 286 DataSet ds = new DataSet(); 287 da.Fill(ds); 288 return ds; 289 } 290 } 291 } 292 } 293 #endregion 294 295 #region ExecuteList Entity 執行sql語句或者存儲過程,返回一個List<T>---List<T> 296 public static List<Entity> ExecuteList<Entity>(string commandTextOrSpName, CommandType commandType = CommandType.Text) where Entity : class 297 { 298 return GetListFromDataSetV2<Entity>(ExecuteDataSet(commandTextOrSpName, commandType)); 299 } 300 public static List<Entity> ExecuteList<Entity>(string commandTextOrSpName, CommandType commandType, IDictionary<string, object> dictionary) where Entity : class 301 { 302 return GetListFromDataSetV2<Entity>(ExecuteDataSet(commandTextOrSpName, commandType, dictionary)); 303 } 304 #endregion 305 306 #region ExecuteEntity 執行sql語句或者存儲過程,返回一個Entity---Entity 307 public static Entity ExecuteEntity<Entity>(string commandTextOrSpName, CommandType commandType = CommandType.Text) where Entity : class 308 { 309 return GetEntityFromDataSetV2<Entity>(ExecuteDataSet(commandTextOrSpName, commandType)); 310 } 311 public static Entity ExecuteEntity<Entity>(string commandTextOrSpName, CommandType commandType, IDictionary<string, object> dictionary) where Entity : class 312 { 313 return GetEntityFromDataSetV2<Entity>(ExecuteDataSet(commandTextOrSpName, commandType, dictionary)); 314 } 315 #endregion 316 317 #region ---PreparCommand 構建一個通用的command對象供內部方法進行調用--- 318 private static void PreparCommand(DbConnection conn, DbCommand cmd, string commandTextOrSpName, CommandType commandType, IDictionary<string, object> dictionary = null) 319 { 320 //打開鏈接 321 if (conn.State != ConnectionState.Open) 322 { 323 conn.Open(); 324 } 325 326 //設置SqlCommand對象的屬性值 327 cmd.Connection = conn; 328 cmd.CommandType = commandType; 329 cmd.CommandText = commandTextOrSpName; 330 cmd.CommandTimeout = 60; 331 332 if (dictionary != null) 333 { 334 cmd.Parameters.Clear(); 335 DbParameter[] parameters; 336 switch (conn) 337 { 338 case SqlConnection s: 339 parameters = new SqlParameter[dictionary.Count]; 340 break; 341 case MySqlConnection m: 342 parameters = new MySqlParameter[dictionary.Count]; 343 break; 344 //case OracleConnection o: 345 //parameters = new OracleParameter[dictionary.Count]; 346 //break; 347 default: 348 parameters = new SqlParameter[dictionary.Count]; 349 break; 350 } 351 352 string[] keyArray = dictionary.Keys.ToArray(); 353 object[] valueArray = dictionary.Values.ToArray(); 354 355 for (int i = 0; i < parameters.Length; i++) 356 { 357 switch (conn) 358 { 359 case SqlConnection s: 360 parameters[i] = new SqlParameter(keyArray[i], valueArray[i]); 361 break; 362 case MySqlConnection m: 363 parameters[i] = new MySqlParameter(keyArray[i], valueArray[i]); 364 break; 365 //case OracleConnection o: 366 // parameters[i] = new OracleParameter(keyArray[i], valueArray[i]); 367 // break; 368 default: 369 parameters[i] = new SqlParameter(keyArray[i], valueArray[i]); 370 break; 371 } 372 } 373 cmd.Parameters.AddRange(parameters); 374 } 375 } 376 #endregion 377 378 #region 經過Model反射返回結果集 Model爲 Entity 泛型變量的真實類型---反射返回結果集 379 public static List<Entity> GetListFromDataSet<Entity>(DataSet ds) where Entity : class 380 { 381 List<Entity> list = new List<Entity>();//實例化一個list對象 382 PropertyInfo[] propertyInfos = typeof(Entity).GetProperties(); //獲取T對象的全部公共屬性 383 384 DataTable dt = ds.Tables[0];//獲取到ds的dt 385 if (dt.Rows.Count > 0) 386 { 387 //判斷讀取的行是否>0 即數據庫數據已被讀取 388 foreach (DataRow row in dt.Rows) 389 { 390 Entity model1 = System.Activator.CreateInstance<Entity>();//實例化一個對象,便於往list裏填充數據 391 foreach (PropertyInfo propertyInfo in propertyInfos) 392 { 393 try 394 { 395 //遍歷模型裏全部的字段 396 if (row[propertyInfo.Name] != System.DBNull.Value) 397 { 398 //判斷值是否爲空,若是空賦值爲null見else 399 if (propertyInfo.PropertyType.IsGenericType && propertyInfo.PropertyType.GetGenericTypeDefinition().Equals(typeof(Nullable<>))) 400 { 401 //若是convertsionType爲nullable類,聲明一個NullableConverter類,該類提供從Nullable類到基礎基元類型的轉換 402 NullableConverter nullableConverter = new NullableConverter(propertyInfo.PropertyType); 403 //將convertsionType轉換爲nullable對的基礎基元類型 404 propertyInfo.SetValue(model1, Convert.ChangeType(row[propertyInfo.Name], nullableConverter.UnderlyingType), null); 405 } 406 else 407 { 408 propertyInfo.SetValue(model1, Convert.ChangeType(row[propertyInfo.Name], propertyInfo.PropertyType), null); 409 } 410 } 411 else 412 { 413 propertyInfo.SetValue(model1, null, null);//若是數據庫的值爲空,則賦值爲null 414 } 415 } 416 catch (Exception) 417 { 418 propertyInfo.SetValue(model1, null, null);//若是數據庫的值爲空,則賦值爲null 419 } 420 } 421 list.Add(model1);//將對象填充到list中 422 } 423 } 424 return list; 425 } 426 public static List<Entity> GetListFromDataSetV2<Entity>(DataSet ds) where Entity : class 427 { 428 List<Entity> list = new List<Entity>(); 429 DataTable dt = ds.Tables[0]; 430 if (dt.Rows.Count > 0) 431 { 432 foreach (DataRow row in dt.Rows) 433 { 434 Entity entity = FillAdapter<Entity>.AutoFill(row); 435 list.Add(entity); 436 } 437 } 438 return list; 439 } 440 public static Entity GetEntityFromDataReader<Entity>(DbDataReader reader) where Entity : class 441 { 442 Entity model = System.Activator.CreateInstance<Entity>(); //實例化一個T類型對象 443 PropertyInfo[] propertyInfos = model.GetType().GetProperties(); //獲取T對象的全部公共屬性 444 using (reader) 445 { 446 if (reader.Read()) 447 { 448 foreach (PropertyInfo propertyInfo in propertyInfos) 449 { 450 //遍歷模型裏全部的字段 451 if (reader[propertyInfo.Name] != System.DBNull.Value) 452 { 453 //判斷值是否爲空,若是空賦值爲null見else 454 if (propertyInfo.PropertyType.IsGenericType && propertyInfo.PropertyType.GetGenericTypeDefinition().Equals(typeof(Nullable<>))) 455 { 456 //若是convertsionType爲nullable類,聲明一個NullableConverter類,該類提供從Nullable類到基礎基元類型的轉換 457 NullableConverter nullableConverter = new NullableConverter(propertyInfo.PropertyType); 458 //將convertsionType轉換爲nullable對的基礎基元類型 459 propertyInfo.SetValue(model, Convert.ChangeType(reader[propertyInfo.Name], nullableConverter.UnderlyingType), null); 460 } 461 else 462 { 463 propertyInfo.SetValue(model, Convert.ChangeType(reader[propertyInfo.Name], propertyInfo.PropertyType), null); 464 } 465 } 466 else 467 { 468 propertyInfo.SetValue(model, null, null);//若是數據庫的值爲空,則賦值爲null 469 } 470 } 471 return model;//返回T類型的賦值後的對象 model 472 } 473 } 474 return default(Entity);//返回引用類型和值類型的默認值0或null 475 } 476 public static Entity GetEntityFromDataSet<Entity>(DataSet ds) where Entity : class 477 { 478 return GetListFromDataSet<Entity>(ds).FirstOrDefault(); 479 } 480 public static Entity GetEntityFromDataSetV2<Entity>(DataSet ds) where Entity : class 481 { 482 DataTable dt = ds.Tables[0];// 獲取到ds的dt 483 if (dt.Rows.Count > 0) 484 { 485 return FillAdapter<Entity>.AutoFill(dt.Rows[0]); 486 } 487 return default(Entity); 488 } 489 #endregion 490 } 491 492 /// <summary> 493 /// Auto Fill Adapter 494 /// </summary> 495 /// <typeparam name="Entity"></typeparam> 496 internal class FillAdapter<Entity> 497 { 498 private static readonly Func<DataRow, Entity> funcCache = GetFactory(); 499 public static Entity AutoFill(DataRow row) 500 { 501 return funcCache(row); 502 } 503 private static Func<DataRow, Entity> GetFactory() 504 { 505 var type = typeof(Entity); 506 var rowType = typeof(DataRow); 507 var rowDeclare = Expression.Parameter(rowType, "row"); 508 var instanceDeclare = Expression.Parameter(type, "t"); 509 //new Student() 510 var newExpression = Expression.New(type); 511 //(t = new Student()) 512 var instanceExpression = Expression.Assign(instanceDeclare, newExpression); 513 //row == null 514 var nullEqualExpression = Expression.NotEqual(rowDeclare, Expression.Constant(null)); 515 var containsMethod = typeof(DataColumnCollection).GetMethod("Contains"); 516 var indexerMethod = rowType.GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(string) }, new[] { new ParameterModifier(1) }); 517 var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public); 518 var setExpressions = new List<Expression>(); 519 //row.Table.Columns 520 var columns = Expression.Property(Expression.Property(rowDeclare, "Table"), "Columns"); 521 foreach (var propertyInfo in properties) 522 { 523 if (propertyInfo.CanWrite) 524 { 525 //Id,Id is a property of Entity 526 var propertyName = Expression.Constant(propertyInfo.Name, typeof(string)); 527 //row.Table.Columns.Contains("Id") 528 var checkIfContainsColumn = Expression.Call(columns, containsMethod, propertyName); 529 //t.Id 530 var propertyExpression = Expression.Property(instanceDeclare, propertyInfo); 531 //row.get_Item("Id") 532 var value = Expression.Call(rowDeclare, indexerMethod, propertyName); 533 //t.Id = Convert(row.get_Item("Id"), Int32) 534 var propertyAssign = Expression.Assign(propertyExpression, Expression.Convert(value, propertyInfo.PropertyType)); 535 //t.Id = default(Int32) 536 var propertyAssignDefault = Expression.Assign(propertyExpression, Expression.Default(propertyInfo.PropertyType)); 537 //if(row.Table.Columns.Contains("Id")&&!value.Equals(DBNull.Value<>)) {t.Id = Convert(row.get_Item("Id"), Int32)}else{t.Id = default(Int32)} 538 var checkRowNull = Expression.IfThenElse(Expression.AndAlso(checkIfContainsColumn, Expression.NotEqual(value, Expression.Constant(System.DBNull.Value))), propertyAssign, propertyAssignDefault); 539 //var checkContains = Expression.IfThen(checkIfContainsColumn, propertyAssign); 540 setExpressions.Add(checkRowNull); 541 } 542 } 543 var checkIfRowIsNull = Expression.IfThen(nullEqualExpression, Expression.Block(setExpressions)); 544 var body = Expression.Block(new[] { instanceDeclare }, instanceExpression, checkIfRowIsNull, instanceDeclare); 545 return Expression.Lambda<Func<DataRow, Entity>>(body, rowDeclare).Compile(); 546 } 547 } 548 549 /** 550 * author:qixiao 551 * time:2017-9-18 18:02:23 552 * description:safe create sqlconnection support 553 * */ 554 internal class SqlConnection_RW : IDisposable 555 { 556 /// <summary> 557 /// SqlConnection 558 /// </summary> 559 public DbConnection DbConnection { get; set; } 560 561 public SqlConnection_RW(DataBaseType dataBaseType, string ConnString_RW) 562 { 563 this.DbConnection = GetDbConnection(dataBaseType, ConnString_RW); 564 } 565 /** 566 * if read db disabled,switchover to read write db immediately 567 * */ 568 public SqlConnection_RW(DataBaseType dataBaseType, string ConnString_R, string ConnString_RW) 569 { 570 try 571 { 572 this.DbConnection = GetDbConnection(dataBaseType, ConnString_R); 573 } 574 catch (Exception) 575 { 576 this.DbConnection = GetDbConnection(dataBaseType, ConnString_RW); 577 } 578 } 579 580 /// <summary> 581 /// GetDataBase ConnectionString by database type and connection string -- private use 582 /// </summary> 583 /// <param name="dataBaseType"></param> 584 /// <param name="ConnString"></param> 585 /// <returns></returns> 586 private DbConnection GetDbConnection(DataBaseType dataBaseType, string ConnString) 587 { 588 switch (dataBaseType) 589 { 590 case DataBaseType.SqlServer: 591 return new SqlConnection(ConnString); 592 case DataBaseType.MySql: 593 return new MySqlConnection(ConnString); 594 case DataBaseType.Oracle: 595 //return new OracleConnection(ConnString); 596 default: 597 return new SqlConnection(ConnString); 598 } 599 } 600 /// <summary> 601 /// Must Close Connection after use 602 /// </summary> 603 public void Dispose() 604 { 605 if (this.DbConnection != null) 606 { 607 this.DbConnection.Dispose(); 608 } 609 } 610 } 611 /// <summary> 612 /// Common sqlcommand 613 /// </summary> 614 internal class DbCommandCommon : IDisposable 615 { 616 /// <summary> 617 /// common dbcommand 618 /// </summary> 619 public DbCommand DbCommand { get; set; } 620 public DbCommandCommon(DataBaseType dataBaseType) 621 { 622 this.DbCommand = GetDbCommand(dataBaseType); 623 } 624 625 /// <summary> 626 /// Get DbCommand select database type 627 /// </summary> 628 /// <param name="dataBaseType"></param> 629 /// <returns></returns> 630 private DbCommand GetDbCommand(DataBaseType dataBaseType) 631 { 632 switch (dataBaseType) 633 { 634 case DataBaseType.SqlServer: 635 return new SqlCommand(); 636 case DataBaseType.MySql: 637 return new MySqlCommand(); 638 case DataBaseType.Oracle: 639 //return new OracleCommand(); 640 default: 641 return new SqlCommand(); 642 } 643 } 644 /// <summary> 645 /// must dispose after use 646 /// </summary> 647 public void Dispose() 648 { 649 if (this.DbCommand != null) 650 { 651 this.DbCommand.Dispose(); 652 } 653 } 654 } 655 /// <summary> 656 /// DbDataAdapterCommon 657 /// </summary> 658 internal class DbDataAdapterCommon : DbDataAdapter, IDisposable 659 { 660 public DbDataAdapter DbDataAdapter { get; set; } 661 public DbDataAdapterCommon(DataBaseType dataBaseType, DbCommand dbCommand) 662 { 663 //get dbAdapter 664 this.DbDataAdapter = GetDbAdapter(dataBaseType, dbCommand); 665 //provid select command 666 this.SelectCommand = dbCommand; 667 } 668 private DbDataAdapter GetDbAdapter(DataBaseType dataBaseType, DbCommand dbCommand) 669 { 670 switch (dataBaseType) 671 { 672 case DataBaseType.SqlServer: 673 return new SqlDataAdapter(); 674 case DataBaseType.MySql: 675 return new MySqlDataAdapter(); 676 case DataBaseType.Oracle: 677 //return new OracleDataAdapter(); 678 default: 679 return new SqlDataAdapter(); 680 } 681 } 682 /// <summary> 683 /// must dispose after use 684 /// </summary> 685 public new void Dispose() 686 { 687 if (this.DbDataAdapter != null) 688 { 689 this.DbDataAdapter.Dispose(); 690 } 691 } 692 } 693 694 /// <summary> 695 /// 用於批量操做的批量操做實體 696 /// </summary> 697 public class BatchExecuteModel 698 { 699 /// <summary> 700 /// 執行的語句或者存儲過程名稱 701 /// </summary> 702 public string CommandTextOrSpName { get; set; } 703 /// <summary> 704 /// 執行類別,默認執行sql語句 705 /// </summary> 706 public CommandType CommandType { get; set; } = CommandType.Text; 707 /// <summary> 708 /// 執行語句的參數字典 709 /// </summary> 710 public IDictionary<string, object> ParamsDic { get; set; } 711 } 712 }
表映射描述器定義了一系列對實體的標籤,以描述該實體和數據庫以及數據庫表之間的映射關係。除此以外還擴展了對數據庫表緩存的描述。
實體類+條件 Sql語句轉化過程:
Sql語句轉化器的功能爲將友好查詢Api傳遞的Lambda表達式語句轉化成對應功能的Sql條件語句,以及對應不一樣數據庫生成針對數據庫的Sql語句。
數據庫操做上下文做爲所有數據操做的載體,在DbContext的基礎上分離出SqlDbContext和NoSqlDbContext,分別支持關係型數據庫和非關係型數據庫。在關係型數據庫上下文基礎上又能夠衍生出各類類型的關係型數據庫上下文。該設計保證了組件的水平擴容的能力。
上下文除了維繫各類數據庫操做的支持之外,還擴展出了緩存組件的強力支持,能夠在上下文中設置當前會話的緩存配置項。
緩存核心拓撲:
緩存核心處理流程:
詳細緩存策略:
緩存的處理邏輯比較細化,組件的緩存統一由緩存管理核心處理,緩存核心分別調用一級緩存和二級緩存處理對象緩存。緩存處理的步驟以下:
1.判斷是否開啓了二級緩存,若是未開啓,跳過。
2.若是開啓了二級緩存,檢查是否存在二級緩存,若是不存在,則判斷是否對實體開啓表緩存,若是開啓,則開啓後臺線程掃描表,存儲表數據爲二級緩存。
3.判斷是否存在一級緩存,若是存在,直接返回一級緩存的結果集。
4.若是一級緩存不存在,則執行查詢命令,並寫入一級緩存。
當二級緩存存在時:
1.拿出二級緩存並對二級緩存執行增刪改查操做,並執行對應的增刪改操做持久化過程。
2.後續全部查詢優先從二級緩存中獲取。
若是二級緩存開啓狀態,執行增刪改命令的同時,會同步維護持久化數據和二級緩存數據。
備註:
二級緩存是針對某表進行的策略,不是針對全部數據庫表的,若是數據庫表數量太大,則不建議對該表開啓二級緩存,以避免耗費大量的內存資源。
Nuget包源搜索 SevenTiny.Bantina.Bankinate 安裝
新建一個數據庫上下文類(對應數據庫名稱,相似EntityFramework的上下文類)
若是和庫名不一致,則使用DataBase標籤進行特殊映射。並在上下文類傳遞連接字符串和一級緩存和二級緩存的開啓配置(默認都關閉)。
這裏測試使用SqlServer數據庫,所以繼承了SqlServerDbContext,若是是其餘數據庫,則繼承對應的數據庫。
根據數據庫表建立實體類(實體類可使用代碼生成器自動生成,生成器迭代升級中,有需求能夠聯繫博主)。
這裏提供了一個Student類(表),並使用 TableCaching 標籤指定了該表二級緩存的開啓(重載能夠配置該表二級緩存時間)。
Id爲主鍵,而且是自增列。
Api列表:
SevenTiny.Bantina.Bankinate ORM框架提供了一系列標準的非標準的數據查詢api,api基於Lambda Expression寫法,以便習慣了.Net平臺Linq的人羣很快上手,無學習成本。
1 /********************************************************* 2 * CopyRight: 7TINY CODE BUILDER. 3 * Version: 5.0.0 4 * Author: 7tiny 5 * Address: Earth 6 * Create: 2018-04-19 23:58:08 7 * Modify: 2018-04-19 23:58:08 8 * E-mail: dong@7tiny.com | sevenTiny@foxmail.com 9 * GitHub: https://github.com/sevenTiny 10 * Personal web site: http://www.7tiny.com 11 * Technical WebSit: http://www.cnblogs.com/7tiny/ 12 * Description: 13 * Thx , Best Regards ~ 14 *********************************************************/ 15 using System; 16 using System.Collections.Generic; 17 using System.Data; 18 using System.Linq.Expressions; 19 20 namespace SevenTiny.Bantina.Bankinate 21 { 22 /// <summary> 23 /// 通用的Api接口,具有基礎的操做,緩存 24 /// </summary> 25 public interface IDbContext : IDisposable, IBaseOerate, ICacheable 26 { 27 } 28 29 /// <summary> 30 /// 基礎操做Api 31 /// </summary> 32 public interface IBaseOerate 33 { 34 void Add<TEntity>(TEntity entity) where TEntity : class; 35 void AddAsync<TEntity>(TEntity entity) where TEntity : class; 36 void Add<TEntity>(IEnumerable<TEntity> entities) where TEntity : class; 37 void AddAsync<TEntity>(IEnumerable<TEntity> entities) where TEntity : class; 38 39 void Update<TEntity>(Expression<Func<TEntity, bool>> filter, TEntity entity) where TEntity : class; 40 void UpdateAsync<TEntity>(Expression<Func<TEntity, bool>> filter, TEntity entity) where TEntity : class; 41 42 void Delete<TEntity>(Expression<Func<TEntity, bool>> filter) where TEntity : class; 43 void DeleteAsync<TEntity>(Expression<Func<TEntity, bool>> filter) where TEntity : class; 44 45 bool QueryExist<TEntity>(Expression<Func<TEntity, bool>> filter) where TEntity : class; 46 int QueryCount<TEntity>(Expression<Func<TEntity, bool>> filter) where TEntity : class; 47 TEntity QueryOne<TEntity>(Expression<Func<TEntity, bool>> filter) where TEntity : class; 48 List<TEntity> QueryList<TEntity>(Expression<Func<TEntity, bool>> filter) where TEntity : class; 49 } 50 51 /// <summary> 52 /// 執行sql語句擴展Api 53 /// </summary> 54 public interface IExecuteSqlOperate 55 { 56 void ExecuteSql(string sqlStatement, IDictionary<string, object> parms = null); 57 void ExecuteSqlAsync(string sqlStatement, IDictionary<string, object> parms = null); 58 DataSet ExecuteQueryDataSetSql(string sqlStatement, IDictionary<string, object> parms = null); 59 object ExecuteQueryOneDataSql(string sqlStatement, IDictionary<string, object> parms = null); 60 TEntity ExecuteQueryOneSql<TEntity>(string sqlStatement, IDictionary<string, object> parms = null) where TEntity : class; 61 List<TEntity> ExecuteQueryListSql<TEntity>(string sqlStatement, IDictionary<string, object> parms = null) where TEntity : class; 62 } 63 64 /// <summary> 65 /// 分頁查詢擴展Api 66 /// </summary> 67 public interface IQueryPagingOperate 68 { 69 List<TEntity> QueryListPaging<TEntity>(int pageIndex, int pageSize, Expression<Func<TEntity, object>> orderBy, Expression<Func<TEntity, bool>> filter, bool isDESC = false) where TEntity : class; 70 List<TEntity> QueryListPaging<TEntity>(int pageIndex, int pageSize, Expression<Func<TEntity, object>> orderBy, Expression<Func<TEntity, bool>> filter, out int count, bool isDESC = false) where TEntity : class; 71 } 72 73 /// <summary> 74 /// 緩存接口,實現該接口的類必須具有ORM緩存 75 /// </summary> 76 public interface ICacheable 77 { 78 } 79 }
查詢所有(能夠根據使用場景組裝lambda表達式):
新增一條數據:
修改數據:
刪除數據:
緩存性能測試的單元測試代碼:
1 [Theory] 2 [InlineData(100)] 3 [Trait("desc", "無緩存測試")] 4 public void QueryListWithNoCacheLevel1(int times) 5 { 6 int fromCacheTimes = 0; 7 var timeSpan = StopwatchHelper.Caculate(times, () => 8 { 9 using (var db = new SqlServerTestDbContext()) 10 { 11 var students = db.QueryList<Student>(t => true); 12 if (db.IsFromCache) 13 { 14 fromCacheTimes++; 15 } 16 } 17 }); 18 Trace.WriteLine($"執行查詢{times}次耗時:{timeSpan.TotalMilliseconds},有{fromCacheTimes}次從緩存中獲取,有{times - fromCacheTimes}次從數據庫獲取"); 19 //執行查詢100次耗時:6576.8009 20 } 21 22 23 [Theory] 24 [InlineData(10000)] 25 [Trait("desc", "一級緩存測試")] 26 [Trait("desc", "測試該用例,請將一級緩存(QueryCache)打開")] 27 public void QueryListWithCacheLevel1(int times) 28 { 29 int fromCacheTimes = 0; 30 var timeSpan = StopwatchHelper.Caculate(times, () => 31 { 32 using (var db = new SqlServerTestDbContext()) 33 { 34 var students = db.QueryList<Student>(t => true); 35 if (db.IsFromCache) 36 { 37 fromCacheTimes++; 38 } 39 } 40 }); 41 Trace.WriteLine($"執行查詢{times}次耗時:{timeSpan.TotalMilliseconds},有{fromCacheTimes}次從緩存中獲取,有{times - fromCacheTimes}次從數據庫獲取"); 42 //執行查詢10000次耗時:1598.2349 43 } 44 45 [Theory] 46 [InlineData(10000)] 47 [Trait("desc", "二級緩存測試")] 48 [Trait("desc", "測試該用例,請將二級緩存(TableCache)打開,並在對應表的實體上添加緩存標籤")] 49 public void QueryListWithCacheLevel2(int times) 50 { 51 int fromCacheTimes = 0; 52 var timeSpan = StopwatchHelper.Caculate(times, () => 53 { 54 using (var db = new SqlServerTestDbContext()) 55 { 56 var students = db.QueryList<Student>(t => true); 57 if (db.IsFromCache) 58 { 59 fromCacheTimes++; 60 } 61 } 62 }); 63 Trace.WriteLine($"執行查詢{times}次耗時:{timeSpan.TotalMilliseconds},有{fromCacheTimes}次從緩存中獲取,有{times - fromCacheTimes}次從數據庫獲取"); 64 //執行查詢10000次耗時:5846.0249,有9999次從緩存中獲取,有1次從數據庫獲取。 65 //經過更爲詳細的打點得知,共有兩次從數據庫獲取值。第一次直接按條件查詢存在一級緩存,後臺線程掃描表存在了二級緩存。 66 //緩存打點結果:二級緩存沒有掃描完畢從一級緩存獲取數據,二級緩存掃描完畢則都從二級緩存裏面獲取數據 67 } 68 69 [Theory] 70 [InlineData(1000)] 71 [Trait("desc", "開啓二級緩存增刪改查測試")] 72 [Trait("desc", "測試該用例,請將二級緩存(TableCache)打開,並在對應表的實體上添加緩存標籤")] 73 public void AddUpdateDeleteQueryCacheLevel2(int times) 74 { 75 int fromCacheTimes = 0; 76 var timeSpan = StopwatchHelper.Caculate(times, () => 77 { 78 using (var db = new SqlServerTestDbContext()) 79 { 80 //查詢單個 81 var stu = db.QueryOne<Student>(t => t.Id == 1); 82 //修改單個屬性 83 stu.Name = "test11-1"; 84 db.Update<Student>(t => t.Id == 1, stu); 85 86 var students = db.QueryList<Student>(t => true); 87 if (db.IsFromCache) 88 { 89 fromCacheTimes++; 90 } 91 } 92 }); 93 Trace.WriteLine($"執行查詢{times}次耗時:{timeSpan.TotalMilliseconds},有{fromCacheTimes}次從緩存中獲取,有{times - fromCacheTimes}次從數據庫獲取"); 94 //執行查詢1000次耗時:19102.6441,有1000次從緩存中獲取,有0次從數據庫獲取 95 //事實上,第一次查詢單條的時候已經從數據庫掃描並放在了緩存中,後續都是對二級緩存的操做以及二級緩存中查詢 96 }
執行查詢100次耗時:6576.8009 ms
執行查詢10000次耗時:1598.2349 ms
執行查詢10000次耗時:5846.0249
實際上,二級緩存開啓之後,最初的查詢會走一級緩存。待二級緩存對錶掃描結束之後,後續查詢將維護二級緩存數據,再也不訪問數據庫表。
1.查詢api對特定列查詢列的支持(性能提高)
2.對一對多關係的主外鍵查詢支持
3.更多種類數據庫的支持
4.打點日誌的支持
經過本文的一系列分析,不知各位看官對ORM框架的設計思路有沒有一個總體的認識。若是在分析結束尚未充分理解設計思路,那麼簡單粗暴直接上GitHub克隆源碼看唄~
項目基於.NetStandard 2.0構建,所以支持.NetCore2.0以上以及.NetFramework4.6.1以上。對更低版本不兼容。
雖然原理分析比較冗長,可是代碼結構仍是很是清晰的,有任何建議,還望不吝賜教,對代碼質量的指教很是期待 ^_^
附源碼地址: https://github.com/sevenTiny/SevenTiny.Bantina.Bankinate