废话开头
这篇文章是我有史以来编辑最长时间的,历时 4小时!!!原本我可以利用这 4小时编写一堆胶水代码,真心希望善良的您点个赞,谢谢了!!
很久很久没有写文章了,上一次还是在元旦发布 1.0 版本的时候,今年版本规划是每月底发布小版本(年底发布 2.0),全年的开源工作主要是收集用户需求增加功能,完善测试,修复 bug。FreeSql 1.0 -> 1.5 相隔半年有哪些新功能?只能说每个功能都能让我兴奋,并且能感受到使用者也一样兴奋(妄想症)。
迫不及待的人会问,这更新速度也太快了吧,升级会不会有问题?
- 不了解版本的更新日志,直接升级不是好的习惯,建议关注我们的更新日志(github 上有专门的文档);
- 我们的版本开发原则:在尽量保证兼容的情况下,增加新功能,砍掉少量不合理的功能;
- 我们的单元测试数量:4000+,这是我们引以自豪,发布版本的保障;
入戏准备
FreeSql 是 .Net ORM,能支持 .NetFramework4.0+、.NetCore、Xamarin、XAUI、Blazor、以及还有说不出来的运行平台,因为代码绿色无依赖,支持新平台非常简单。目前单元测试数量:4000+,Nuget下载数量:123K+,源码几乎每天都有提交。值得高兴的是 FreeSql 加入了 ncc 开源社区:https://github.com/dotnetcore/FreeSql,加入组织之后社区责任感更大,需要更努力做好品质,为开源社区出一份力。QQ开发群:4336577
为什么要重复造轮子?
FreeSql 主要优势在于易用性上,基本是开箱即用,在不同数据库之间切换兼容性比较好。作者花了大量的时间精力在这个项目,肯请您花半小时了解下项目,谢谢。
FreeSql 整体的功能特性如下:
- 支持 CodeFirst 对比结构变化迁移;
- 支持 DbFirst 从数据库导入实体类;
- 支持 丰富的表达式函数,自定义解析;
- 支持 批量添加、批量更新、BulkCopy;
- 支持 导航属性,贪婪加载、延时加载、级联保存;
- 支持 读写分离、分表分库,租户设计;
- 支持 MySql/SqlServer/PostgreSQL/Oracle/Sqlite/达梦/MsAccess;
1.0 -> 1.5 更新的重要功能如下:
一、UnitOfWorkManager 工作单元管理器,可实现 Spring 事务设计;
二、IFreeSql.InsertOrUpdate 实现批量保存,执行时根据数据库自动适配执行 merge into 或者 on duplicate key update;
三、ISelect.WhereDynamicFilter 方法实现动态过滤条件(与前端交互);
四、自动适配表达式解析 yyyyMMdd 常用 c# 日期格式化;
五、IUpdate.SetSourceIgnore 方法实现忽略属性值为 null 的字段;
六、FreeSql.Provider.Dameng 基于 DmProvider Ado.net 访问达梦数据库;
七、自动识别 EFCore 常用的实体特性,FreeSql.DbContext 拥有和 EFCore 高相似度的语法,并且支持 90% 相似的 FluentApi;
八、ISelect.ToTreeList 扩展方法查询数据,把配置父子导航属性的实体加工为树型 List;
九、BulkCopy 相关方法提升大批量数据插入性能;
十、Sqlite :memrory: 内存模式;
FreeSql 使用非常简单,只需要定义一个 IFreeSql 对象即可:
static IFreeSql fsql = new FreeSql.FreeSqlBuilder()
.UseConnectionString(FreeSql.DataType.MySql, connectionString)
.UseAutoSyncStructure(true) //自动同步实体结构到数据库
.Build(); //请务必定义成 Singleton 单例模式
一、UnitOfWorkManager 工作单元管理器
public class SongService
{
BaseRepository<Song> _repo;
public SongService(BaseRepository<Song> repo)
{
_repo = repo;
}
[Transactional]
public virtual void Test1()
{
_repo.Insert(new Song {
Title = "卡农1" }); //事务1
this.Test2();
}
[Transactional(Propagation = Propagation.Nested)] //嵌套事务,新的(不使用 Test1 的事务)
public virtual void Test2()
{
_repo.Insert(new Song {
Title = "卡农2" });
}
}
BaseRepository 是 FreeSql.BaseRepository 包实现的通用仓储类,实际项目中可以继承它再使用。
Propagation 的模式参考了 Spring 事务,在以下几种模式:
- Requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,默认的选择。
- Supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
- Mandatory:使用当前事务,如果没有当前事务,就抛出异常。
- NotSupported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- Never:以非事务方式执行操作,如果当前事务存在则抛出异常。
- Nested:以嵌套事务方式执行。(上面的例子使用的这个)
UnitOfWorkManager 正是干这件事的。避免了每次对数据操作都要现获得 Session 实例来启动事务/提交/回滚事务还有繁琐的Try/Catch操作。这些也是 AOP(面向切面编程)机制很好的应用。一方面使开发业务逻辑更清晰、专业分工更加容易进行。另一方面就是应用 AOP 隔离降低了程序的耦合性使我们可以在不同的应用中将各个切面结合起来使用大大提高了代码重用度。
使用前准备第一步:配置 Startup.cs 注入
//Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IFreeSql>(fsql);
services.AddScoped<UnitOfWorkManager>();
services.AddFreeRepository(null, typeof(Startup).Assembly);
}
UnitOfWorkManager 成员 | 说明 |
---|---|
IUnitOfWork Current | 返回当前的工作单元 |
void Binding(repository) | 将仓储的事务交给它管理 |
IUnitOfWork Begin(propagation, isolationLevel) | 创建工作单元 |
使用前准备第二步:定义事务特性
[AttributeUsage(AttributeTargets.Method)]
public class TransactionalAttribute : Attribute
{
/// <summary>
/// 事务传播方式
/// </summary>
public Propagation Propagation {
get; set; } = Propagation.Requierd;
/// <summary>
/// 事务隔离级别
/// </summary>
public IsolationLevel? IsolationLevel {
get; set; }
}
使用前准备第三步:引入动态代理库
在 Before 从容器中获取 UnitOfWorkManager,调用它的 var uow = uowManager.Begin(attr.Propagation, attr.IsolationLevel) 方法
在 After 调用 Before 中的 uow.Commit 或者 Rollback 方法,最后调用 uow.Dispose
自问自答:是不是进方法就开事务呢?
不一定是真实事务,有可能是虚的,就是一个假的 unitofwork(不带事务),也有可能是延用上一次的事务,也有可能是新开事务,具体要看传播模式。
二、IFreeSql.InsertOrUpdate 批量插入或更新
IFreeSql 定义了 InsertOrUpdate 方法实现批量插入或更新的功能,利用的是数据库特性进行保存,执行时根据数据库自动适配:
Database | Features |
---|---|
MySql | on duplicate key update |
PostgreSQL | on conflict do update |
SqlServer | merge into |
Oracle | merge into |
Sqlite | replace into |
Dameng | merge into |
fsql.InsertOrUpdate<T>()
.SetSource(items) //需要操作的数据
.ExecuteAffrows();
由于我们前面定义 fsql 变量的类型是 MySql,所以执行的语句大概是这样的:
INSERT INTO `T`(`id`, `name`) VALUES(1, '001'), (2, '002'), (3, '003'), (4, '004')
ON DUPLICATE KEY UPDATE
`name` = VALUES(`name`