輕量級ORM框架 Bankinate

【前言】

  前面講過ORM的前世此生,對ORM框架不瞭解的朋友能夠參考博文:http://www.javashuo.com/article/p-oupygolv-eq.htmlhtml

  今天,咱們主要經過設計一款輕量級的ORM框架來介紹:"如何實現一個ORM框架"mysql

  文末給出了GitHub源碼地址~git

【基本要素】

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

  1.Sql語句的自動生成

  2.數據結果集自動映射到類型實體

  3.多數據庫的支持

  甚至能夠在此三點的基礎上擴展出更多的:web

  1.緩存處理

  2.Api的封裝

  3.日誌系統

  基於以上幾點,那麼咱們逐步開始咱們的設計: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 }
DbHelper

  表映射描述器:

  

 

  表映射描述器定義了一系列對實體的標籤,以描述該實體和數據庫以及數據庫表之間的映射關係。除此以外還擴展了對數據庫表緩存的描述。

  Sql語句轉化器:

  

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

  

  Sql語句轉化器的功能爲將友好查詢Api傳遞的Lambda表達式語句轉化成對應功能的Sql條件語句,以及對應不一樣數據庫生成針對數據庫的Sql語句。

  數據操做上下文:

  

  數據庫操做上下文做爲所有數據操做的載體,在DbContext的基礎上分離出SqlDbContext和NoSqlDbContext,分別支持關係型數據庫和非關係型數據庫。在關係型數據庫上下文基礎上又能夠衍生出各類類型的關係型數據庫上下文。該設計保證了組件的水平擴容的能力。

  上下文除了維繫各類數據庫操做的支持之外,還擴展出了緩存組件的強力支持,能夠在上下文中設置當前會話的緩存配置項。

  緩存核心:

  緩存核心拓撲:

  

  緩存核心處理流程:

  

  詳細緩存策略:

  

  

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

  1.判斷是否開啓了二級緩存,若是未開啓,跳過。

  2.若是開啓了二級緩存,檢查是否存在二級緩存,若是不存在,則判斷是否對實體開啓表緩存,若是開啓,則開啓後臺線程掃描表,存儲表數據爲二級緩存。

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

  4.若是一級緩存不存在,則執行查詢命令,並寫入一級緩存。

  當二級緩存存在時

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

  2.後續全部查詢優先從二級緩存中獲取。

  若是二級緩存開啓狀態,執行增刪改命令的同時,會同步維護持久化數據和二級緩存數據。

  備註

  二級緩存是針對某表進行的策略,不是針對全部數據庫表的,若是數據庫表數量太大,則不建議對該表開啓二級緩存,以避免耗費大量的內存資源。

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

  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

相關文章
相關標籤/搜索