手擼ORM淺談ORM框架之Add篇


快速傳送

手擼ORM淺談ORM框架之基礎篇

手擼ORM淺談ORM框架之Add篇

手擼ORM淺談ORM框架之Update篇

手擼ORM淺談ORM框架之Delete篇

手擼ORM淺談ORM框架之Query篇

后續待定。。。。。。

合抱之木,生於毫末

反射

在計算機科學領域,反射是指一類應用,它們能夠自描述和自控制。也就是說,這類應用通過采用某種機制來實現對自己行為的描述(self-representation)和監測(examination),並能根據自身行為的狀態和結果,調整或修改應用所描述行為的狀態和相關的語義。

手擼ORM淺談ORM框架系列使用反射機制來實現的動態獲取所需要的信息,反射機制有優點也有缺點,使用了反射我們可以不用每次更新實體模型數據訪問層轉換Sql等很多需要修改的地方,享受快捷便利的同時肯定會帶來相應的缺點,反射的性能在執行同樣的操作時會略低於直接使用強類型語言的原生特性,畢竟它至少多了動態獲取信息步驟;我們不能因此否認反射,反射確實在很多場景可以為我們做很多不必要的重復性工作,可以節約出時間做更棘手的問題。

對於我們需要使用什么決定因素需要看使用前后的變化,利大於弊且符合業務需要就放心使用,完美不存在,更美從未止步。。。

泛型

泛型(摘錄百度百科:泛型)

由於.NET Framework 泛型的類型參數之實際類型在運行時均不會被消除,運行速度會因為類型轉換的次數減少而加快。Java 泛型的參數只可以代表類,不能代表個別對象。由於 Java 泛型的類型參數之實際類型在編譯時會被消除,所以無法在運行時得知其類型參數的類型。Java 編譯器在編譯泛型時會自動加入類型轉換的編碼,故運行速度不會因為使用泛型而加快。

透過現象看本質

( 2020-10-30新增)
為什么通過反射就可以實現把任意實體寫入數據庫?其實,根據當前請求傳入的實體通過反射拿到當前實體的public屬性字段以及每個public字段的值,當我們拿到了當前實體的public屬性字段及字段的值,我們就可以根據當前實體的主鍵是否寫入數據庫來動態的生成Insert into插入語句(自動遞增、指定主鍵、復合主鍵等),ORM框架本身並不是萬能的,只是根據當前實體主鍵定義規則動態的幫助我們生成Sql語句,ORM框架也可以在生成Sql語句后把Sql語句打印出來或提供寫到日志文件等,獲得程序的執行Sql語句可以更好地幫助我們優化或者更好地使用ORM框架。當然,打印執行的Sql語句應該有一個一鍵開關的功能想想就是美好的~

九層之台,起於累土

主要通過反射獲取實體信息,目前項目中的實體唯一主鍵統一使用的bigint自動遞增。

BaseRepository-》GetCurrentTableName獲取表名稱;

 1 /// <summary>
 2 /// get current-table-name
 3 /// </summary>
 4 /// <returns>return table-name</returns>
 5 private string GetCurrentTableName()
 6 {
 7     string currentTableName = string.Empty;
 8     var t = typeof(T);
 9     if (t != null && !t.Name.IsNullOrEmpty())
10     {
11         currentTableName = t.Name;
12     }
13     if (currentTableName.IsNullOrEmpty())
14     {
15         throw new ArgumentNullException("get table-name is null");
16     }
17     return currentTableName;
18 }

BaseRepository-》GetExcludeKeyAllFields獲取主鍵以外的public字段;

 1 /// <summary>
 2 /// get exclude key all fields
 3 /// </summary>
 4 /// <returns>return exclude key all fields</returns>
 5 private List<PropertyInfo> GetExcludeKeyAllFields()
 6 {
 7     PropertyInfo[] properties = typeof(T).GetProperties();
 8     // filter abstract virtual property
 9     properties = properties.Where(p => p.PropertyType.IsAbstract == false && !p.GetMethod.IsVirtual == true).ToArray();
10     if (properties == null || properties.Length <= 0)
11     {
12         throw new ArgumentNullException("public value fields is null");
13     }
14     List<PropertyInfo> list = new List<PropertyInfo>();
15     foreach (var item in properties)
16     {
17         if (item.CustomAttributes.Any(c => c.AttributeType.Name == nameof(KeyAttribute)))
18         {
19             continue;
20         }
21         list.Add(item);
22     }
23     if (list == null || list.Count <= 0)
24     {
25         throw new ArgumentNullException("public value fields is null");
26     }
27     return list;
28 }

BaseRepository-》GetValue獲取字段對應的值,String和Char前面分別加了N前綴(Sql Server反射弧),MySql中文字符可加或不加中文字段不會出現亂碼,若出現亂碼可以根據實際情況設置MySql數據庫字符集的格式;

2020-10-21初版-》Sql Server中文字符有可能會出現寫入數據庫產生亂碼,中文值部分可以參照使用: N'趙錢孫李';

 1 /// <summary>
 2 /// get value
 3 /// </summary>
 4 /// <param name="property"></param>
 5 /// <param name="entity"></param>
 6 /// <returns></returns>
 7 private string GetValue(PropertyInfo property , T entity)
 8 {
 9     var val = property.GetValue(entity);
10     if (val == null)
11     {
12       return "NULL";
13     }
14     else
15     {
16         string prefixN = string.Empty;
17         if (property.PropertyType.Name == nameof(String) || property.PropertyType.Name == nameof(Char))
18         {
19             prefixN = "N";
20         }
21         if (property.PropertyType.Name == nameof(Boolean))
22         {
23             return string.Format("{0}", GetBoolValue(val));
24         }
25         return string.Format("{0}'{1}'", prefixN, val);
26     }
27 }

2020-10-28(增加)MySql推薦使用下面的方法;

 1 /// <summary>
 2 /// get value
 3 /// </summary>
 4 /// <param name="property"></param>
 5 /// <param name="entity"></param>
 6 /// <returns></returns>
 7 private string GetValue(PropertyInfo property , T entity)
 8 {
 9     var val = property.GetValue(entity);
10     if (val == null)
11     {
12       return "NULL";
13     }
14     else
15     {
16         if (property.PropertyType.Name == nameof(Boolean))
17         {
18             return string.Format("{0}", GetBoolValue(val));
19         }
20         return string.Format("'{0}'", val);
21     }
22 }

BaseRepository-》GetBoolValue值屬性轉換,Sql Server中數據類型bit:'true' or 'false' 等同於'1' or '0';MySql 8.x數據類型bit: 需要把bool 'true' or 'false' 轉換成'1' or '0' 。目前項目中使用到的數據類型bigint、varchar、int、char、datetime、bit,bit->需要轉換;

 1 /// <summary>
 2 /// get bool value
 3 /// mysql true or false convert 1 or 0
 4 /// </summary>
 5 /// <param name="obj"></param>
 6 /// <returns></returns>
 7 private int GetBoolValue(object obj)
 8 {
 9     //return obj.ToString().ToLower() == "false" ? 0 : 1;
10     if (obj.ToString().ToLower() == "false")
11     {
12         return 0;
13     }
14     return 1;
15 }

(2020-10-30新增start)

我們先來看看Insert Sql語句:

1. INSERT INTO TABLE_NAME VALUES (值1, 值2,....)  --表的每一列都需要給值;

2. INSERT INTO TABLE_NAME (列1, 列2,...) VALUES (值1, 值2,....)  --指定列插入指定值。

前面敘述了那么多我們的目的還是單純的,我們只是想在當前的請求傳入的實體做文章 。如何獲取實體的表名稱、實體public非主鍵字段以及它們的值;主鍵需要單獨處理一下,實體的主鍵是自動遞增、指定主鍵還是復合主鍵,本篇是最簡單的自動遞增,所以,Insert語句沒有主鍵列,其他復雜的情況我們可以定義xxxKeyAttribute來區分當前鍵是自動遞增、指定主鍵、復合主鍵等。

(2020-10-30新增end)

BaseRepository-》GetInsertSql一路走來Sql出現了(提高性能可以優化,緩存當前項目所有表的增刪查改Sql語句)

 1 /// <summary>
 2 /// get insert sql
 3 /// </summary>
 4 /// <param name="entity">entity</param>
 5 /// <returns>return insert sql</returns>
 6 private string GetInsertSql(T entity)
 7 {
 8     string tableName = GetCurrentTableName();
 9     StringBuilder sbField = new StringBuilder();
10     StringBuilder sbValue = new StringBuilder();
11     PropertyInfo[] properties = GetAllFields(true);
12     foreach (var item in properties)
13     {
14         sbField.AppendFormat("{0},", item.Name);
15         sbValue.AppendFormat("{0},", GetValue(item, entity));
16     }
17     sbField.Remove(sbField.Length - 1, 1);
18     sbValue.Remove(sbValue.Length - 1, 1);
19     //todo ;SELECT @@identity return identity
20     return string.Format("INSERT INTO {0} ({1}) VALUES ({2})", tableName, sbField, sbValue);
21 }

BaseRepository-》Add千呼萬喚始出來,終於到寫入數據庫了(Add成功后沒有返回實體因為NET Core的DbContext取消了SqlQuery,基類BaseRepository實現Add后返回當前的實體主鍵,基本原生 SQL 查詢 可使用 FromSqlRaw 擴展方法基於原始 SQL 查詢開始 LINQ 查詢。 FromSqlRaw 只能在直接位於 DbSet<> 上的查詢根上使用;NET Framework DBContext中有此方法SqlQuery,可以使用Query在Insert語句后面;SELECT @@identity返回int、bigint類型自動遞增主鍵並賦值給當前實體,如果是指定主鍵直接返回當前實體,也期待園友提出更好的解決方案)

 1 /// <summary>
 2 /// add entity
 3 /// </summary>
 4 /// <param name="entity">entity</param>
 5 /// <returns>return true or false</returns>
 6 public bool Add(T entity)
 7 {
 8     string sql = GetInsertSql(entity);
 9     context.Database.ExecuteSqlRaw(sql);
10     return true;
11 }

實操Repository方法泛型約束

 1 /// <summary>
 2 /// LearnStudentRepository
 3 /// </summary>
 4 public partial class LearnStudentRepository: ILearnStudentRepository
 5 {
 6     public bool Add(Learn_Student learnStudent)
 7     {
 8         using (MySqlDbContext mySqlDbContext=new MySqlDbContext())
 9         {
10             BaseRepository<Learn_Student> baseRepository = new BaseRepository<Learn_Student>(mySqlDbContext);
11             return baseRepository.Add(learnStudent);
12         }
13     }
14 }

登高望遠,更上層樓

learn-orm-net缺少以下情況的處理:

  • 1. Sql語句沒有緩存,緩存提高一些性能 (使用緩存提高一些性能2020-10-26);
  • 2.不支持指定主鍵的實體,既是已經提前把主鍵生成好了;
  • 3.項目使用的實體是自動遞增主鍵並且沒有返回主鍵的值,不能滿足主子表有外鍵關系業務場景,既是子表存主表的主鍵(自動遞增類型主鍵);
  • 4.復合主鍵(主鍵都是指定主鍵,提前按照一定規則生成的鍵值);
  • 5.復合主鍵里面包含自增主鍵,既是有指定類型主鍵也有自動遞增類型主鍵;
  • 6.實體包含導航屬性級聯寫入數據庫(類似於3)
  • (如果業務需要特殊的方式,ORM框架沒有我們可以寫基類方法或者擴展方法來適應項目需要,待補充...)
  • 注:learn-orm-net目前只是作為學習ORM框架原理的Demo,項目會做出一定的優化處理,但不能直接拿來在項目中使用,畢竟現在NET Framework、NET Core已經有很多優秀的ORM框架,NET下一次發布就是只有一個版本了,我們沒有必要重復造輪子,造輪子是因為沒有現成的優秀的輪子可用。

如果只是停留在會使用當前項目所使用的ORM框架基本增刪查改,對於根據業務更好的使用ORM框架是有點困難的;所以,深入理解ORM原理,為未來的某個時刻我們遇到了問題,更好的根據ORM框架有的功能做出比較符合業務需要的程序;或者,擴展當前ORM框架沒有的功能來適應我們項目的業務需求。

代碼下載地址: SourceCode  作者水平有限歡迎園友糾正錯誤及不恰當之處,予以及時修正以免誤導他人!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM