.NET ORM 仓储层必备的功能介绍之 FreeSql Repository 实现篇

发布网友

我来回答

1个回答

热心网友

写在开头

2018年11月的某一天,头脑发热开启了 FreeSql 开源项目之旅,时间一晃已经四年多,当初从舒服区走向一个巨大的坑,回头一看后背一凉。四年时间从无到有,经历了数不清的日夜奋战(有人问我花了多长时间投入,答案:全职x2 + 前两年无休息,以及后面两年的持续投入)。今天 FreeSql 已经很强大,感谢第一期、第二期、第N期持续提出建议的网友。

FreeSql 现如今已经是一个稳定的版本,主要体现:

API 已经确定,不会轻易推翻重作调整,坚持十年不变的原则,让使用者真真正正的不再关心 ORM 使用问题;

单元测试覆盖面广,6336+ 个单元测试,小版本更新升级无须考虑修东墙、补西墙的问题;

经历四年时间的生产考验,nuget下载量已超过900K+,平均每日750+;

感叹:有些人说 .Net 陷入 orm 怪圈,动手的没几个,指点江山的一堆,.Net orm 真的如他们所讲的简单吗?

项目介绍

FreeSql 是 .Net ORM,能支持 .NetFramework4.0+、.NetCore、Xamarin、MAUI、Blazor、以及还有说不出来的运行平台,因为代码绿色无依赖,支持新平台非常简单。目前单元测试数量:6336+,Nuget下载数量:900K+。QQ群:4336577(已满)、8578575(在线)、52508226(在线)

温馨提醒:以下内容无商吹成份,FreeSql 不打诳语

为什么要重复造轮子?

FreeSql 主要优势在于易用性上,基本是开箱即用,在不同数据库之间切换兼容性比较好。作者花了大量的时间精力在这个项目,肯请您花半小时了解下项目,谢谢。FreeSql 整体的功能特性如下:

支持 CodeFirst 对比结构变化迁移;

支持 DbFirst 从数据库导入实体类;

支持 丰富的表达式函数,自定析;

支持 批量添加、批量更新、BulkCopy;

支持 导航属性,贪婪加载、延时加载、级联保存;

支持 读写分离、分表分库,租户设计;

支持 MySql/SqlServer/PostgreSQL/Oracle/Sqlite/Firebird/达梦/神通/金仓/翰高/MsAccess Ado.NET 实现包,以及 Odbc 的专门实现包;

5500+个单元测试作为基调,支持10多数数据库,我们提供了通用Odbc理论上支持所有数据库,目前已知有群友使用 FreeSql 操作华为高斯、mycat、tidb 等数据库。安装时只需要选择对应的数据库实现包:

dotnet add packages FreeSql.Provider.MySql

FreeSql.Repository 是 FreeSql 项目的延申扩展类库,支持 .NETFramework4.0+、.NETCore2.0+、.NET5+、Xamarin 平台。

FreeSql.Repository 除了 CRUD 还有很多实用性功能,不防耐下心花10分钟看完。

01 安装

环境1:.NET Core 或 .NET 5.0+

dotnet?add?package?FreeSql.Repository

环境2、.NET Framework

Install-Package?FreeSql.DbContextstatic?IFreeSql?fsql?=?new?FreeSql.FreeSqlBuilder()????.UseConnectionString(FreeSql.DataType.Sqlite,?connectionString)????.UseAutoSyncStructure(true)?//自动迁移实体的结构到数据库????.Build();?//请务必定义成?Singleton?单例模式02 使用方法

方法1、IFreeSql 的扩展方法;

var?curd?=?fsql.GetRepository<Topic>();

注意:Repository 对象多线程不安全,因此不应在多个线程上同时对其执行工作。

fsql.GetRepository 方法返回新仓储实例

不支持从不同的线程同时使用同一仓储实例

以下为了方便测试代码演示,我们都使用方法1,fsql.GetRepository创建新仓储实例

方法2、继承实现;

public?class?TopicRepository:?BaseRepository<Topic,?int>?{????public?TopicRepository(IFreeSql?fsql)?:?base(fsql,?null,?null)?{}????//在这里增加?CURD?以外的方法}

方法3、依赖注入;

public?void?ConfigureServices(IServiceCollection?services){????services.AddSingleton<IFreeSql>(Fsql);????services.AddFreeRepository(null,?this.GetType().Assembly);}//在控制器使用public?TopicController(IBaseRepository<Topic>?repo)?{}03 添加数据

repo.Insert 插入数据,适配了各数据库优化执行 ExecuteAffrows/ExecuteIdentity/ExecuteInserted

1、如果表有自增列,插入数据后应该要返回 id。

var?repo?=?fsql.GetRepository<Topic>();repo.Insert(topic);

内部会将插入后的自增值填充给 topic.Id

2、批量插入

var?repo?=?fsql.GetRepository<Topic>();var?topics?=?new?[]?{?new?Topic?{?...?},?new?Topic?{?...?}?};repo.Insert(topics);

3、插入数据库时间

使用 [Column(ServerTime = DateTimeKind.Utc)] 特性,插入数据时,使用适配好的每种数据库内容,如 getutcdate()

4、插入特殊类型

使用 [Column(RereadSql = "{0}.STAsText()", RewriteSql = "geography::STGeomFromText({0},4236)")] 特性,插入和读取时特别处理

5、插入时忽略

使用 [Column(CanInsert = false)] 特性

04 更新数据

1、只更新变化的属性

var?repo?=?fsql.GetRepository<Topic>();var?item?=?repo.Where(a?=>?a.Id?==?1).First();??//此时快照?itemitem.Name?=?"newtitle";repo.Update(item);?//对比快照时的变化//UPDATE?`Topic`?SET?`Title`?=?'newtitle'//WHERE?(`Id`?=?1)

2、手工管理状态

var?repo?=?fsql.GetRepository<Topic>();var?item?=?new?Topic?{?Id?=?1?};repo.Attach(item);?//此时快照?itemitem.Title?=?"newtitle";repo.Update(item);?//对比快照时的变化//UPDATE?`Topic`?SET?`Title`?=?'newtitle'//WHERE?(`Id`?=?1)

3、直接使用 repo.UpdateDiy,它是 IFreeSql 提供的原生 IUpdate 对象,功能更丰富

05 级联保存数据

实践发现,N对1 不适合做级联保存。保存 Topic 的时候把 Type 信息也保存?我个人认为自下向上保存的功能太不可控了,FreeSql 目前不支持自下向上保存。因此下面我们只讲 OneToOne/OneToMany/ManyToMany 级联保存。至于 ManyToOne 级联保存使用手工处理,更加安全可控。

功能1:SaveMany 手工保存

完整保存,对比表已存在的数据,计算出添加、修改、删除执行。

递归保存导航属性不安全,不可控,并非技术问题,而是出于安全考虑,提供了手工完整保存的方式。

Install-Package?FreeSql.DbContext0

SaveMany 仅支持 OneToMany、ManyToMany 导航属性

只保存 Topics,不向下递归追朔

当 Topics 为 Empty 时,删除 type 存在的 Topics 所有表数据,确认?

ManyToMany 机制为,完整对比保存中间表,外部表只追加不更新

如:

本表 Topic

外部表 Tag

中间表 TopicTag

功能2:EnableCascadeSave 仓储级联保存

DbContext/Repository EnableCascadeSave 可实现保存对象的时候,递归追朔其 OneToOne/OneToMany/ManyToMany 导航属性也一并保存,本文档说明机制防止误用。

1、OneToOne 级联保存

v3.2.606+ 支持,并且支持级联删除功能(文档请向下浏览)

2、OneToMany 追加或更新子表,不删除子表已存在的数据

Install-Package?FreeSql.DbContext1

不删除 Topics 子表已存在的数据,确认?

当 Topics 属性为 Empty 时,不做任何操作,确认?

保存 Topics 的时候,还会保存 Topics[0-..] 的下级集合属性,向下18层,确认?

向下18层的意思,比如【类型】表,下面有集合属性【文章】,【文章】下面有集合属性【评论】。

保存【类型】表对象的时候,他会向下检索出集合属性【文章】,然后如果【文章】被保存的时候,再继续向下检索出集合属性【评论】。一起做 InsertOrUpdate 操作。

3、ManyToMany 完整对比保存中间表,追加外部表

完整对比保存中间表,对比【多对多】中间表已存在的数据,计算出添加、修改、删除执行。

追加外部表,只追加不更新。

本表 Topic

外部表 Tag

中间表 TopicTag

06 删除数据Install-Package?FreeSql.DbContext207 级联删除数据

第一种:基于【对象】级联删除

比如使用过 Include/IncludeMany 查询的对象,可以使用此方法级联删除它们。

Install-Package?FreeSql.DbContext3

第二种:基于【数据库】级联删除,不依赖数据库外键

根据设置的导航属性,递归删除 OneToOne/OneToMany/ManyToMany 对应数据,并返回已删除的数据。此功能不依赖数据库外键

Install-Package?FreeSql.DbContext408 添加或修改数据Install-Package?FreeSql.DbContext5

如果内部的状态管理存在数据,则更新。

如果内部的状态管理不存在数据,则查询数据库,判断是否存在。

存在则更新,不存在则插入

缺点:不支持批量操作

提醒:IFreeSql 也定义了 InsertOrUpdate 方法,两者实现机制不同,它利用了数据库特性:

DatabaseFeaturesDatabaseFeaturesMySqlon duplicate key update达梦merge intoPostgreSQLon conflict do update金仓on conflict do updateSqlServermerge into神通merge intoOraclemerge into南大通用merge intoSqlitereplace intoMsAccess不支持Firebirdmerge intoInstall-Package?FreeSql.DbContext609 批量编辑数据Install-Package?FreeSql.DbContext7

上面的代码 EndEdit 方法执行的时候产生 3 条 SQL 如下:

Install-Package?FreeSql.DbContext8

场景:winform 加载表数据后,一顿添加、修改、删除操作之后,点击【保存】

提醒:该操作只对变量 cts 有效,不是针对全表对比更新。

10 弱类型 CRUDInstall-Package?FreeSql.DbContext911 无参数化命令

支持参数化、无参数化命令执行,有一些特定的数据库,使用无参数化命令执行效率更高哦,并且调试起来更直观。

static?IFreeSql?fsql?=?new?FreeSql.FreeSqlBuilder()????.UseConnectionString(FreeSql.DataType.Sqlite,?connectionString)????.UseAutoSyncStructure(true)?//自动迁移实体的结构到数据库????.Build();?//请务必定义成?Singleton?单例模式012 工作单元(事务)

UnitOfWork 可将多个仓储放在一个单元管理执行,最终通用 Commit 执行所有操作,内部采用了数据库事务。

方法1:随时创建使用

static?IFreeSql?fsql?=?new?FreeSql.FreeSqlBuilder()????.UseConnectionString(FreeSql.DataType.Sqlite,?connectionString)????.UseAutoSyncStructure(true)?//自动迁移实体的结构到数据库????.Build();?//请务必定义成?Singleton?单例模式1

方法2:使用 AOP + UnitOfWorkManager 实现多种事务传播

本段内容引导,如何在 asp.net core 项目中使用特性(注解) 的方式管理事务。

UnitOfWorkManager 支持六种传播方式(propagation),意味着跨方法的事务非常方便,并且支持同步异步:

Requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,默认的选择。

Supports:支持当前事务,如果没有当前事务,就以非事务方法执行。

Mandatory:使用当前事务,如果没有当前事务,就抛出异常。

NotSupported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

Never:以非事务方式执行操作,如果当前事务存在则抛出异常。

Nested:以嵌套事务方式执行。

第一步:配置 Startup.cs 注入

static?IFreeSql?fsql?=?new?FreeSql.FreeSqlBuilder()????.UseConnectionString(FreeSql.DataType.Sqlite,?connectionString)????.UseAutoSyncStructure(true)?//自动迁移实体的结构到数据库????.Build();?//请务必定义成?Singleton?单例模式2UnitOfWorkManager 成员说明IUnitOfWork Current返回当前的工作单元void Binding(repository)将仓储的事务交给它管理IUnitOfWork Begin(propagation, isolationLevel)创建工作单元

第二步:定义事务特性

static?IFreeSql?fsql?=?new?FreeSql.FreeSqlBuilder()????.UseConnectionString(FreeSql.DataType.Sqlite,?connectionString)????.UseAutoSyncStructure(true)?//自动迁移实体的结构到数据库????.Build();?//请务必定义成?Singleton?单例模式3

第三步:引入动态代理库

在 Before 从容器中获取 UnitOfWorkManager,调用它的 var uow = Begin(attr.Propagation, attr.IsolationLevel) 方法

在 After 调用 Before 中的 uow.Commit 或者 Rollback 方法,最后调用 uow.Dispose

提醒:动态代理,一定注意处理好异步 await,否则会出现事务异常的问题

第四步:在 Controller 或者 Service 中使用事务特性

static?IFreeSql?fsql?=?new?FreeSql.FreeSqlBuilder()????.UseConnectionString(FreeSql.DataType.Sqlite,?connectionString)????.UseAutoSyncStructure(true)?//自动迁移实体的结构到数据库????.Build();?//请务必定义成?Singleton?单例模式4

是不是进方法就开事务呢?

不一定是真实事务,有可能是虚的,就是一个假的 unitofwork(不带事务)

也有可能是延用上一次的事务

也有可能是新开事务,具体要看传播模式

示例项目:https://github.com/dotnetcore/FreeSql/tree/master/Examples/aspnetcore_transaction

Autofac 动态代理参考项目:

https://github.com/luoyunchong/lin-cms-dotnetcore

https://github.com/luoyunchong/dotnetcore-examples/tree/master/ORM/FreeSql/OvOv.FreeSql.AutoFac.DynamicProxy

AOP + FreeSql 跨方法异步事务 https://www.cnblogs.com/igeekfan/p/aop-freesql-autofac.html

13 手工分表

FreeSql 原生用法、FreeSql.Repository 仓储用法 都提供了 AsTable 方法对分表进行 CRUD 操作,例如:

static?IFreeSql?fsql?=?new?FreeSql.FreeSqlBuilder()????.UseConnectionString(FreeSql.DataType.Sqlite,?connectionString)????.UseAutoSyncStructure(true)?//自动迁移实体的结构到数据库????.Build();?//请务必定义成?Singleton?单例模式5

跨库,但是在同一个数据库服务器下,也可以使用 AsTable(oldname => $"db2.dbo.{oldname}")

static?IFreeSql?fsql?=?new?FreeSql.FreeSqlBuilder()????.UseConnectionString(FreeSql.DataType.Sqlite,?connectionString)????.UseAutoSyncStructure(true)?//自动迁移实体的结构到数据库????.Build();?//请务必定义成?Singleton?单例模式6

分表总结:

分表、相同服务器跨库 可以使用 AsTable 进行 CRUD;

AsTable CodeFirst 会自动创建不存在的分表;

不可在分表分库的实体类型中使用《延时加载》;

v3.2.500 按时间自动分表方案:https://github.com/dotnetcore/FreeSql/discussions/1066

写在最后

FreeSql 他是免费自由的 ORM,也可以说是宝藏 ORM。更多文档请前往 github wiki 查看。

FreeSql 已经步入第四个年头,期待少年的你十年后还能归来在此贴回复,兑现当初吹过十年不变的承诺。

多的不说了,希望民间的开源力量越来越强大。

希望作者的努力能打动到你,请求正在使用的、善良的您能动一动小手指,把文章转发一下,让更多人知道 .NET 有这样一个好用的 ORM 存在。谢谢!!

FreeSql 使用最宽松的开源协议 MIT https://github.com/dotnetcore/FreeSql ,完全可以商用,文档齐全,甚至拿去卖钱也可以。QQ群:4336577(已满)、8578575(在线)、52508226(在线)

声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com