写在前面

  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 导航属性的联级保存功能相关推荐

  1. FreeSql (十八)导航属性

    导航属性是 FreeSql 的特色功能之一,可通过约定配置.或自定义配置对象间的关系. 导航属性有 OneToMany, ManyToOne, ManyToMany, OneToOne, Parent ...

  2. FreeSql (十七)联表查询

    FreeSql在查询数据下足了功能,链式查询语法.多表查询.表达式函数支持得非常到位. IFreeSql fsql = new FreeSql.FreeSqlBuilder().UseConnecti ...

  3. 关于Entity Framework自动关联查询与自动关联更新导航属性对应的实体注意事项说明...

    一.首先了解下Entity Framework 自动关联查询: Entity Framework 自动关联查询,有三种方法:Lazy Loading(延迟加载),Eager Loading(预先加载) ...

  4. 富文本编辑器宽度自适应及取消自动保存功能

    1.富文本编辑器宽度自适应设置 初始化富文本编辑器是 设置属性 initialFrameWidth: '100%' 2.富文本编辑器取消自动保存功能 在ueditor.all.js中找到 UE.reg ...

  5. 联级阴影贴图CSM(Cascaded shadow map)原理与实现

    联级阴影贴图CSM(Cascaded shadow map)原理与实现 CSM是利用分层的ShadowMap技术,实现大场景的阴影算法.示意图如下图: 我们通过给眼视锥分片,为每个分片生成一个相同分辨 ...

  6. 模拟实现图片长按保存功能

    关于图片的长按保存(原生代码暂未集成,先记录一下准备工作) 最近要实现图片的长按保存功能,百度了好多,单由前端实现的方法网上也有,不过看上去好麻烦,头疼,后来有看到博文说原生实现会简单些,咨询了老板, ...

  7. 第十七节:两片联级74HC595驱动16个LED灯的基本驱动程序

    . 开场白: 上一节讲了如何把矩阵键盘翻译成独立按键的处理方式.这节讲74HC595的驱动程序.要教会大家两个知识点: 第一点:朱兆祺的学习板是用74HC595控制LED,因此可以直接把595的OE引 ...

  8. 【Proteus仿真】2片CD4026联级秒脉冲2位数码管计数

    [Proteus仿真]2片CD4026联级秒脉冲2位数码管计数 相关篇<[Proteus仿真]CD4026秒脉冲0-9循环计数> Proteus仿真演示 演示的是利用CD4026驱动2片共 ...

  9. efcore 实体配置_C# 数据操作系列 - 7. EF Core 导航属性配置

    在上一篇,大概介绍了Entity Framework Core关于关系映射的逻辑.在上一篇中留下了EF的外键映射没有说,也就是一对一,一对多,多对一,多对多的关系等.这一篇将为大家细细分析一下,如何设 ...

  10. 兼容性—IE6/7下带有overflow:hidden属性的父级元素包不住带有position:relative属性的子元素...

    IE6/7下带有overflow:hidden属性的父级元素包不住带有position:relative属性的子元素 <!DOCTYPE html> <html lang=" ...

最新文章

  1. 2、运行.py文件、字符串、保留字符、行和缩进、多行语句、引号、注释、等待用户输入、同一行显示多条语句、命令行参数
  2. 应用深度学习(台大陈蕴侬李宏毅) Part1
  3. java开发中spring常用的工具类
  4. SQL查询提速秘诀,避免锁死数据库的数据库代码
  5. 机器学习:用正规方程法求解线性回归
  6. 随机化算法-数值随机化算法
  7. Playing with OS(操作系统)
  8. 用SQL语句可以取出中文汉字的首字母
  9. 080 matplolib模块
  10. 2019年网络工程师考试大纲
  11. FPGA控制ADF4351实现2MS的扫频操作
  12. 计算机c盘装什么,电脑只有一个C盘!怎么为电脑重装系统?
  13. RHCE(一、二)nmcli命令学习、Linux例行性工作命令atd和crond学习
  14. 简单分析一个通过 js 劫持进行案例
  15. 仿高德地图首页效果,简单代码实现
  16. 新农慕课python答案、第七周_优学院《作业治疗》完整答案中国大学慕课《农作学》课后作业参考答案...
  17. 【笔记】python中的for循环(遍历列表)、for循环中的一些缩进问题
  18. Unity VR开发教程 OpenXR+XR Interaction Toolkit(七)射线抓取
  19. [转帖]美国《工程索引》收录中国科技论文的最新规定
  20. [BZOJ1116] CLO

热门文章

  1. 矩阵基变换和坐标变换
  2. RMII RGMII MII GMII个人总结
  3. 基于opencv的简单数字识别
  4. iRedMail批量用户添加命令
  5. 原生微信小程序下拉刷新和加载动画
  6. 关于ArcGIS的一些基础知识
  7. 网络安全工程师面试分享
  8. 微信小游戏《头脑吃鸡》题库
  9. 实例讲解反向传播(简单易懂)
  10. 为什么局域网需要https加密?如何选择内网SSL证书?