很高興咱們的 ORM 數據訪問框架(Zongsoft.Data)在歷經兩個 SaaS 產品的應用以後,今天正式宣佈對外推廣!前端
這是一個類 GraphQL 風格的 ORM(Object/Relational Mapping) 數據訪問框架。git
在很長時間裏,.NET 陣營彷佛一直缺少一個被廣泛使用的 ORM 數據訪問框架,從最先的原生 ADO.NET 到舶來品 iBatis.NET 和 Hibernate.NET,後來又經歷了 Linq for SQL 與 Entity Framework 的混戰,多是由於 Entity Framework 早期版本的模糊定位和反覆變動的設計致使了它失之霸主之位,進而造就了一段百舸爭流、羣雄共逐的戰國時代。在歷經漫長而反覆的期待、失望、糾結和痛苦以後,我終於決定動手造一個輪子。github
在開始動手以前,先肯定如下基本設計原則:sql
在一個業務系統中,數據結構及其關係毋庸置疑是最底層的基礎性結構,數據庫應由系統架構師或開發負責人進行仔細設計 _(No Schema/Weakly Schema 的思潮是塗抹了蜂蜜的毒藥)_,數據訪問映射以數據庫表結構關係爲基石,在此之上業務層亦以概念映射模型爲準繩,層級之間相互隔離。數據庫
領域模型實體避免經過註解 (標籤) 來進行元數據定義,應確保嚴格符合 POCO/POJO 範式。經過語義化的 Schema 來聲明訪問的數據結構關係,禁止應用層的 SQL 和 Linq 式的類 SQL 代碼可下降業務層對數據層的依賴、提高代碼可維護性外,還具有更加統一可控的便利性,併爲數據訪問引擎的實現提供了更大的優化空間和自由度。後端
下面經過三個的例子 (注:例子均基於 Zongsoft.Community 項目) 來佐證上面的部分設計理念,更多示例和闡述請參考 Zongsoft.Data 項目的 README.md 文檔和 Zongsoft.Community 項目的代碼。緩存
提示: 下面的範例均基於 Zongsoft.Community 開源項目,該項目是一個完整的論壇社區的後臺程序。你可能須要預先閱讀一下該項目的 《數據庫表結構設計》文檔,以便更好的理解範例代碼的業務邏輯。
導航查詢及導航過濾微信
var forums = this.DataAccess.Select<Forum>( Condition.Equal("SiteId", this.User.SiteId) & Condition.In("Visibility", Visibility.Internal, Visibility.Public) | ( Condition.Equal("Visibility", Visibility.Specified) & Condition.Exists("Users", Condition.Equal("UserId", this.User.UserId) & ( Condition.Equal("IsModerator", true) | Condition.NotEqual("Permission", Permission.None) ) ) ), "*, MostRecentThread{ThreadId,Title,Creator{Name,Nickname,Avatar}}" );
上述數據訪問的查詢方法大體生成以下SQL腳本:數據結構
SELECT t.*, t1.ThreadId AS 'MostRecentThread.ThreadId', t1.Title AS 'MostRecentThread.Title', t1.CreatorId AS 'MostRecentThread.CreatorId', t2.UserId AS 'MostRecentThread.Creator.UserId', t2.Name AS 'MostRecentThread.Creator.Name', t2.Nickname AS 'MostRecentThread.Creator.Nickname', t2.Avatar AS 'MostRecentThread.Creator.Avatar' FROM Forum t LEFT JOIN Thread AS t1 ON t.MostRecentThreadId=t1.ThreadId LEFT JOIN UserProfile AS t2 ON t1.CreatorId=t2.UserId WHERE t.SiteId = @p1 AND t.Visibility IN (@p2, @p3) OR ( t.Visibility = @p4 AND EXISTS ( SELECT u.SiteId, u.ForumId, u.UserId FROM ForumUser u WHERE u.SiteId = t.SiteId AND u.ForumId = t.ForumId AND u.UserId = @p5 AND ( u.IsModerator = @p6 OR u.Permission != @p7 ) ) );
上述示例經過Select
查詢方法的schema
參數 (即值爲*, MostRecentThread{ThreadId,Title,Creator{Name,Nickname,Avatar}}
的參數) 從數據結構關係的層次指定了查詢數據的形狀,於是再也不須要 SQL 或類 SQL 語法中 JOIN 這樣命令式的語法元素,它不光提供了更簡潔且語義化的 API 訪問方式,並且還給數據訪問引擎底層提供了更大的優化空間和自由度。若是將
Select
查詢方法的schema
參數值改成*,Moderators{*},MostRecentThread{ThreadId,Title,Creator{Name,Nickname,Avatar}}
後,數據訪問引擎會將查詢內部分解爲一對多的兩條 SQL 語句進行迭代執行,而這些都不須要業務層進行分拆處理,於是提高了效率並下降了業務層的複雜度。架構注: 將 Schema 模式表達式經過 Web API 提供給前端應用,將大大減小後端開發的工做量,提高先後端的工做效率。
一對多的關聯新增
// 構建待新增的實體對象 var forum = new { SiteId = this.User.SiteId, GroupId = 100, Name = "xxxx", // 一對多的導航屬性 Users = new ForumUser[] { new ForumUser { UserId = 1001, IsModerator = true }, new ForumUser { UserId = 1002, Permission = Permission.Read }, new ForumUser { UserId = 1003, Permission = Permission.Write }, } } // 執行數據新增操做 this.DataAccess.Insert<Forum>(forum, "*, Users{*}");
上述數據訪問的新增方法大體生成以下SQL腳本:
/* 主表插入語句,執行一次 */ INSERT INTO Forum (SiteId,ForumId,GroupId,Name,...) VALUES (@p1,@p2,@p3,@p4,...); /* 子表插入語句,執行屢次 */ INSERT INTO ForumUser (SiteId,ForumId,UserId,Permission,IsModerator) VALUES (@p1,@p2,@p3,@p4,@p5);
上述示例經過Insert
新增方法的schema
參數(即值爲*,User{*}
的參數)指定了新增數據的形狀,由數據訪問引擎根據映射定義自動處理底層的 SQL 執行方式,確保業務層代碼的簡潔和更高的執行效率。
一對一和一對多的關聯更新,對於「一對多」的導航屬性,還能確保該屬性值 (集合類型) 以 UPSERT 模式寫入。
public bool Approve(ulong threadId) { //構建更新的條件 var criteria = Condition.Equal(nameof(Thread.ThreadId), threadId) & Condition.Equal(nameof(Thread.Approved), false) & Condition.Equal(nameof(Thread.SiteId), this.User.SiteId) & Condition.Exists("Forum.Users", Condition.Equal(nameof(Forum.ForumUser.UserId), this.User.UserId) & Condition.Equal(nameof(Forum.ForumUser.IsModerator), true)); //執行數據更新操做 return this.DataAccess.Update<Thread>(new { Approved = true, ApprovedTime = DateTime.Now, Post = new { Approved = true, } }, criteria, "*,Post{Approved}") > 0; }
上述數據訪問的更新方法大體生成以下SQL腳本:
/* 如下代碼爲支持 OUTPUT/RETURNING 子句的數據庫(如:SQLServer,Oracle,PostgreSQL) */ /* 根據更新的關聯鍵建立臨時表 */ CREATE TABLE #TMP ( PostId bigint NOT NULL ); /* 更新主表,並將更新的關聯鍵輸出到內存臨時表 */ UPDATE T SET T.[Approved]=@p1, T.[ApprovedTime]=@p2 OUTPUT DELETED.PostId INTO #TMP FROM [Community_Thread] AS T LEFT JOIN [Community_Forum] AS T1 ON /* Forum */ T1.[SiteId]=T.[SiteId] AND T1.[ForumId]=T.[ForumId] WHERE T.[ThreadId]=@p3 AND T.[Approved]=@p4 AND T.[SiteId]=@p5 AND EXISTS ( SELECT [SiteId],[ForumId] FROM [Community_ForumUser] WHERE [SiteId]=T1.[SiteId] AND [ForumId]=T1.[ForumId] AND [UserId]=@p6 AND [IsModerator]=@p7 ); /* 更新關聯表 */ UPDATE T SET T.[Approved]=@p1 FROM [Community_Post] AS T WHERE EXISTS ( SELECT [PostId] FROM #TMP WHERE [PostId]=T.[PostId]);
上述示例經過Update
更新方法的schema
參數(即值爲*,Post{Approved}
的參數)指定了更新數據的形狀,數據訪問引擎將根據數據庫類型生成高效的 SQL 語句,對於業務層而言這一切都是無感的、透明的。對於一對多的導航屬性,數據訪問引擎默認將以 UPSERT 模式處理子集的寫入,關於 UPSERT 更多信息請參考 Zongsoft.Data 項目文檔。
咱們但願提供最佳的綜合性價比,對於一個 ORM 數據訪問引擎來講,性能的關注點主要 (不限) 有這些要素:
實現層面咱們採用 Emitting 動態編譯技術對實體組裝(Populate)、數據參數綁定等進行預熱處理,可查閱 DataPopulator 等相關類的源碼深刻了解。
得益於 「以聲明方式來表達數據結構關係」 的語義化設計理念,相對於命令式設計而言,它使得程序意圖更加聚焦,自然地對底層數據的表達和優化更加寬容與自由。
更多詳細內容 (譬如:讀寫分離、繼承表、數據模式、映射文件、過濾器、驗證器、類型轉換、數據隔離) 請查閱相關文檔。
咱們歡迎並期待任何形式的推廣支持!
若是你認同咱們的設計理念請爲這個項目點贊(Star),若是你認爲該項目頗有用,而且但願支持它將來的發展,請給予必要的資金來支持它:
原文網址:http://zongsoft.com/blog/zh-cn/zongsoft/announcing-data-engine/