【原創】基於.NET的輕量級高性能 ORM - TZM.XFramework 之讓代碼更優雅

 【前言】html

  你們好,我是TANZAME。出乎意料的,咱們在立冬的前一天又見面了,天氣慢慢轉涼,朋友們注意添衣保暖,愉快擼碼。距離 TZM.XFramework 的首秀已數月有餘,期間收到很多朋友的鼓勵、建議和反饋,在此致以深深的感謝。git

  很多圍觀的朋友常常問題我,.NET 體系下優秀的 O/RM 官方的有EF,第三方的有linq2db (國外)、StackExchange/Dapper (國外)、NHibernate (國外)、PetaPoco (國外)、Freesql (國內)等等,What's your problem?Ok,我們就用一分鐘的時間聊聊 What's my Advantage,聊聊如何用 ORM 讓代碼變得更優雅更加清爽。github

 【正文】sql

  相信朋友們都遇到過這樣的場景:要插入/刪除/修改的數據來自外鍵表,怎麼辦?先查出來再進行接下的操做嗎,別這樣老鐵,至少兩次以上的數據庫訪問,從性能角度來講並非最優的作法。手擼純 SQL嗎,看起來還行至少不會那麼使人不舒服。若是有 ORM 能幫咱們擼這種 SQL,豈不更痛快?來看看咱們的 ORM 是怎麼操做的:數據庫

  

  1. 多表關聯更新  app

// 更新本表值等於別表的字段值
var query =
    from a in context.GetTable<Model.Client>()
    join b in context.GetTable<Model.CloudServer>() on a.CloudServerId equals b.CloudServerId
    join c in context.GetTable<Model.ClientAccount>() on a.ClientId equals c.ClientId
    where c.AccountId == "1"
    select a;
context.Update<Model.Client, Model.CloudServer, Model.ClientAccount>((a, b, c) => new
{
    CloudServerId = b.CloudServerId,
    Qty = c.Qty > 0 ? c.Qty : 1,
}, query);
context.SubmitChanges();

-- 產生的SQL
--UPDATE t0 SET
--t0.[CloudServerId] = t1.[CloudServerId],
--t0.[Qty] = (CASE WHEN t2.[Qty] > @p1 THEN t2.[Qty] ELSE @p2 END)
--FROM [Bas_Client] AS [t0]
--INNER JOIN [Sys_CloudServer] t1 ON t0.[CloudServerId] = t1.[CloudServerId]
--INNER JOIN [Bas_ClientAccount] t2 ON t0.[ClientId] = t2.[ClientId]
--WHERE t2.[AccountId] = @p3

  仔細觀察查詢語義和對應的 SQL 不難發現,from a in context.GetTable 這一句正是對應了 UPDAE *** FROM TABLE 這一句,接下來就是關聯到外鍵表也便是 INNER JOIN TABLE,最後是咱們熟悉的 WHERE 語句。有一個特別的地方就是 Oracle 它沒有 UPDATE FROM 這種語法,只能用 MERGE INTO 來代替。來看看源碼是怎麼實現的:性能

// 預先解析表別名,將查詢語義中出現的如a,b,c這些變量表達成 t0,t1,t2的形式
TableAliasCache aliases = this.PrepareAlias<T>(uQueryInfo.SelectInfo, token);
ExpressionVisitorBase visitor = null;
// 解析UPDATE的字段
visitor = new UpdateExpressionVisitor(this, aliases, uQueryInfo.Expression);
visitor.Write(builder);
// FROM 片斷
builder.AppendNewLine();
builder.Append("FROM ");
builder.AppendMember(typeRuntime.TableName, !typeRuntime.IsTemporary);
builder.AppendAs("t0");

var cmd2 = new MappingCommand(this, aliases, token) { HasMany = uQueryInfo.SelectInfo.HasMany };
// 解析外鍵表
visitor = new JoinExpressionVisitor(this, aliases, uQueryInfo.SelectInfo.Joins);
visitor.Write(cmd2.JoinFragment);
// 解析WHERE條件
visitor = new WhereExpressionVisitor(this, aliases, uQueryInfo.SelectInfo.WhereExpression);
visitor.Write(cmd2.WhereFragment);
cmd2.AddNavMembers(visitor.NavMembers);
// 最後合併全部的片段造成最終SQL語句
builder.Append(cmd2.CommandText);

 

  2. 多表關聯插入ui

// 多表關聯批量新增
var query =
    from a in context.GetTable<Model.Client>()
    join b in context.GetTable<Model.CloudServer>() on a.CloudServerId equals b.CloudServerId
    where a.ClientId <= 5 && b.CloudServerId != 0
    select new Model.Client
    {
        ClientId = DbFunction.RowNumber<int>(x => a.ClientId) + (maxClientId + 2),
        ClientCode = "ABC2",
        ClientName = "啊啵呲2",
        CloudServerId = b.CloudServerId,
        State = 2,
        ActiveDate = DateTime.Now
    };
context.Insert(query);

-- 產生的SQL
--INSERT INTO [Bas_Client]([ClientId],[ClientCode],[ClientName],[CloudServerId],[State],[ActiveDate])
--SELECT 
--ROW_NUMBER() Over(Order By t0.[ClientId]) + @p17 + @p18 AS [ClientId],
--@p19 AS [ClientCode],
--@p20 AS [ClientName],
--t1.[CloudServerId] AS [CloudServerId],
--@p21 AS [State],
--@p22 AS [ActiveDate]
--FROM [Bas_Client] t0 
--INNER JOIN [Sys_CloudServer] t1 ON t0.[CloudServerId] = t1.[CloudServerId]
--WHERE t0.[ClientId] <= @p23 AND t1.[CloudServerId] <> @p24

  從產生的 SQL 能夠看出,除去第一行的 INSERT,剩下的就是整個 SELECT 語句,也就是上面示例代碼中 query 變量表示的查詢語義。這裏須要注意的是在解析 SELECT 語句的同時要把所的字段記錄下來,INSERT INTO 語句須要拼接上這些字段。來看看代碼實現:this

// INSERT INTO 片段
builder.Append("INSERT INTO ");
builder.AppendMember(typeRuntime.TableName, !typeRuntime.IsTemporary);
builder.Append('(');

// 解析 SELECT 塊
MappingCommand cmd2 = this.ParseSelectCommand(nQueryInfo.SelectInfo, 0, true, token) as MappingCommand;
int i = 0;
// 拼接 INSERT INTO 字段
foreach (Column column in cmd2.PickColumns)
{
    builder.AppendMember(column.NewName);
    if (i < cmd2.PickColumns.Count - 1) builder.Append(',');
    i++;
}
builder.Append(')');
// 最後合併全部的片段造成最終SQL語句
builder.AppendNewLine();
builder.Append(cmd2.CommandText);

  

3. 多表關聯刪除spa

// Query 關聯批量刪除
var query =
    from a in context.GetTable<Model.Client>()
    join b in context.GetTable<Model.ClientAccount>() on a.ClientId equals b.ClientId
    join c in context.GetTable<Model.ClientAccountMarket>() on new { b.ClientId, b.AccountId } equals new { c.ClientId, c.AccountId }
    where c.ClientId > 100 && c.AccountId == "1" && c.MarketId == 1
    select a;
context.Delete<Model.Client>(query1);

-- 產生的SQL
--DELETE t0 FROM [Bas_Client] t0 
--INNER JOIN [Bas_ClientAccount] t1 ON t0.[ClientId] = t1.[ClientId]
--INNER JOIN [Bas_ClientAccountMarket] t2 ON t1.[ClientId] = t2.[ClientId] AND t1.[AccountId] = t2.[AccountId]
--WHERE t2.[ClientId] > @p2 AND t2.[AccountId] = @p3 AND t2.[MarketId] = @p4 

  刪除跟更新的更新的原理是同樣的,無非是 UPDATE 換成了 DELETE。另外 Oracle 也沒有 DELETE FROM 語法,咱們換一種取巧一下,用 ROWID 同樣能達到關聯刪除的效果。來看看最後的代碼實現:

// DELETE FROM 片段
builder.Append("DELETE t0 FROM ");
builder.AppendMember(typeRuntime.TableName, !typeRuntime.IsTemporary);
builder.Append(" t0 ");
// 預先解析表別名,將查詢語義中出現的如a,b,c這些變量表達成 t0,t1,t2的形式
TableAliasCache aliases = this.PrepareAlias<T>(dQueryInfo.SelectInfo, token);
var cmd2 = new MappingCommand(this, aliases, token) { HasMany = dQueryInfo.SelectInfo.HasMany };
// 解析外鍵表
ExpressionVisitorBase visitor = new JoinExpressionVisitor(this, aliases, dQueryInfo.SelectInfo.Joins);
visitor.Write(cmd2.JoinFragment);
// 解析WHERE條件
visitor = new WhereExpressionVisitor(this, aliases, dQueryInfo.SelectInfo.WhereExpression);
visitor.Write(cmd2.WhereFragment);
cmd2.AddNavMembers(visitor.NavMembers);
// 最後合併全部的片段造成最終SQL語句
builder.Append(cmd2.CommandText);

    

 【結語】

  通過大半月的努力,TZM.XFramework 也已正式支持 SQLite了,託管地址:GitHub託管地址:https://github.com/TANZAME/XFramework 。最後借用某公衆號上面的一句話與你們共勉,有趣和好奇心是爲了取悅本身,而後纔能有意思和有用是去取悅別人。擼碼不易,不喜輕噴,有不一樣見解老友歡迎加羣交流。

  技術交流羣:816425449

相關文章
相關標籤/搜索