FreeSql 一个款 .net 平台下支持 .net framework 4.5+、.net core 2.1+ 的开源 ORM。单元测试超过3100+,正在不断吸引新的开发者,生命不息开发不止。html
和 EFCore 同样,咱们也有导航对象,支持【OneToOne】(一对一)、【ManyToOne】(多对一)、【OneToMany】(一对多)、【ParentChild】(父子)、【ManyToMany】(多对多),能够约定配置或手工配置实体间的关联,也可使用 fluent api 设置关联。git
联级保存功能可实现保存对象的时候,将其【OneToMany】、【ManyToMany】导航属性集合也一并保存,本文档说明实现的机制防止误用。github
【一对多】模型下, 保存时可联级保存实体的属性集合。出于使用安全考虑咱们没作完整对比,只实现实体属性集合的添加或更新操做,因此不会删除实体属性集合的数据。sql
完整对比的功能使用起来太危险,试想下面的场景:数据库
【多对多】模型下,咱们对中间表的保存是完整对比操做,对外部实体的操做只做新增(注意不会更新)api
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<T>(); repo.DbContextOptions.EnableAddOrUpdateNavigateList = false;
为了方便展现,如下是一个 ParentChild 关系,其实他也是 OneToMany,只不过是本身指向本身。ui
[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<CagetoryParent> Childs { get; set; } }
初始化测试数据:
var cts = new[] { new CagetoryParent { Name = "分类1", Childs = new List<CagetoryParent>(new[] { new CagetoryParent { Name = "分类1_1" }, new CagetoryParent { Name = "分类1_2" }, new CagetoryParent { Name = "分类1_3" } }) }, new CagetoryParent { Name = "分类2", Childs = new List<CagetoryParent>(new[] { new CagetoryParent { Name = "分类2_1" }, new CagetoryParent { Name = "分类2_2" } }) } };
一、执行批量插入:
var repo = g.sqlite.GetRepository<CagetoryParent>(); 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')
二、测试批量修改:
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 执行了,可是控制台没有输出执行删除子集合语句,说明没有作完整的对比
三、子集合表已存在数据,继续添加数据
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) 不会做完整对比,只会添加或更新,添加测试数据的时候用它能简化好多代码。
如下咱们建立了三个类,Song 为本体类,Tag 为外部类,SongTag 为 中间关联数据类,采用命名约定的方式进行了导航关系设置。
[Table(Name = "EAUNL_MTM_SONG")] class Song { public Guid Id { get; set; } public string Name { get; set; } public List<Tag> Tags { get; set; } } [Table(Name = "EAUNL_MTM_TAG")] class Tag { public Guid Id { get; set; } public string TagName { get; set; } public List<Song> 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<Tag>(new[] { tags[0], tags[1] }) }, new Song { Name = "李白.mp3", Tags = new List<Tag>(new[] { tags[0], tags[2] }) } };
一、执行批量插入:
var repo = g.sqlite.GetRepository<Song>(); 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')
二、测试批量更新,而且中间表数据有了变化
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 测试数据是两条,double 了,若是单条记录大概是 4-5 条,取决因而否有新增的关联数据须要添加。
三、测试清空关联数据
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<song_tag> 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<tag> tags { get; set; }
也可使用 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 愈来愈好,
原 .net core 愈来愈好!(虽然 3.0 升级不少人翻了车,有心中那些情怀在,翻了车最可能是骂几句而已,骂完还得接着用它)