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 升級不少人翻了車,有心中那些情懷在,翻了車最可能是罵幾句而已,罵完還得接着用它)