天天看點

輕量級ORM架構 Bankinate

【前言】

  前面講過ORM的前世今生,對ORM架構不了解的朋友可以參考博文:https://www.cnblogs.com/7tiny/p/9551754.html

  今天,我們主要通過設計一款輕量級的ORM架構來介紹:"如何實作一個ORM架構"

  文末給出了GitHub源碼位址~

【基本要素】

  既然是ORM架構,那麼必不可或缺的三點:

  1.Sql語句的自動生成

  2.資料結果集自動映射到類型實體

  3.多資料庫的支援

  甚至可以在此三點的基礎上擴充出更多的:

  1.緩存處理

  2.Api的封裝

  3.日志系統

  基于以上幾點,那麼我們逐漸開始我們的設計:

  為了功能抽象和細化的職責劃分,我們将各個功能點拆分成為各個元件,靈活進行裝配。

   

輕量級ORM架構 Bankinate

  資料存儲核心:調用底層資料庫驅動執行Sql語句,将資料持久化

  表映射描述器:描述表和實體的映射關系

  Sql語句轉化器:将封裝的資料操作Api轉化成對應資料庫的Sql語句

  資料操作上下文:使用者資料操作資訊傳遞,包裝,資料庫連接配接管理等,緩存核心配置資訊的承載

  緩存核心:使用者ORM架構的緩存支援(一級緩存/二級緩存)

【實作細節】

  我們抽象出核心功能元件後,對各個功能元件進行詳細設計:

  資料存儲核心:

輕量級ORM架構 Bankinate

  資料存儲核心主要包括對多種資料庫驅動的封裝調用,讀寫分離的簡單政策,查詢資料集合與強類型實體的映射(性能優化點,目前采用Expression 表達式樹緩存委托方式)。

  這裡以封裝的支援多種關系型資料庫的DbHelper形式呈現

輕量級ORM架構 Bankinate
輕量級ORM架構 Bankinate
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: [email protected] | [email protected] 
  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 }      

DbHelper

  表映射描述器:

  

輕量級ORM架構 Bankinate

  表映射描述器定義了一系列對實體的标簽,以描述該實體和資料庫以及資料庫表之間的映射關系。除此之外還擴充了對資料庫表緩存的描述。

  Sql語句轉化器:

輕量級ORM架構 Bankinate

  實體類+條件 Sql語句轉化過程:

輕量級ORM架構 Bankinate

  Sql語句轉化器的功能為将友好查詢Api傳遞的Lambda表達式語句轉化成對應功能的Sql條件語句,以及對應不同資料庫生成針對資料庫的Sql語句。

  資料操作上下文:

輕量級ORM架構 Bankinate

  資料庫操作上下文作為全部資料操作的載體,在DbContext的基礎上分離出SqlDbContext和NoSqlDbContext,分别支援關系型資料庫和非關系型資料庫。在關系型資料庫上下文基礎上又可以衍生出各種類型的關系型資料庫上下文。該設計保證了元件的水準擴容的能力。

  上下文除了維系各種資料庫操作的支援以外,還擴充出了緩存元件的強力支援,可以在上下文中設定目前會話的緩存配置項。

  緩存核心:

  緩存核心拓撲:

輕量級ORM架構 Bankinate

  緩存核心處理流程:

輕量級ORM架構 Bankinate

  詳細緩存政策:

輕量級ORM架構 Bankinate
輕量級ORM架構 Bankinate

  緩存的處理邏輯比較細化,元件的緩存統一由緩存管理核心處理,緩存核心分别調用一級緩存和二級緩存處理對象緩存。緩存處理的步驟如下:

  1.判斷是否開啟了二級緩存,如果未開啟,跳過。

  2.如果開啟了二級緩存,檢查是否存在二級緩存,如果不存在,則判斷是否對實體開啟表緩存,如果開啟,則開啟背景線程掃描表,存儲表資料為二級緩存。

  3.判斷是否存在一級緩存,如果存在,直接傳回一級緩存的結果集。

  4.如果一級緩存不存在,則執行查詢指令,并寫入一級緩存。

  當二級緩存存在時:

  1.拿出二級緩存并對二級緩存執行增删改查操作,并執行對應的增删改操作持久化過程。

  2.後續所有查詢優先從二級緩存中擷取。

  如果二級緩存開啟狀态,執行增删改指令的同時,會同步維護持久化資料和二級緩存資料。

  備注:

  二級緩存是針對某表進行的政策,不是針對所有資料庫表的,如果資料庫表數量太大,則不建議對該表開啟二級緩存,以免耗費大量的記憶體資源。

【SevenTiny.Bantina.Bankinate ORM架構的使用】

  Nuget包源搜尋 SevenTiny.Bantina.Bankinate 安裝

輕量級ORM架構 Bankinate

  建立一個資料庫上下文類(對應資料庫名稱,類似EntityFramework的上下文類)

  如果和庫名不一緻,則使用DataBase标簽進行特殊映射。并在上下文類傳遞連結字元串和一級緩存和二級緩存的開啟配置(預設都關閉)。

 

輕量級ORM架構 Bankinate

  這裡測試使用SqlServer資料庫,是以繼承了SqlServerDbContext,如果是其他資料庫,則繼承對應的資料庫。

  根據資料庫表建立實體類(實體類可以使用代碼生成器自動生成,生成器疊代更新中,有需求可以聯系部落客)。

輕量級ORM架構 Bankinate

  這裡提供了一個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: [email protected] | [email protected] 
 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表達式):

輕量級ORM架構 Bankinate

   新增一條資料:

輕量級ORM架構 Bankinate

  修改資料:

輕量級ORM架構 Bankinate

  删除資料:

輕量級ORM架構 Bankinate

【架構緩存性能測試】

  緩存性能測試的單元測試代碼:

輕量級ORM架構 Bankinate
輕量級ORM架構 Bankinate
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

【部落客聲明】

本文為七小站主原創作品,轉載請注明出處:http://www.cnblogs.com/7tiny/ 且在文章頁面明顯位置給出原文連結。

作者資訊(詳情):

QiXiao_柒小(東)

Software Development

北京市海澱區 Haidian Area Beijing 100089,P.R.China

郵箱Email : [email protected]  

輕量級ORM架構 Bankinate

網址Http: http://www.7tiny.com

QQ:1124999434 , WeChat: wd8622088 (盡量加微信)

(專好結交天下英雄好漢,可聊天,可談技,可約飯,可..嗯,原則是要有的~)

更多聯系方式點我哦~

Best Regard ~

c#