轻量级ORM框架 Bankinate

【前言】

  前面讲过ORM的前世今生,对ORM框架不了解的朋友可以参考博文:https://www.cnblogs.com/7tiny/p/9551754.html

  今天,我们主要通过设计一款轻量级的ORM框架来介绍:"如何实现一个ORM框架"

  文末给出了GitHub源码地址~

【基本要素】

  既然是ORM框架,那么必不可或缺的三点:

  1.Sql语句的自动生成

  2.数据结果集自动映射到类型实体

  3.多数据库的支持

  甚至可以在此三点的基础上扩展出更多的:

  1.缓存处理

  2.Api的封装

  3.日志系统

  基于以上几点,那么我们逐步开始我们的设计:

  为了功能抽象和细化的职责划分,我们将各个功能点拆分成为各个组件,灵活进行装配。

   

  数据存储核心:调用底层数据库驱动执行Sql语句,将数据持久化

  表映射描述器:描述表和实体的映射关系

  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

posted @ 2018-09-02 23:57 7tiny 阅读(...) 评论(...) 编辑 收藏