FreeSql.Repository (九)级联保存
欢迎来到《FreeSql.Repository 仓储模式》系列文档,本系列文档专注介绍 【仓储+工作单元】 的使用方式。完整文档请前往 wiki 中心:https://github.com/dotnetcore/FreeSql/wiki
前面说到,仓储模式推荐使用导航属性,联表查询、子表查询、级联加载都已得到解决,本文继续配置导航属性之后,级联保存如何使用。
实践发现,N对1 不适合做级联保存。保存 Topic 的时候把 Type 信息也保存?我个人认为自下向上保存的功能太不可控了,FreeSql 目前不支持自下向上保存。因此下面我们只讲 OneToOne/OneToMany/ManyToMany 级联保存。至于 ManyToOne 级联保存使用手工处理,更加安全可控。
SaveMany 手工保存
完整保存,对比表已存在的数据,计算出添加、修改、删除执行
递归保存导航属性不安全,不可控,并非技术问题,而是出于安全考虑,提供了手工完整保存的方式。
var repo = fsql.GetRepository<Type>();
var type = new Type
{
name = "c#",
Topics = new List<Topic>(new[]
{
new Topic { ... }
})
};
repo.Insert(type);
repo.SaveMany(type, "Topics"); //手工完整保存 Topics
请确认机制:
- 有可能删除表已存在的数据,确认?
- 当 Topics 属性为 Empty 时,删除 type 存在的 Topics 所有表数据,确认?
- 保存 Topics 的时候,不、不、不保存 Childs[0-..] 的下级集合属性,只保存 Topics 当前层级,确认?
- ManyToMany 机制为,完整对比保存中间表,外部表只追加不更新
如:
- 本表 Song
- 外部表 Tag
- 中间表 SongTag
EnableCascadeSave 仓储级联保存
DbContext/Repository EnableCascadeSave 可实现保存对象的时候,递归追朔其 OneToOne/OneToMany/ManyToMany 导航属性也一并保存,本文档说明机制防止误用。
此方式建议用在不太重要的功能,或者测试数据添加
1、OneToOne 级联保存
v3.2.606+ 支持,并且支持级联删除功能
2、OneToMany 追加或更新子表,不删除子表已存在的数据
repo.DbContextOptions.EnableCascadeSave = true; //需要手工开启
repo.Insert(type);
- 不删除 Topics 子表已存在的数据,确认?
- 当 Topics 属性为 Empty 时,不做任何操作,确认?
- 保存 Topics 的时候,还会保存 Topics[0-..] 的下级集合属性,向下18层,确认?
向下18层的意思,比如【类型】表,下面有集合属性【文章】,【文章】下面有集合属性【评论】。
保存【类型】表对象的时候,他会向下检索出集合属性【文章】,然后如果【文章】被保存的时候,再继续向下检索出集合属性【评论】。一起做 InsertOrUpdate 操作。
3、ManyToMany 完整对比保存中间表,追加外部表
完整对比保存中间表,对比【多对多】中间表已存在的数据,计算出添加、修改、删除执行。
追加外部表,只追加不更新。
- 本表 Song
- 外部表 Tag
- 中间表 SongTag
级联删除
1、第一种:基于【对象】级联删除
比如 Include/IncludeMany 查询的对象,可以使用此方法级联删除它们。
var repo = fsql.GetRepository<Group>();
repo.DbContextOptions.EnableCascadeSave = true; //关键设置
repo.Insert(new UserGroup
{
GroupName = "group01",
Users = new List<User>
{
new User { Username = "admin01", Password = "pwd01", UserExt = new UserExt { Remark = "用户备注01" } },
new User { Username = "admin02", Password = "pwd02", UserExt = new UserExt { Remark = "用户备注02" } },
new User { Username = "admin03", Password = "pwd03", UserExt = new UserExt { Remark = "用户备注03" } },
}
}); //级联添加测试数据
//INSERT INTO "usergroup"("groupname") VALUES('group01') RETURNING "id"
//INSERT INTO "user"("username", "password", "groupid") VALUES('admin01', 'pwd01', 1), ('admin02', 'pwd02', 1), ('admin03', 'pwd03', 1) RETURNING "id" as "Id", "username" as "Username", "password" as "Password", "groupid" as "GroupId"
//INSERT INTO "userext"("userid", "remark") VALUES(3, '用户备注01'), (4, '用户备注02'), (5, '用户备注03')
var groups = repo.Select
.IncludeMany(a => a.Users,
then => then.Include(b => b.UserExt))
.ToList();
repo.Delete(groups); //级联删除,递归向下遍历 group OneToOne/OneToMany/ManyToMany 导航属性
//DELETE FROM "userext" WHERE ("userid" IN (3,4,5))
//DELETE FROM "user" WHERE ("id" IN (3,4,5))
//DELETE FROM "usergroup" WHERE ("id" = 1)
2、第二种:基于【数据库】级联删除
根据设置的导航属性,递归删除 OneToOne/OneToMany/ManyToMany 对应数据,并返回已删除的数据。此功能不依赖数据库外键
var repo = fsql.GetRepository<Group>();
var ret = repo.DeleteCascadeByDatabase(a => a.Id == 1);
//SELECT a."id", a."username", a."password", a."groupid" FROM "user" a WHERE (a."groupid" = 1)
//SELECT a."userid", a."remark" FROM "userext" a WHERE (a."userid" IN (3,4,5))
//DELETE FROM "userext" WHERE ("userid" IN (3,4,5))
//DELETE FROM "user" WHERE ("id" IN (3,4,5))
//DELETE FROM "usergroup" WHERE ("id" = 1)
//ret Count = 7 System.Collections.Generic.List<object>
// [0] {UserExt} object {UserExt}
// [1] {UserExt} object {UserExt}
// [2] {UserExt} object {UserExt}
// [3] {User} object {User}
// [4] {User} object {User}
// [5] {User} object {User}
// [6] {UserGroup} object {UserGroup}
public class Group
{
[Column(IsIdentity = true)]
public int Id { get; set; }
public string GroupName { get; set; }
[Navigate(nameof(User.GroupId))]
public List<User> Users { get; set; }
}
public class User
{
[Column(IsIdentity = true)]
public int Id { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public int GroupId { get; set; }
[Navigate(nameof(Id))]
public UserExt UserExt { get; set; }
}
public class UserExt
{
[Column(IsPrimary = true)]
public int UserId { get; set; }
public string Remark { get; set; }
[Navigate(nameof(UserId))]
public User User { get; set; }
}