zoukankan      html  css  js  c++  java
  • 【原创】基于.NET的轻量级高性能 ORM

     【前言】

      大家好,我是TANZAME。出乎意料的,我们在立冬的前一天又见面了,天气慢慢转凉,朋友们注意添衣保暖,愉快撸码。距离 TZM.XFramework 的首秀已数月有余,期间收到不少朋友的鼓励、建议和反馈,在此致以深深的感谢。

      不少围观的朋友经常问题我,.NET 体系下优秀的 O/RM 官方的有EF,第三方的有linq2db (国外)、StackExchange/Dapper (国外)、NHibernate (国外)、PetaPoco (国外)、Freesql (国内)等等,What's your problem?Ok,咱们就用一分钟的时间聊聊如何用 ORM 让代码变得更优雅更加清爽。

     【正文】

      相信朋友们都遇到过这样的场景:要插入/删除/修改的数据来自外键表,怎么办?先查出来再进行接下的操作吗,别这样老铁,至少两次以上的数据库访问,从性能角度来说并不是最优的做法。手撸纯 SQL吗,看起来还行至少不会那么令人不舒服。如果有 ORM 能帮我们撸这种 SQL,岂不更痛快?来看看我们的 ORM 是怎么操作的:

      

      1. 多表关联更新  

    // 更新本表值等于别表的字段值
    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. 多表关联插入

    // 多表关联批量新增
    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 语句需要拼接上这些字段。来看看代码实现:

    // 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. 多表关联删除

    // 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

      

  • 相关阅读:
    java 基本数据类型的取值范围
    警惕自增的陷阱
    三元操作符的类型务必一致
    不要随便设置随机种子
    优先使用整形池
    IN、ANY、ALL与SOME
    第六章-序列:字符串、列表和元组 笔记
    第十二章-安全性
    第五章-数字 课后答案
    第十一章-约束、视图与事务
  • 原文地址:https://www.cnblogs.com/yiting/p/11823878.html
Copyright © 2011-2022 走看看