.NET的轻量级高性能ORM 让代码更优雅

转自:TANZAME

cnblogs.com/yiting/p/11823878.html

前言

大家好,我是TANZAME。天气慢慢转凉,朋友们注意添衣保暖,愉快撸码。

距离 TZM.XFramework 的首秀已数月有余,期间收到不少朋友的鼓励、建议和反馈,在此致以深深的感谢。

不少围观的朋友经常问题我,.NET 体系下优秀的 O/RM 官方的有EF,第三方的有linq2db (国外)、StackExchange/Dapper (国外)、NHibernate (国外)、PetaPoco (国外)、Freesql (国内)等等,What's your problem?

Ok,咱们就用一分钟的时间聊聊 What's my Advantage,聊聊如何用 ORM 让代码变得更优雅更加清爽。

正文

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

如果有 ORM 能帮我们撸这种 SQL,岂不更痛快?来看看我们的 ORM 是怎么操作的:

一、多表关联更新  

// 更新本表值等于别表的字段值
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);

二、多表关联插入

// 多表关联批量新增
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);

三、多表关联删除

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

有趣和好奇心是为了取悦自己,然后才能有意思和有用是去取悦别人。

撸码不易,不喜轻喷,有不同看法老友欢迎交流。

- EOF -

技术群:添加小编微信dotnet999

公众号:dotnet讲堂

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
DotNetCoding(http//www.dotnetcoding.net)是一款帮助开发以数据库为基础,以DotNet为开发语言的Case开发工具。它集系统设计,代码自动生成等功能于一体 系统运用分层原理和组件原理,将所有的基于数据库访问的系统划分为用户界面层(UI),业务规则层(BLL), 数据访问层(DAL)和数据库层(DB). 每一下层为上层提供支持,一般不跨层访问,是一个真正意义上的多层架构; 同时结合现在一些已经成熟的设计框架,设计模式和构件模式,将一个解决方案分解为6个Project. 分别是通用类库, 通用控件库, 数据访问工程, 业务规则工程,项目专用控件工程,UI界面工程.整个方案根据已经设计好的数据库自动生成,在生成的过程种可以灵活的定义生成参数. 系统实现了比较完整的O-R映射.数据库中的所有逻辑对象表,列,字段,主键,外键,Null, Default,唯一索引都在类里得到了体现,同时还支持代码表,代码列,自增长机制. 系统支持完整的数据操作功能,支持带事务功能的数据操作,提供数据自动装载功能,有很强的数据查询功能. 系统采用强类型来进行代码的生成,并且对最终开发人员的强类型支持很好,从而减少在编码过程中引入的错误,为编写高质量的代码提供了强有力的支持. 系统也比较充分的考虑了变与维护的需要,在数据库设计发生变化的情况下,只要在已有工程的基础上重新生成一遍,系统就会自动合并最终开发编写的代码到生成的代码中,使系统的变变的很容易. UI界面层按到组件原则来进行代码的自动生成.自动生数据编辑,数据显示控件,页面组合组件,并采用控件和CSS技术,由于采用了这些组件技术,使得最终开发人员可以很方便的变自动生成的界面样式; UI界面应用MVC模式,将这个UI界面代码部分分为三部分:视图部分,模型部分和控制,自动生成Data Source属性来完成view和Model的相互映射;并且能按照功能组生成菜单, 同时系统能自动完成null, PK ,UK等基本的数据检验和基于单表数据的增删查改等基础功能. 此系统还提供了文档生成,数据库迁移与转换及部分设计有关的功能 现在此平台支持多种数据访问框架(三层框架, PetShop框架,工厂框架, WebServeice框架) 支持Windows , Web两种应用类型 支持VB.net , c#.Net两种开发语言 支持SqlServer, Oracle, Access等多种数据库, 并且支持用SqlServer数据库来进行数据模式的设计,而实际开发运行数据库是其他数据库的情况, 开发的应用可以非常轻松的变换数据库. 本系统的应用范围:有数据库访问功能的.net应用 应用前提:需要有已经设计好的数据库 采用此平台来开发信息管理系统, 能够直接将设计转化为代码,并支持系统的重构;最终开发人员一般只需要在业务规则层扩展相关业务规则的编码,对自动生成的界面进行少量的调整,在UI界面工程里添加少量的控制代码. 采用此开发平台,能够复用成熟的开发架构,自动生成所有非商业有关的代码,在公司范围内不断的积累开发技能和经验,极大的提高开发效率,规范开发,提高开发质量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值