轻量级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 }
表映射描述器:
表映射描述器定义了一系列对实体的标签,以描述该实体和数据库以及数据库表之间的映射关系。除此之外还扩展了对数据库表缓存的描述。
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
【博主声明】
作者:
7tiny
Software Development
北京市海淀区 Haidian Area Beijing 100089,P.R.China
郵箱Email : seventiny@foxmail.com
網址Http: http://www.7tiny.com
WeChat: seven-tiny
更多联系方式点我哦~
Best Regard ~