FreeSql 导航属性的联级保存功能
写在前面
FreeSql 一个款 .net 平台下支持 .net framework 4.5+、.net core 2.1+ 的开源 ORM。单元测试超过3100+,正在不断吸引新的开发者,生命不息开发不止。
和 EFCore 一样,我们也有导航对象,支持【OneToOne】(一对一)、【ManyToOne】(多对一)、【OneToMany】(一对多)、【ParentChild】(父子)、【ManyToMany】(多对多),可以约定配置或手工配置实体间的关联,也可以使用 fluent api 设置关联。
联级保存功能可实现保存对象的时候,将其【OneToMany】、【ManyToMany】导航属性集合也一并保存,本文档说明实现的机制防止误用。
机制规则
【一对多】模型下, 保存时可联级保存实体的属性集合。出于使用安全考虑我们没做完整对比,只实现实体属性集合的添加或更新操作,所以不会删除实体属性集合的数据。
完整对比的功能使用起来太危险,试想下面的场景:
保存的时候,实体的属性集合是空的,如何操作?记录全部删除?保存的时候,由于数据库中记录非常之多,那么只想保存子表的部分数据,或者只需要添加,如何操作?
【多对多】模型下,我们对中间表的保存是完整对比操作,对外部实体的操作只作新增(注意不会更新)
属性集合为空时,删除他们的所有关联数据(中间表)属性集合不为空时,与数据库存在的关联数据(中间表)完整对比,计算出应该删除和添加的记录
功能开启和关闭
IFreeSql fsql=new FreeSql.FreeSqlBuilder()
.UseConnectionString(FreeSql.DataType.Sqlite, "Data Source=|DataDirectory|/document22.db;Pooling=true;Max Pool Size=10")
.UseAutoSyncStructure(true) //自动同步结构到数据库
.UseMonitorCommand(cmd=> Trace.WriteLine(cmd.CommandText)) //监听SQL命令对象,在执行后
.Build();
使用 FreeSqlBuilder 创建好的 IFreeSql 对象,联级保存功能,默认是打开的。
全局关闭:
fsql.SetDbContextOptions(opt=> opt.EnableAddOrUpdateNavigateList=false);
局部关闭:
var repo=fsql.GetRepository();
repo.DbContextOptions.EnableAddOrUpdateNavigateList=false;
一对多(OneToMany)代码测试
为了方便展示,以下是一个 ParentChild 关系,其实他也是 OneToMany,只不过是自己指向自己。
[Table(Name="EAUNL_OTMP_CT")]
class CagetoryParent
{
public Guid Id { get; set; }
public string Name { get; set; }
public Guid ParentId { get; set; }
[Navigate("ParentId")]
public List Childs { get; set; }
}
初始化测试数据:
var cts=new[] {
new CagetoryParent
{
Name="分类1",
Childs=new List(new[]
{
new CagetoryParent { Name="分类1_1" },
new CagetoryParent { Name="分类1_2" },
new CagetoryParent { Name="分类1_3" }
})
},
new CagetoryParent
{
Name="分类2",
Childs=new List(new[]
{
new CagetoryParent { Name="分类2_1" },
new CagetoryParent { Name="分类2_2" }
})
}
};
1、执行批量插入:
var repo=g.sqlite.GetRepository();
repo.Insert(cts);
初始执行该方法时,会执行自动创建数据库表操作。如果表已存在,则执行对比,若无变化则不执行操作。
经过断点调试,在控制台可以看到输出 SQL 内容为:
INSERT INTO "EAUNL_OTMP_CT"("Id", "Name", "ParentId") VALUES('5d90afcb-ed57-f6f4-0082-cb6b78eaaf9f', '分类1', '00000000-0000-0000-0000-000000000000'), ('5d90afcb-ed57-f6f4-0082-cb6c5b531b3e', '分类2', '00000000-0000-0000-0000-000000000000')
INSERT INTO "EAUNL_OTMP_CT"("Id", "Name", "ParentId") VALUES('5d90afcb-ed57-f6f4-0082-cb6d0c1c5f1a', '分类1_1', '5d90afcb-ed57-f6f4-0082-cb6b78eaaf9f'), ('5d90afcb-ed57-f6f4-0082-cb6e74bd8eef', '分类1_2', '5d90afcb-ed57-f6f4-0082-cb6b78eaaf9f'), ('5d90afcb-ed57-f6f4-0082-cb6f6267cc5f', '分类1_3', '5d90afcb-ed57-f6f4-0082-cb6b78eaaf9f'), ('5d90afcb-ed57-f6f4-0082-cb7057c41d46', '分类2_1', '5d90afcb-ed57-f6f4-0082-cb6c5b531b3e'), ('5d90afcb-ed57-f6f4-0082-cb7156e0375e', '分类2_2', '5d90afcb-ed57-f6f4-0082-cb6c5b531b3e')
2、测试批量修改:
cts[0].Name="分类11";
cts[0].Childs.Clear();
cts[1].Name="分类22";
cts[1].Childs.Clear();
repo.Update(cts);
控制台看到输出 SQL 内容为:
UPDATE "EAUNL_OTMP_CT" SET "Name"=CASE "Id"
WHEN '5d90afcb-ed57-f6f4-0082-cb6b78eaaf9f' THEN '分类11'
WHEN '5d90afcb-ed57-f6f4-0082-cb6c5b531b3e' THEN '分类22' END
WHERE ("Id" IN ('5d90afcb-ed57-f6f4-0082-cb6b78eaaf9f','5d90afcb-ed57-f6f4-0082-cb6c5b531b3e'))
Childs.Clear 执行了,但是控制台没有输出执行删除子集合语句,说明没有做完整的对比
3、子集合表已存在数据,继续添加数据
cts[0].Name="分类111";
cts[0].Childs.Clear();
cts[0].Childs.Add(new CagetoryParent { Name="分类1_33" });
cts[1].Name="分类222";
cts[1].Childs.Clear();
cts[1].Childs.Add(new CagetoryParent { Name="分类2_22" });
repo.Update(cts);
控制台看到输出 SQL 内容为:
UPDATE "EAUNL_OTMP_CT" SET "Name"=CASE "Id"
WHEN '5d90afcb-ed57-f6f4-0082-cb6b78eaaf9f' THEN '分类111'
WHEN '5d90afcb-ed57-f6f4-0082-cb6c5b531b3e' THEN '分类222' END
WHERE ("Id" IN ('5d90afcb-ed57-f6f4-0082-cb6b78eaaf9f','5d90afcb-ed57-f6f4-0082-cb6c5b531b3e'))
INSERT INTO "EAUNL_OTMP_CT"("Id", "Name", "ParentId") VALUES('5d90afe8-ed57-f6f4-0082-cb725df546ea', '分类1_33', '5d90afcb-ed57-f6f4-0082-cb6b78eaaf9f'), ('5d90afe8-ed57-f6f4-0082-cb7338a6214c', '分类2_22', '5d90afcb-ed57-f6f4-0082-cb6c5b531b3e')
再一次验证了【一对多】(OneToMany) 不会作完整对比,只会添加或更新,添加测试数据的时候用它能简化好多代码。
多对多(ManyToMany)代码测试
以下我们创建了三个类,Song 为本体类,Tag 为外部类,SongTag 为 中间关联数据类,采用命名约定的方式进行了导航关系设置。
[Table(Name="EAUNL_MTM_SONG")]
class Song
{
public Guid Id { get; set; }
public string Name { get; set; }
public List Tags { get; set; }
}
[Table(Name="EAUNL_MTM_TAG")]
class Tag
{
public Guid Id { get; set; }
public string TagName { get; set; }
public List Songs { get; set; }
}
[Table(Name="EAUNL_MTM_SONGTAG")]
class SongTag
{
public Guid SongId { get; set; }
public Song Song { get; set; }
public Guid TagId { get; set; }
public Tag Tag { get; set; }
}
初始化测试数据:
var tags=new[] {
new Tag { TagName="流行" },
new Tag { TagName="80后" },
new Tag { TagName="00后" },
new Tag { TagName="摇滚" }
};
var ss=new[]
{
new Song
{
Name="爱你一万年.mp3",
Tags=new List(new[]
{
tags[0], tags[1]
})
},
new Song
{
Name="李白.mp3",
Tags=new List(new[]
{
tags[0], tags[2]
})
}
};
1、执行批量插入:
var repo=g.sqlite.GetRepository();
repo.Insert(ss);
初始执行该方法时,会执行自动创建数据库表操作。如果表已存在,则执行对比,若无变化则不执行操作。
经过断点调试,在控制台可以看到输出 SQL 内容为:
INSERT INTO "EAUNL_MTM_SONG"("Id", "Name") VALUES('5d90fdb3-6a6b-2c58-00c8-37974177440d', '爱你一万年.mp3'), ('5d90fdb3-6a6b-2c58-00c8-37987f29b197', '李白.mp3')
INSERT INTO "EAUNL_MTM_TAG"("Id", "TagName") VALUES('5d90fdb7-6a6b-2c58-00c8-37991ead4f05', '流行'), ('5d90fdbd-6a6b-2c58-00c8-379a0432a09c', '80后')
INSERT INTO "EAUNL_MTM_SONGTAG"("SongId", "TagId") VALUES('5d90fdb3-6a6b-2c58-00c8-37974177440d', '5d90fdb7-6a6b-2c58-00c8-37991ead4f05'), ('5d90fdb3-6a6b-2c58-00c8-37974177440d', '5d90fdbd-6a6b-2c58-00c8-379a0432a09c')
INSERT INTO "EAUNL_MTM_TAG"("Id", "TagName") VALUES('5d90fdcc-6a6b-2c58-00c8-379b5af59d25', '00后')
INSERT INTO "EAUNL_MTM_SONGTAG"("SongId", "TagId") VALUES('5d90fdb3-6a6b-2c58-00c8-37987f29b197', '5d90fdb7-6a6b-2c58-00c8-37991ead4f05'), ('5d90fdb3-6a6b-2c58-00c8-37987f29b197', '5d90fdcc-6a6b-2c58-00c8-379b5af59d25')
2、测试批量更新,并且中间表数据有了变化
ss[0].Name="爱你一万年.mp5";
ss[0].Tags.Clear();
ss[0].Tags.Add(tags[0]);
ss[1].Name="李白.mp5";
ss[1].Tags.Clear();
ss[1].Tags.Add(tags[3]);
repo.Update(ss);
控制台看到输出 SQL 内容为:
UPDATE "EAUNL_MTM_SONG" SET "Name"=CASE "Id"
WHEN '5d90fdb3-6a6b-2c58-00c8-37974177440d' THEN '爱你一万年.mp5'
WHEN '5d90fdb3-6a6b-2c58-00c8-37987f29b197' THEN '李白.mp5' END
WHERE ("Id" IN ('5d90fdb3-6a6b-2c58-00c8-37974177440d','5d90fdb3-6a6b-2c58-00c8-37987f29b197'))
SELECT a."SongId", a."TagId"
FROM "EAUNL_MTM_SONGTAG" a
WHERE (a."SongId"='5d90fdb3-6a6b-2c58-00c8-37974177440d')
DELETE FROM "EAUNL_MTM_SONGTAG" WHERE ("SongId"='5d90fdb3-6a6b-2c58-00c8-37974177440d' AND "TagId"='5d90fdbd-6a6b-2c58-00c8-379a0432a09c')
INSERT INTO "EAUNL_MTM_TAG"("Id", "TagName") VALUES('5d90febd-6a6b-2c58-00c8-379c21acfc72', '摇滚')
SELECT a."SongId", a."TagId"
FROM "EAUNL_MTM_SONGTAG" a
WHERE (a."SongId"='5d90fdb3-6a6b-2c58-00c8-37987f29b197')
DELETE FROM "EAUNL_MTM_SONGTAG" WHERE ("SongId"='5d90fdb3-6a6b-2c58-00c8-37987f29b197' AND "TagId"='5d90fdb7-6a6b-2c58-00c8-37991ead4f05' OR "SongId"='5d90fdb3-6a6b-2c58-00c8-37987f29b197' AND "TagId"='5d90fdcc-6a6b-2c58-00c8-379b5af59d25')
INSERT INTO "EAUNL_MTM_SONGTAG"("SongId", "TagId") VALUES('5d90fdb3-6a6b-2c58-00c8-37987f29b197', '5d90febd-6a6b-2c58-00c8-379c21acfc72')
执行的过程如下:
第一步,批量更新 song 数据第二步,由于是 song 是更新操作,所以需要先查出 song 的关联数据第三步,删除 song 的关联数据(tags[0] 除外),因为 tags[0] 是本次保存有的数据,直白的说就是删除非本次保存的所有关联数据第四步,添加 tags[3] 摇滚外部数据,因为它还不存在外部表第五步,与第二步相同第六步,与第三步相同第七步,插入中间表数据,李白.mp5 与 摇滚 关联
为什么会有这么多步呢?原因是 song 测试数据是两条,double 了,如果单条记录大概是 4-5 条,取决于是否有新增的关联数据需要添加。
3、测试清空关联数据
ss[0].Name="爱你一万年.mp4";
ss[0].Tags.Clear();
ss[1].Name="李白.mp4";
ss[1].Tags.Clear();
repo.Update(ss);
控制台看到输出 SQL 内容为:
DELETE FROM "EAUNL_MTM_SONGTAG" WHERE ("SongId"='5d90fdb3-6a6b-2c58-00c8-37974177440d')
DELETE FROM "EAUNL_MTM_SONGTAG" WHERE ("SongId"='5d90fdb3-6a6b-2c58-00c8-37987f29b197')
UPDATE "EAUNL_MTM_SONG" SET "Name"=CASE "Id"
WHEN '5d90fdb3-6a6b-2c58-00c8-37974177440d' THEN '爱你一万年.mp4'
WHEN '5d90fdb3-6a6b-2c58-00c8-37987f29b197' THEN '李白.mp4' END
WHERE ("Id" IN ('5d90fdb3-6a6b-2c58-00c8-37974177440d','5d90fdb3-6a6b-2c58-00c8-37987f29b197'))
再一次证明【ManyToMany】(多对多) 模型下,中间表是完整的对比操作,外部表只会插入,不更新。
导航对象
除了联级保存功能外,导航对象的主要设计目的为快速在实体间点点点穿插,以便执行 lambda 表达式的查询操作。
如何自定义导航关系?
//导航属性,OneToMany
[Navigate("song_id")]
public virtual List Obj_song_tag { get; set; }
//导航属性,ManyToOne/OneToOne
[Navigate("song_id")]
public virtual Song Obj_song { get; set; }
//导航属性,ManyToMany
[Navigate(ManyToMany=typeof(tag_song))]
public virtual List tags { get; set; }
可约定,可不约定;不约定的,需指定 Navigate 特性关联;无关联的,查询时可以指明 On 条件,LeftJoin(a=> a.Parent.Id==a.ParentId);已关联的,直接使用导航对象就行,On 条件会自动附上;
也可以使用 FluentApi 在外部设置导航关系:
fsql.CodeFirst.ConfigEntity<实体类>(a=> a
.Navigate(b=> b.roles, null, typeof(多对多中间实体类))
.Navigate(b=> b.users, "uid")
);
优先级,特性 > FluentApi
写在最后
FreeSql 发布已经10个月了,元旦将发布 1.0 正式版,希望将来可以成为 .net 社区下给力的轮子,也算是我不枉十几年对 .net 不离不弃的一点贡献吧。
希望 FreeSql 越来越好,
FreeSql 导航属性的联级保存功能相关推荐
- FreeSql (十八)导航属性
导航属性是 FreeSql 的特色功能之一,可通过约定配置.或自定义配置对象间的关系. 导航属性有 OneToMany, ManyToOne, ManyToMany, OneToOne, Parent ...
- FreeSql (十七)联表查询
FreeSql在查询数据下足了功能,链式查询语法.多表查询.表达式函数支持得非常到位. IFreeSql fsql = new FreeSql.FreeSqlBuilder().UseConnecti ...
- 关于Entity Framework自动关联查询与自动关联更新导航属性对应的实体注意事项说明...
一.首先了解下Entity Framework 自动关联查询: Entity Framework 自动关联查询,有三种方法:Lazy Loading(延迟加载),Eager Loading(预先加载) ...
- 富文本编辑器宽度自适应及取消自动保存功能
1.富文本编辑器宽度自适应设置 初始化富文本编辑器是 设置属性 initialFrameWidth: '100%' 2.富文本编辑器取消自动保存功能 在ueditor.all.js中找到 UE.reg ...
- 联级阴影贴图CSM(Cascaded shadow map)原理与实现
联级阴影贴图CSM(Cascaded shadow map)原理与实现 CSM是利用分层的ShadowMap技术,实现大场景的阴影算法.示意图如下图: 我们通过给眼视锥分片,为每个分片生成一个相同分辨 ...
- 模拟实现图片长按保存功能
关于图片的长按保存(原生代码暂未集成,先记录一下准备工作) 最近要实现图片的长按保存功能,百度了好多,单由前端实现的方法网上也有,不过看上去好麻烦,头疼,后来有看到博文说原生实现会简单些,咨询了老板, ...
- 第十七节:两片联级74HC595驱动16个LED灯的基本驱动程序
. 开场白: 上一节讲了如何把矩阵键盘翻译成独立按键的处理方式.这节讲74HC595的驱动程序.要教会大家两个知识点: 第一点:朱兆祺的学习板是用74HC595控制LED,因此可以直接把595的OE引 ...
- 【Proteus仿真】2片CD4026联级秒脉冲2位数码管计数
[Proteus仿真]2片CD4026联级秒脉冲2位数码管计数 相关篇<[Proteus仿真]CD4026秒脉冲0-9循环计数> Proteus仿真演示 演示的是利用CD4026驱动2片共 ...
- efcore 实体配置_C# 数据操作系列 - 7. EF Core 导航属性配置
在上一篇,大概介绍了Entity Framework Core关于关系映射的逻辑.在上一篇中留下了EF的外键映射没有说,也就是一对一,一对多,多对一,多对多的关系等.这一篇将为大家细细分析一下,如何设 ...
- 兼容性—IE6/7下带有overflow:hidden属性的父级元素包不住带有position:relative属性的子元素...
IE6/7下带有overflow:hidden属性的父级元素包不住带有position:relative属性的子元素 <!DOCTYPE html> <html lang=" ...
最新文章
- 2、运行.py文件、字符串、保留字符、行和缩进、多行语句、引号、注释、等待用户输入、同一行显示多条语句、命令行参数
- 应用深度学习(台大陈蕴侬李宏毅) Part1
- java开发中spring常用的工具类
- SQL查询提速秘诀,避免锁死数据库的数据库代码
- 机器学习:用正规方程法求解线性回归
- 随机化算法-数值随机化算法
- Playing with OS(操作系统)
- 用SQL语句可以取出中文汉字的首字母
- 080 matplolib模块
- 2019年网络工程师考试大纲
- FPGA控制ADF4351实现2MS的扫频操作
- 计算机c盘装什么,电脑只有一个C盘!怎么为电脑重装系统?
- RHCE(一、二)nmcli命令学习、Linux例行性工作命令atd和crond学习
- 简单分析一个通过 js 劫持进行案例
- 仿高德地图首页效果,简单代码实现
- 新农慕课python答案、第七周_优学院《作业治疗》完整答案中国大学慕课《农作学》课后作业参考答案...
- 【笔记】python中的for循环(遍历列表)、for循环中的一些缩进问题
- Unity VR开发教程 OpenXR+XR Interaction Toolkit(七)射线抓取
- [转帖]美国《工程索引》收录中国科技论文的最新规定
- [BZOJ1116] CLO