zoukankan      html  css  js  c++  java
  • FreeSql v0.11 几个实用功能说明

    FreeSql 开源发布快一年了,立志成为 .Net 平台方便好用的 ORM,仓库地址:https://github.com/2881099/FreeSql

    随着不断的迭代更新,越来越稳定,也越来越强大。预计在一周年的时候(2020年1月1日)发布 1.0 正式版本。

    金九银十的日子过去了,在这个铜一般的月份里,鄙人做了几个重大功能,希望对使用者开发提供更大的便利。

    • 一、Dto 映射查询
    • 二、IncludeMany 联级加载
    • 三、Where(a => true) 逻辑表达式解析优化
    • 四、SaveManyToMany 联级保存多对多集合属性
    • 五、迁移实体 - 到指定表名
    • 六、MySql 特有功能 On Duplicate Key Update,和 Pgsql upsert
    • 七、ISelect.ToDelete 高级删除
    • 八、全局过滤器

    以下的代码,先决定义代码如下 :

    IFreeSql fsql = new FreeSql.FreeSqlBuilder()
        .UseConnectionString(FreeSql.DataType.Sqlite, @"Data Source=|DataDirectory|db1.db;Max Pool Size=10";)
        .UseAutoSyncStructure(true) //自动同步实体结构到数据库
        .Build();
    
    public class Blog
    {
        public Guid Id { get; set; }
        public string Url { get; set; }
        public int Rating { get; set; }
    }
    

    一、Dto 映射查询

    class Dto
    {
        public Guid Id { get; set; }
        public string Url { get; set; }
        public int xxx { get; set; }
    }
    
    fsql.Select<Blog>().ToList<Dto>();
    //SELECT Id, Url FROM Blog
    
    fsql.Select<Blog>().ToList(a => new Dto { xxx = a.Rating} );
    //SELECT Id, Url, Rating as xxx FROM Blog
    //这样写,附加所有映射,再额外映射 xxx
    
    fsql.Select<Blog>().ToList(a => new Blog { Id = a.Id }) 
    //这样写,只查询 id
    
    fsql.Select<Blog>().ToList(a => new { a.Id }) 
    //这样写,只查询 id,返回匿名对象
    

    映射支持单表/多表,是在查询数据之前映射(不是先查询所有字段再到内存映射)

    查找规则,查找属性名,会循环内部对象 _tables(join 查询后会增长),以 主表优先查,直到查到相同的字段。

    如:

    A, B, C 都有 id,Dto { id, a1, a2, b1, b2 },A.id 被映射。也可以指定 id = C.id 映射。

    友情提醒:在 dto 可以直接映射一个导航属性


    二、IncludeMany 联级加载

    之前已经实现,有设置关系,和未设置关系 的导航集合属性联级加载。

    有设置关系的(支持一对多、多对多):

    fsql.Select<Tag>().IncludeMany(a => a.Goods).ToList();
    

    未设置关系的,临时指定关系(只支持一对多):

    fsql.Select<Goods>().IncludeMany(a => a.Comment.Where(b => b.TagId == a.Id));
    

    只查询每项子集合的前几条数据,避免像EfCore加载所有数据导致IO性能低下(比如某商品下有2000条评论):

    fsql.Select<Goods>().IncludeMany(a => a.Comment.Take(10));
    

    上面已有的 IncludeMany 功能还不够自由灵活。

    新功能1:在 Dto 上做映射 IncludeMany

    老的 IncludeMany 限制只能在 ISelect 内使用,必须要先查上级数据,解决这个问题我们做了直接在 Dto 上做映射:

    查询 Goods 商品表,分类1、分类2、分类3 各10条数据

    //定义临时类,也可以是 Dto 类
    class Dto {
        public int TypeId { get; set; }
        public List<Goods > GoodsList { get; set; }
    }
    
    var dto = new [] { 1,2,3 }.Select(a => new Dto { TypeId = a }).ToList();
    dto.IncludeMany(d => d.GoodsList.Take(10).Where(gd => gd.TypeId == d.TypeId));
    
    //执行后,dto 每个元素.Vods 将只有 10条记录
    

    现在 IncludeMany 不再是 ISelect 的专利,普通的 List<T> 也可以用它来贪婪加载数据,并准确填充到内部各元素中。

    新功能2:查询子集合表的指定字段

    老的 IncludeMany 限制只能查子表的所有字段,子表过段多过的话比较浪费 IO 性能。

    新功能可以设置子集合返回部分字段,避免子集合字段过多的问题。

    fsql.Select<Tag>().IncludeMany(a => a.Goods.Select(b => new Goods { Id = b.Id, Title = b.Title }));
    //只查询 goods 表 id, title 字段,再作填充
    

    三、Where(a => true) 逻辑表达式解析优化

    相信很多 ORM 解析表达式的时候处理不了这个问题,我们之前已经解决了 99%。

    这个月发现还有一余孽未清,发现问题后及时解决了,并增加单元测试代码以绝后患。

    四、SaveManyToMany 联级保存多对多集合属性

    在此之前,FreeSql.DbContext 和 仓储实现,已经实现了联级保存功能,如下:

    联级保存功能可实现保存对象的时候,将其【OneToMany】、【ManyToMany】导航属性集合也一并保存。

    全局关闭:

    fsql.SetDbContextOptions(opt => opt.EnableAddOrUpdateNavigateList = false);
    

    局部关闭:

    var repo = fsql.GetRepository<T>();
    repo.DbContextOptions.EnableAddOrUpdateNavigateList = false;
    

    新功能:

    保存实体的指定【多对多】导航属性,SaveManyToMany 方法实现在 BaseRepository、DbContext。

    解决问题:当实体类导航数据过于复杂的时候,选择关闭联级保存的功能是明智之选,但是此时【多对多】数据保存功能写起来非常繁琐麻烦(因为要与现有数据对比后保存)。

    var song = new Song { Id = 1 };
    song.Tags = new List<Tag>();
    song.Tags.Add(new Tag ...);
    song.Tags.Add(new Tag ...);
    song.Tags.Add(new Tag ...);
    repo.SaveManyToMany(song, "Tags");
    //轻松保存 song 与 tag 表的关联
    

    机制规则与联级保存的【多对多】一样,如下:

    我们对中间表的保存是完整对比操作,对外部实体的操作只作新增(注意不会更新)

    • 属性集合为空时,删除他们的所有关联数据(中间表)
    • 属性集合不为空时,与数据库存在的关联数据(中间表)完全对比,计算出应该删除和添加的记录

    五、迁移实体 - 到指定表名

    fsql.CodeFirst.SyncStructure(typeof(Log), "Log_1"); //迁移到 Log_1 表
    fsql.CodeFirst.SyncStructure(typeof(Log), "Log_2"); //迁移到 Log_2 表
    

    在此功能上,我们对分表功能做了点升级,以下动作都会做迁移动作:

    fsql.Select<Log>().AsTable((_, oldname) => $"{oldname}_1");
    fsql.GetRepository<Log>(null, oldname => $"{oldname}_1");
    

    六、MySql 特有功能 On Duplicate Key Update,和 Pgsql upsert

    FreeSql 提供了多种插入或更新方法,v0.11 之前主要使用 FreeSql.Repository/FreeSql.DbContext 库提供的方法实现。

    FreeSql.Repository 之 InsertOrUpdate

    此方法与 FreeSql.DbContext AddOrUpdate 方法功能一样。

    var repo = fsql.GetRepository<T>();
    repo.InsertOrUpdate(实体);
    

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

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

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

    缺点:不支持批量操作

    新功能:MySql 特有功能 On Duplicate Key Update

    FreeSql.Provider.MySql 和 FreeSql.Provider.MySqlConnector 在 v0.11.11 版本已支持 MySql 特有的功能,On Duplicate Key Update。

    这个功能也可以实现插入或更新数据,并且支持批量操作。

    class TestOnDuplicateKeyUpdateInfo
    {
        [Column(IsIdentity = true)]
        public int id { get; set; }
        public string title { get; set; }
        public DateTime time { get; set; }
    }
    
    var item = new TestOnDuplicateKeyUpdateInfo { id = 100, title = "title-100", time = DateTime.Parse("2000-01-01") };
    fsql.Insert(item)
        .NoneParameter()
        .OnDuplicateKeyUpdate().ToSql();
    //INSERT INTO `TestOnDuplicateKeyUpdateInfo`(`id`, `title`, `time`) VALUES(100, 'title-100', '2000-01-01 00:00:00.000')
    //ON DUPLICATE KEY UPDATE
    //`title` = VALUES(`title`), 
    //`time` = VALUES(`time`)
    

    OnDuplicateKeyUpdate() 之后可以调用的方法:

    方法名 描述
    IgnoreColumns 忽略更新的列,机制和 IUpdate.IgnoreColumns 一样
    UpdateColumns 指定更新的列,机制和 IUpdate.UpdateColumns 一样
    Set 手工指定更新的列,与 IUpdate.Set 功能一样
    SetRaw 作为 Set 方法的补充,可传入 SQL 字符串
    ToSql 返回即将执行的 SQL 语句
    ExecuteAffrows 执行,返回影响的行数

    IInsert 与 OnDuplicateKeyUpdate 都有 IgnoreColumns、UpdateColumns 方法。

    当插入实体/集合实体的时候,忽略了 time 列,代码如下:

    fsql.Insert(item)
        .IgnoreColumns(a => a.time)
        .NoneParameter()
        .OnDuplicateKeyUpdate().ToSql();
    //INSERT INTO `TestOnDuplicateKeyUpdateInfo`(`id`, `title`) VALUES(200, 'title-200')
    //ON DUPLICATE KEY UPDATE
    //`title` = VALUES(`title`), 
    //`time` = '2000-01-01 00:00:00.000'
    

    我们发现,UPDATE time 部分变成了常量,而不是 VALUES(`time`),机制如下:

    当 insert 部分中存在的列,在 update 中将以 VALUES(`字段`) 的形式设置;

    当 insert 部分中不存在的列,在 update 中将为常量形式设置,当操作实体数组的时候,此常量为 case when ... end 执行(与 IUpdate 一样);

    新功能2:PostgreSQL 特有功能 On Conflict Do Update

    使用方法 MySql OnDuplicateKeyUpdate 大致相同。


    七、ISelect.ToDelete 高级删除

    默认 IDelete 不支持导航对象,多表关联等。ISelect.ToDelete 可将查询转为删除对象,以便支持导航对象或其他查询功能删除数据,如下:

    fsql.Select<T1>().Where(a => a.Options.xxx == 1).ToDelete().ExecuteAffrows();
    

    注意:此方法不是将数据查询到内存循环删除,上面的代码产生如下 SQL 执行:

    DELETE FROM `T1` WHERE id in (select a.id from T1 a left join Options b on b.t1id = a.id where b.xxx = 1)
    

    复杂删除使用该方案的好处:

    • 删除前可预览测试数据,防止错误删除操作;
    • 支持更加复杂的删除操作(IDelete 默认只支持简单的操作),甚至在 ISelect 上使用 Limit(10) 将只删除附合条件的前 10条记录;

    还有 ISelect.ToUpdate 高级更新数据功能,使用方法类似


    八、全局过滤器

    FreeSql 基础层实现了 Select/Update/Delete 可设置的全局过滤器功能。

    public static AsyncLocal<Guid> TenantId { get; set; } = new AsyncLocal<Guid>();
    
    fsql.GlobalFilter
        .Apply<TestAddEnum>("test1", a => a.Id == TenantId.Value)
        .Apply<AuthorTest>("test2", a => a.Id == 111)
        .Apply<AuthorTest>("test3", a => a.Name == "11");
    

    Apply 泛型参数可以设置为任何类型,当使用 Select/Update/Delete 方法时会进行过滤器匹配尝试(try catch):

    • 匹配成功的,将附加 where 条件;
    • 匹配失败的,标记下次不再匹配,避免性能损耗;

    如何禁用?

    fsql.Select<TestAddEnum>().ToList(); //所有生效
    fsql.Select<TestAddEnum>().DisableGlobalFilter("test1").ToList(); //禁用 test1
    fsql.Select<TestAddEnum>().DisableGlobalFilter().ToList(); //禁用所有
    

    fsql.Update/Delete 方法效果同上。

    注意:IFreeSql.GlobalFilter 与 仓储过滤器 不是一个功能,可以同时生效

    鸣谢

    感谢反馈 bug 的朋友!

    仓库地址:https://github.com/2881099/FreeSql

    请移步更新日志:https://github.com/2881099/FreeSql/wiki/更新日志

  • 相关阅读:
    微服务简介
    Apache httpd.conf
    搭建PHP开发环境
    搭建Apache开发环境
    Swift 项目编译优化(一)
    用Flutter 写一个简单页面
    Sign In With Apple(一)(转)
    Xcode DeviceSupport
    MQTT初始篇笔记整理
    UITableView使用过程中可能遇到的问题
  • 原文地址:https://www.cnblogs.com/kellynic/p/11881941.html
Copyright © 2011-2022 走看看