跳至主要內容

低代码

nicye大约 4 分钟约 1326 字

低代码

本篇是继《动态操作》文档之后的大功能,专门为低代码设计。

FreeSql 默认依赖实体类型,虽然运行时可以动态创建 Type,但不断的动态编译导致会内存无法释放,对 Type 的版本也难以管理。

本功能是独立的,使用纯字典(无实体类型)进行 CRUD,支持导航属性,级联操作等功能。

注意:本功能是独立的,请勿与其他文档的级联机制搞混。

字典 CUD(单表)

var dic = new Dictionary<string, object>();
dic.Add("id", 1);
dic.Add("name", "xxxx");

fsql.InsertDict(dic).AsTable("table1").ExecuteAffrows();
fsql.UpdateDict(dic).AsTable("table1").WherePrimary("id").ExecuteAffrows();
fsql.DeleteDict(dic).AsTable("table1").ExecuteAffrows();
fsql.InsertOrUpdateDict(dic).AsTable("table1").WherePrimary("id").ExecuteAffrows();

InsertDict/UpdateDict/DeleteDict/InsertOrUpdateDict 都支持批量操作,对应类型 List<Dictionary<string, object>>

无类型 CRUD(更高级)

不依赖实体类型,不需要动态编译,纯字典操作,支持导航属性,级联保存,AOT 编译福音。

nuget 安装:

dotnet add package FreeSql.Extensions.ZeroEntity

var ctx = new ZeroDbContext(fsql, JsonConvert.DeserializeObject<TableDescriptor[]>(json)); //在文档后面

var item = JsonConvert.DeserializeObject<Dictionary<string, object>>(@"
{
  ""Name"":""user1"",
  ""Ext"":
  {
    ""Remarks"":[{""Remark"":""remark1""},{""Remark"":""remark2""}]
  },
  ""Claims"":[{""ClaimName"":""claim1""},{""ClaimName"":""claim2""},{""ClaimName"":""claim3""}],
  ""Roles"":[{""Name"":""role1""},{""Name"":""role2""}]
}");
ctx.Insert(item);

var item2 = JsonConvert.DeserializeObject<Dictionary<string, object>>(@"
{
  ""Id"":1,
  ""Name"":""user1111"",
  ""Ext"":{},
  ""Claims"":[{""Id"":1,""ClaimName"":""claim1111""},{""Id"":""3"",""ClaimName"":""claim3222222""},{""ClaimName"":""claim0000""}],
  ""Roles"":[{""Name"":""role111100001""},{""Id"":2,""Name"":""role2""}]
}");
ctx.Update(item2);
ctx.Delete(item2);
INSERT INTO [Role]([Name]) OUTPUT INSERTED.[Id] as [Id], INSERTED.[Name] as [Name] VALUES(N'role1'), (N'role2')
INSERT INTO [User]([Name]) OUTPUT INSERTED.[Id] as [Id], INSERTED.[Name] as [Name] VALUES(N'user1')
INSERT INTO [UserExt]([UserId]) VALUES(1)
INSERT INTO [UserExtRemarks]([RemarkId], [UserId], [Remark]) VALUES('6570e3f8-a226-c3ac-00d1-a3dd18b30339', 1, N'remark1'), ('6570e3f8-a226-c3ac-00d1-a3de16d9aa68', 1, N'remark2')
INSERT INTO [UserClaim]([UserId], [ClaimName]) OUTPUT INSERTED.[Id] as [Id], INSERTED.[UserId] as [UserId], INSERTED.[ClaimName] as [ClaimName] VALUES(1, N'claim1'), (1, N'claim2'), (1, N'claim3')
INSERT INTO [UserRole]([UserId], [RoleId]) VALUES(1, 5), (1, 6)

INSERT INTO [Role]([Name]) OUTPUT INSERTED.[Id] as [Id], INSERTED.[Name] as [Name] VALUES(N'role111100001'), (N'role2')
INSERT INTO [UserClaim]([UserId], [ClaimName]) OUTPUT INSERTED.[Id] as [Id], INSERTED.[UserId] as [UserId], INSERTED.[ClaimName] as [ClaimName] VALUES(1, N'claim0000')
INSERT INTO [UserRole]([UserId], [RoleId]) VALUES(1, 7), (1, 8)
DELETE FROM [UserRole] WHERE ([UserId] = 1 AND [RoleId] = 6)
DELETE FROM [UserRole] WHERE ([UserId] = 1 AND [RoleId] = 5)
DELETE FROM [UserClaim] WHERE ([Id] = 2)
UPDATE [User] SET [Name] = N'user1111'
WHERE ([Id] = 1)
UPDATE [UserClaim] SET [ClaimName] = CASE [Id]
WHEN 1 THEN N'claim1111'
WHEN 3 THEN N'claim3222222' END
WHERE ([Id] IN (1,3))

DELETE FROM [UserRole] WHERE (([UserId] = 1 AND [RoleId] = 7) OR ([UserId] = 1 AND [RoleId] = 8))
DELETE FROM [UserClaim] WHERE ([Id] IN (1,3,4))
DELETE FROM [UserExt] WHERE ([UserId] = 1)
DELETE FROM [User] WHERE ([Id] = 1)

查询,返回结果是字典 Dictionary<string, object>:

//自动级联
ctx.Select.Where("id", 1).ToList();

//单独查询
ctx.SelectNoTracking("User")
  //.IncludeAll()
  .Include("Ext.Remarks", then => then.Where("remark", "like", "error"))
  .Include("Roles", then => then.Include("Users",
    then => then.Include("Ext.Remarks")))
  .ToList();

//普通多表查询
ctx.SelectNoTracking("User")
  .LeftJoin("UserExt", "UserId", "User.Id")
  .ToList();
//[{id:1, UserExt:{} },..]

上述 ctx 对象依赖 json 配置如下:

[
  {
    "Name":"User",
    "Comment":"用户表",
    "Columns": [
      {"Name":"Id","IsPrimary":true,"IsIdentity":true,"MapType":"System.Int32"},
      {"Name":"Name","MapType":"System.String"}
    ],
    "Navigates":[
      {"Name":"Ext","Type":"OneToOne","RelTable":"UserExt"},
      {"Name":"Claims","Type":"OneToMany","RelTable":"UserClaim","Bind":"UserId"},
      {"Name":"Roles","Type":"ManyToMany","RelTable":"Role","ManyToMany":"UserRole"}
    ],
    "Indexes":[]
  },
  {
    "Name":"UserExt",
    "Comment":"用户扩展信息表",
    "Columns":[
      {"Name":"UserId","IsPrimary":true,"MapType":"System.Int32"},
    ],
    "Navigates":[
      {"Name":"Remarks","Type":"OneToMany","RelTable":"UserExtRemarks","Bind":"UserId"},
    ],
  },
  {
    "Name":"UserExtRemarks",
    "Comment":"用户扩展信息表-子表",
    "Columns":[
      {"Name":"RemarkId","IsPrimary":true,"MapType":"System.Guid"},
      {"Name":"UserId","MapType":"System.Int32"},
      {"Name":"Remark","MapType":"System.String"},
    ],
  },
  {
    "Name":"UserClaim",
    "Comment":"一对多测试表",
    "Columns":[
      {"Name":"Id","IsPrimary":true,"IsIdentity":true,"MapType":"System.Int32"},
      {"Name":"UserId","MapType":"System.Int32"},
      {"Name":"ClaimName","MapType":"System.String"},
    ],
  },
  {
    "Name":"Role",
    "Comment":"权限表",
    "Columns":[
      {"Name":"Id","IsPrimary":true,"IsIdentity":true,"MapType":"System.Int32"},
      {"Name":"Name","MapType":"System.String"}
    ],
    "Navigates":[
      {"Name":"Users","Type":"ManyToMany","RelTable":"User","ManyToMany":"UserRole"}
    ],
    "Indexes":[]
  },
  {
    "Name":"UserRole",
    "Comment":"多对多中间表",
    "Columns":[
      {"Name":"UserId","IsPrimary":true,"MapType":"System.Int32"},
      {"Name":"RoleId","IsPrimary":true,"MapType":"System.Int32"}
    ],
    "Navigates":[
      {"Name":"User","Type":"ManyToOne","RelTable":"User","Bind":"UserId"},
      {"Name":"Role","Type":"ManyToOne","RelTable":"Role","Bind":"RoleId"}
    ]
  }
]

级联机制

理解本机制之前,请先忘记 Repository/DbContext 等之前的级联机制,他们没有关联。

schemas[] 是一组表映射信息定义,包含表名、列名、导航属性、索引等信息

  • 导航属性:OneToOne/OneToMany/ManyToOne/ManyToMany
  • 聚合根:OneToOne/OneToMany/多对多中间表,作为一个整体看待
  • 外部根:ManyToOne/ManyToMany外部表,作为外部看待,它有自己的聚合根整体

举例:

  • User 为聚合根
  • UserExt/UserClaim/UserRole 这三个表是子成员,一起存储/删除
  • Role 为外部根(相对 User 而言,它自己是独立的聚合根)

CURD 都是基于 schemas[0] 聚合根进行操作

  • 查询:贪婪加载所有子成员,以及外部根,以及外部根的外部根(递归)
  • 状态管理:快照聚合根副本(由于外部根也是聚合根,即外部根与聚合根是并行存储关系)

对比保存:

将当前操作的聚合根与状态管理的副本进行对比,计算出发生变化的列

导航属性副本最新动作
OneToOneNULLObject添加新记录
OneToOneObjectNULL删除副本记录
OneToOneObjectObject发生变化则更新,否则忽略
OneToManyNULL/EmptyList添加最新List记录
OneToManyListNULL忽略
OneToManyListEmpty删除副本List记录
OneToManyListList对比保存,计算出添加/更新/删除三种行为
多对多中间表与 OneToMany 一致

插入:

  • OneToOne 级联插入
  • OneToMany 级联插入
  • ManyToOne 先对比保存外部根,关联外部根ID,再插入聚合根
  • ManyToMany 先对比保存外部根,插入聚合根,再插入中间表

更新:

  • OneToOne 级联对比保存
  • OneToMany 级联对比保存
  • ManyToOne 先对比保存外部根,再关联外部根ID,再更新聚合根
  • ManyToMany 先对比保存外部根,再更新聚合根,再对比保存中间表

删除:

  • OneToOne 级联删除
  • OneToMany 级联删除
  • ManyToOne 忽略
  • ManyToMany 级联删除中间表(注意不删除外部根)