AppBoxFuture(低代碼快速開發(fā)框架)- 另類的ORM實現(xiàn)
??通常的ORM實現(xiàn)基于配置或注釋,由反射或Emit生成相應(yīng)的Sql語句,然后將Sql發(fā)送給數(shù)據(jù)庫解析Sql字符串生成AST再交給優(yōu)化器處理后執(zhí)行,返回的數(shù)據(jù)再經(jīng)由反射或Emit轉(zhuǎn)換為相應(yīng)的實體實例。作者認(rèn)為上述方式主要存在以下兩個問題:
- 實體類代碼是硬編碼的,如果實體類定義變更必須重新編譯應(yīng)用再部署,不利于實現(xiàn)運行時動態(tài)變更實體定義;
- CRUD操作轉(zhuǎn)換為Sql的實現(xiàn)復(fù)雜,且需要針對不同的數(shù)據(jù)庫做適配優(yōu)化。
??由于作者追求極致簡單的系統(tǒng)架構(gòu)以及絲般順滑的開發(fā)體驗,所以采用了另類的方式在框架內(nèi)實現(xiàn)了ORM,之于另類在什么地方我們先通過一些簡單示例后再來說明一下實現(xiàn)原理。
一、CRUD操作
??還是用系統(tǒng)自帶的Emploee模型作為示例,在IDE新建服務(wù)模型添加以下代碼保存發(fā)布后可通過主菜單->Service->Invoke運行測試:
//事務(wù)新建兩條記錄var emp1 = new Entities.Emploee();emp1.Name = “Rick”;emp1.Account = “rick@appbox.dev”;emp1.Birthday = new DateTime(1977, 3, 16);var emp2 = new Entities.Emploee();emp2.Name = “Johne”;emp2.Account = “johne@appbox.dev”emp2.Birthday = new DateTime(1979, 1, 2);using(var txn = await Transaction.BeginAsync()){ await EntityStore.SaveAsync(emp1, txn); await EntityStore.SaveAsync(emp2, txn); await txn.CommitAsync();}//查詢記錄var q = new TableScan<Entities.Emploee>();q.Filter(t => t.Name == “Rick”);var emps = await q.ToListAsync();//更新記錄emps[0].Name = “Rick Lu”;await EntityStore.SaveAsync(emps[0]);//刪除記錄await EntityStore.DeleteAsync(emps[0]);
??以上只是已實現(xiàn)的一些Api示例,復(fù)雜的如根據(jù)索引查詢、聚合查詢等Api正在設(shè)計開發(fā)中。
二、實現(xiàn)原理
設(shè)計時:
??這部分實現(xiàn)重度依賴Roslyn功能,服務(wù)端在開發(fā)人員登錄至IDE后會通過Roslyn生成虛擬項目。
- 實體模型保存時服務(wù)端生成虛擬的實體類代碼,并加入虛擬項目內(nèi);
- 服務(wù)模型保存時服務(wù)端生成虛擬的服務(wù)類代碼,并加入虛擬項目內(nèi);發(fā)布時服務(wù)端的編譯引擎解析虛擬的服務(wù)代碼,然后轉(zhuǎn)換為運行時服務(wù)代碼,再通過Roslyn編譯為動態(tài)服務(wù)組件。其中虛擬代碼轉(zhuǎn)換為運行時代碼的過程主要是:
- 將實體屬性取賦值操作(eg: entity.Name = “aa” 或 var temp = entity.Name)轉(zhuǎn)換為針對運行時Entity類的GetXXX()及SetXXX()操作。**這里需要注意的是運行時只存在一個Entity類(類似于KVO通過字典表保存屬性值)**;
- 將查詢條件轉(zhuǎn)換為可序列化的表達(dá)式,這樣存儲引擎執(zhí)行查詢命令時可委托clr emit生成過濾指令,存儲引擎在掃描時直接使用過濾指令計算滿足條件的記錄。
運行時:
??這部分實現(xiàn)參考以下流程圖,需要注意的是存儲引擎是基于RocksDB的,實體數(shù)據(jù)轉(zhuǎn)換為KV數(shù)據(jù)(如Key=Id, Value=[字段標(biāo)識:字段值;字段標(biāo)識:字段值]),這樣轉(zhuǎn)換過程就不需要使用反射或Emit。
三、性能測試
??作者做了簡單的性能測試(單節(jié)點I74C8G虛擬機):
- 并發(fā)插入不帶索引不帶外鍵的簡單實體約28000tps;
- 通過惟一索引查詢約80000qps;
- 帶條件掃描少量記錄約35000qps。
四、查詢限制
- 存儲引擎不支持join,可通過分別查詢出數(shù)據(jù)后利用Linq做join操作;
- 存儲引擎暫只支持根據(jù)Entity.Id或指定索引查詢排序,不支持自定義排序。
五、本篇小結(jié)
??本篇主要介紹了框架集成的ORM的另類實現(xiàn),如果您有問題或Bug報告,請留言或在[Github](https://github.com/enjoycode/appbox.deploy)提交Issue。