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