From e0030b0c00e255c132ac81636cefc5f9adbda25f Mon Sep 17 00:00:00 2001 From: 28810 <28810@YEXIANGQIN> Date: Wed, 13 Nov 2019 16:21:30 +0800 Subject: [PATCH] =?UTF-8?q?-=20=E5=A2=9E=E5=8A=A0=20PostgreSQL=20=E7=89=B9?= =?UTF-8?q?=E6=9C=89=E5=8A=9F=E8=83=BD=20On=20Conflict=20Do=20Update=20?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.xml | 6 +- .../PostgreSQL/Curd/OnConflictDoUpdateTest.cs | 201 ++++++++++++++++++ FreeSql.Tests/FreeSql.Tests/UnitTest1.cs | 6 +- FreeSql.Tests/FreeSql.Tests/UnitTest2.cs | 70 +++--- .../Curd/MySqlUpdate.cs | 1 + .../Curd/OnDuplicateKeyUpdate.cs | 6 +- .../Curd/OnConflictDoUpdate.cs | 197 +++++++++++++++++ .../Curd/PostgreSQLInsert.cs | 13 ++ .../Curd/PostgreSQLUpdate.cs | 10 + .../PostgreSQLExtensions.cs | 17 +- 10 files changed, 492 insertions(+), 35 deletions(-) create mode 100644 FreeSql.Tests/FreeSql.Tests/PostgreSQL/Curd/OnConflictDoUpdateTest.cs create mode 100644 Providers/FreeSql.Provider.PostgreSQL/Curd/OnConflictDoUpdate.cs diff --git a/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.xml b/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.xml index 21a0a7ed..6e6ec049 100644 --- a/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.xml +++ b/FreeSql.Tests/FreeSql.Tests/FreeSql.Tests.xml @@ -99,7 +99,7 @@ 总分 - + 菜单权限ID @@ -119,7 +119,7 @@ 菜单权限 - + 主键 @@ -164,7 +164,7 @@ 创建日期 - + 按钮主键 diff --git a/FreeSql.Tests/FreeSql.Tests/PostgreSQL/Curd/OnConflictDoUpdateTest.cs b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/Curd/OnConflictDoUpdateTest.cs new file mode 100644 index 00000000..25e46a99 --- /dev/null +++ b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/Curd/OnConflictDoUpdateTest.cs @@ -0,0 +1,201 @@ +using FreeSql.DataAnnotations; +using System; +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace FreeSql.Tests.PostgreSQL +{ + public class OnConflictDoUpdateTest + { + class TestOnConflictDoUpdateInfo + { + [Column(IsIdentity = true)] + public int id { get; set; } + public string title { get; set; } + public DateTime? time { get; set; } + } + + [Fact] + public void ExecuteAffrows() + { + g.pgsql.Delete(new[] { 100, 101, 102 }).ExecuteAffrows(); + var odku1 = g.pgsql.Insert(new TestOnConflictDoUpdateInfo { id = 100, title = "title-100", time = DateTime.Parse("2000-01-01") }).NoneParameter().OnConflictDoUpdate(); + Assert.Equal(odku1.ToSql(), @"INSERT INTO ""testonconflictdoupdateinfo"" AS _ftb_ (""id"", ""title"", ""time"") VALUES(100, 'title-100', '2000-01-01 00:00:00.000000') +ON CONFLICT(""id"") DO UPDATE SET +""title"" = EXCLUDED.""title"", +""time"" = EXCLUDED.""time"""); + Assert.Equal(1, odku1.ExecuteAffrows()); + + var odku2 = g.pgsql.Insert(new[] { + new TestOnConflictDoUpdateInfo { id = 100, title = "title-100", time = DateTime.Parse("2000-01-01") }, + new TestOnConflictDoUpdateInfo { id = 101, title = "title-101", time = DateTime.Parse("2000-01-01") }, + new TestOnConflictDoUpdateInfo { id = 102, title = "title-102", time = DateTime.Parse("2000-01-01") } + }).NoneParameter().OnConflictDoUpdate(); + Assert.Equal(odku2.ToSql(), @"INSERT INTO ""testonconflictdoupdateinfo"" AS _ftb_ (""id"", ""title"", ""time"") VALUES(100, 'title-100', '2000-01-01 00:00:00.000000'), (101, 'title-101', '2000-01-01 00:00:00.000000'), (102, 'title-102', '2000-01-01 00:00:00.000000') +ON CONFLICT(""id"") DO UPDATE SET +""title"" = EXCLUDED.""title"", +""time"" = EXCLUDED.""time"""); + odku2.ExecuteAffrows(); + } + + [Fact] + public void IgnoreColumns() + { + g.pgsql.Delete(new[] { 200, 201, 202 }).ExecuteAffrows(); + var odku1 = g.pgsql.Insert(new TestOnConflictDoUpdateInfo { id = 200, title = "title-200", time = DateTime.Parse("2000-01-01") }).IgnoreColumns(a => a.time).NoneParameter().OnConflictDoUpdate(); + Assert.Equal(odku1.ToSql(), @"INSERT INTO ""testonconflictdoupdateinfo"" AS _ftb_ (""id"", ""title"") VALUES(200, 'title-200') +ON CONFLICT(""id"") DO UPDATE SET +""title"" = EXCLUDED.""title"", +""time"" = '2000-01-01 00:00:00.000000'"); + Assert.Equal(1, odku1.ExecuteAffrows()); + + var odku2 = g.pgsql.Insert(new[] { + new TestOnConflictDoUpdateInfo { id = 200, title = "title-200", time = DateTime.Parse("2000-01-01") }, + new TestOnConflictDoUpdateInfo { id = 201, title = "title-201", time = DateTime.Parse("2000-01-01") }, + new TestOnConflictDoUpdateInfo { id = 202, title = "title-202", time = DateTime.Parse("2000-01-01") } + }).IgnoreColumns(a => a.time).NoneParameter().OnConflictDoUpdate(); + Assert.Equal(odku2.ToSql(), @"INSERT INTO ""testonconflictdoupdateinfo"" AS _ftb_ (""id"", ""title"") VALUES(200, 'title-200'), (201, 'title-201'), (202, 'title-202') +ON CONFLICT(""id"") DO UPDATE SET +""title"" = EXCLUDED.""title"", +""time"" = CASE EXCLUDED.""id"" +WHEN 200 THEN '2000-01-01 00:00:00.000000' +WHEN 201 THEN '2000-01-01 00:00:00.000000' +WHEN 202 THEN '2000-01-01 00:00:00.000000' END::timestamp"); + odku2.ExecuteAffrows(); + + + g.pgsql.Delete(new[] { 200, 201, 202 }).ExecuteAffrows(); + odku1 = g.pgsql.Insert(new TestOnConflictDoUpdateInfo { id = 200, title = "title-200", time = DateTime.Parse("2000-01-01") }).IgnoreColumns(a => a.time).NoneParameter().OnConflictDoUpdate().IgnoreColumns(a => a.title); + Assert.Equal(odku1.ToSql(), @"INSERT INTO ""testonconflictdoupdateinfo"" AS _ftb_ (""id"", ""title"") VALUES(200, 'title-200') +ON CONFLICT(""id"") DO UPDATE SET +""time"" = '2000-01-01 00:00:00.000000'"); + Assert.Equal(1, odku1.ExecuteAffrows()); + + odku2 = g.pgsql.Insert(new[] { + new TestOnConflictDoUpdateInfo { id = 200, title = "title-200", time = DateTime.Parse("2000-01-01") }, + new TestOnConflictDoUpdateInfo { id = 201, title = "title-201", time = DateTime.Parse("2000-01-01") }, + new TestOnConflictDoUpdateInfo { id = 202, title = "title-202", time = DateTime.Parse("2000-01-01") } + }).IgnoreColumns(a => a.time).NoneParameter().OnConflictDoUpdate().IgnoreColumns(a => a.title); + Assert.Equal(odku2.ToSql(), @"INSERT INTO ""testonconflictdoupdateinfo"" AS _ftb_ (""id"", ""title"") VALUES(200, 'title-200'), (201, 'title-201'), (202, 'title-202') +ON CONFLICT(""id"") DO UPDATE SET +""time"" = CASE EXCLUDED.""id"" +WHEN 200 THEN '2000-01-01 00:00:00.000000' +WHEN 201 THEN '2000-01-01 00:00:00.000000' +WHEN 202 THEN '2000-01-01 00:00:00.000000' END::timestamp"); + odku2.ExecuteAffrows(); + } + + [Fact] + public void UpdateColumns() + { + g.pgsql.Delete(new[] { 300, 301, 302 }).ExecuteAffrows(); + var odku1 = g.pgsql.Insert(new TestOnConflictDoUpdateInfo { id = 300, title = "title-300", time = DateTime.Parse("2000-01-01") }).InsertColumns(a => a.title).NoneParameter().OnConflictDoUpdate(); + Assert.Equal(odku1.ToSql(), @"INSERT INTO ""testonconflictdoupdateinfo"" AS _ftb_ (""id"", ""title"") VALUES(300, 'title-300') +ON CONFLICT(""id"") DO UPDATE SET +""title"" = EXCLUDED.""title"", +""time"" = '2000-01-01 00:00:00.000000'"); + Assert.Equal(1, odku1.ExecuteAffrows()); + + var odku2 = g.pgsql.Insert(new[] { + new TestOnConflictDoUpdateInfo { id = 300, title = "title-300", time = DateTime.Parse("2000-01-01") }, + new TestOnConflictDoUpdateInfo { id = 301, title = "title-301", time = DateTime.Parse("2000-01-01") }, + new TestOnConflictDoUpdateInfo { id = 302, title = "title-302", time = DateTime.Parse("2000-01-01") } + }).InsertColumns(a => a.title).NoneParameter().OnConflictDoUpdate(); + Assert.Equal(odku2.ToSql(), @"INSERT INTO ""testonconflictdoupdateinfo"" AS _ftb_ (""id"", ""title"") VALUES(300, 'title-300'), (301, 'title-301'), (302, 'title-302') +ON CONFLICT(""id"") DO UPDATE SET +""title"" = EXCLUDED.""title"", +""time"" = CASE EXCLUDED.""id"" +WHEN 300 THEN '2000-01-01 00:00:00.000000' +WHEN 301 THEN '2000-01-01 00:00:00.000000' +WHEN 302 THEN '2000-01-01 00:00:00.000000' END::timestamp"); + odku2.ExecuteAffrows(); + + + g.pgsql.Delete(new[] { 300, 301, 302 }).ExecuteAffrows(); + odku1 = g.pgsql.Insert(new TestOnConflictDoUpdateInfo { id = 300, title = "title-300", time = DateTime.Parse("2000-01-01") }).InsertColumns(a => a.title).NoneParameter().OnConflictDoUpdate().UpdateColumns(a => a.time); + Assert.Equal(odku1.ToSql(), @"INSERT INTO ""testonconflictdoupdateinfo"" AS _ftb_ (""id"", ""title"") VALUES(300, 'title-300') +ON CONFLICT(""id"") DO UPDATE SET +""time"" = '2000-01-01 00:00:00.000000'"); + Assert.Equal(1, odku1.ExecuteAffrows()); + + odku2 = g.pgsql.Insert(new[] { + new TestOnConflictDoUpdateInfo { id = 300, title = "title-300", time = DateTime.Parse("2000-01-01") }, + new TestOnConflictDoUpdateInfo { id = 301, title = "title-301", time = DateTime.Parse("2000-01-01") }, + new TestOnConflictDoUpdateInfo { id = 302, title = "title-302", time = DateTime.Parse("2000-01-01") } + }).InsertColumns(a => a.title).NoneParameter().OnConflictDoUpdate().UpdateColumns(a => a.time); + Assert.Equal(odku2.ToSql(), @"INSERT INTO ""testonconflictdoupdateinfo"" AS _ftb_ (""id"", ""title"") VALUES(300, 'title-300'), (301, 'title-301'), (302, 'title-302') +ON CONFLICT(""id"") DO UPDATE SET +""time"" = CASE EXCLUDED.""id"" +WHEN 300 THEN '2000-01-01 00:00:00.000000' +WHEN 301 THEN '2000-01-01 00:00:00.000000' +WHEN 302 THEN '2000-01-01 00:00:00.000000' END::timestamp"); + odku2.ExecuteAffrows(); + } + + [Fact] + public void Set() + { + g.pgsql.Delete(new[] { 400, 401, 402 }).ExecuteAffrows(); + var odku1 = g.pgsql.Insert(new TestOnConflictDoUpdateInfo { id = 400, title = "title-400", time = DateTime.Parse("2000-01-01") }).NoneParameter().OnConflictDoUpdate().Set(a => a.time, DateTime.Parse("2020-1-1")); + Assert.Equal(odku1.ToSql(), @"INSERT INTO ""testonconflictdoupdateinfo"" AS _ftb_ (""id"", ""title"", ""time"") VALUES(400, 'title-400', '2000-01-01 00:00:00.000000') +ON CONFLICT(""id"") DO UPDATE SET +""time"" = '2020-01-01 00:00:00.000000'"); + Assert.Equal(1, odku1.ExecuteAffrows()); + + var odku2 = g.pgsql.Insert(new[] { + new TestOnConflictDoUpdateInfo { id = 400, title = "title-400", time = DateTime.Parse("2000-01-01") }, + new TestOnConflictDoUpdateInfo { id = 401, title = "title-401", time = DateTime.Parse("2000-01-01") }, + new TestOnConflictDoUpdateInfo { id = 402, title = "title-402", time = DateTime.Parse("2000-01-01") } + }).NoneParameter().OnConflictDoUpdate().Set(a => a.time, DateTime.Parse("2020-1-1")); + Assert.Equal(odku2.ToSql(), @"INSERT INTO ""testonconflictdoupdateinfo"" AS _ftb_ (""id"", ""title"", ""time"") VALUES(400, 'title-400', '2000-01-01 00:00:00.000000'), (401, 'title-401', '2000-01-01 00:00:00.000000'), (402, 'title-402', '2000-01-01 00:00:00.000000') +ON CONFLICT(""id"") DO UPDATE SET +""time"" = '2020-01-01 00:00:00.000000'"); + odku2.ExecuteAffrows(); + + +// var dt2020 = DateTime.Parse("2020-1-1"); +// g.pgsql.Delete(new[] { 400, 401, 402 }).ExecuteAffrows(); +// odku1 = g.pgsql.Insert(new TestOnConflictDoUpdateInfo { id = 400, title = "title-400", time = DateTime.Parse("2000-01-01") }).NoneParameter().OnConflictDoUpdate().Set(a => a.time == dt2020); +// Assert.Equal(odku1.ToSql(), @"INSERT INTO ""testonconflictdoupdateinfo"" AS _ftb_ (""id"", ""title"", ""time"") VALUES(400, 'title-400', '2000-01-01 00:00:00.000000') +//ON CONFLICT(""id"") DO UPDATE SET +//""time"" = '2020-01-01 00:00:00.000000'"); +// Assert.Equal(1, odku1.ExecuteAffrows()); + +// odku2 = g.pgsql.Insert(new[] { +// new TestOnConflictDoUpdateInfo { id = 400, title = "title-400", time = DateTime.Parse("2000-01-01") }, +// new TestOnConflictDoUpdateInfo { id = 401, title = "title-401", time = DateTime.Parse("2000-01-01") }, +// new TestOnConflictDoUpdateInfo { id = 402, title = "title-402", time = DateTime.Parse("2000-01-01") } +// }).NoneParameter().OnConflictDoUpdate().Set(a => a.time == dt2020); +// Assert.Equal(odku2.ToSql(), @"INSERT INTO ""testonconflictdoupdateinfo"" AS _ftb_ (""id"", ""title"", ""time"") VALUES(400, 'title-400', '2000-01-01 00:00:00.000000'), (401, 'title-401', '2000-01-01 00:00:00.000000'), (402, 'title-402', '2000-01-01 00:00:00.000000') +//ON CONFLICT(""id"") DO UPDATE SET +//""time"" = '2020-01-01 00:00:00.000000'"); +// odku2.ExecuteAffrows(); + + +// g.pgsql.Delete(new[] { 400, 401, 402 }).ExecuteAffrows(); +// odku1 = g.pgsql.Insert(new TestOnConflictDoUpdateInfo { id = 400, title = "title-400", time = DateTime.Parse("2000-01-01") }).NoneParameter().OnConflictDoUpdate().Set(a => new { time = dt2020, title = a.title + "123" }); +// Assert.Equal(odku1.ToSql(), @"INSERT INTO ""testonconflictdoupdateinfo"" AS _ftb_ (""id"", ""title"", ""time"") VALUES(400, 'title-400', '2000-01-01 00:00:00.000000') +//ON CONFLICT(""id"") DO UPDATE SET +//""time"" = '2020-01-01 00:00:00.000000', ""title"" = _ftb_.""title"" || '123'"); +// Assert.Equal(1, odku1.ExecuteAffrows()); + +// odku2 = g.pgsql.Insert(new[] { +// new TestOnConflictDoUpdateInfo { id = 400, title = "title-400", time = DateTime.Parse("2000-01-01") }, +// new TestOnConflictDoUpdateInfo { id = 401, title = "title-401", time = DateTime.Parse("2000-01-01") }, +// new TestOnConflictDoUpdateInfo { id = 402, title = "title-402", time = DateTime.Parse("2000-01-01") } +// }).NoneParameter().OnConflictDoUpdate().Set(a => new { time = dt2020, title = a.title + "123" }); +// Assert.Equal(odku2.ToSql(), @"INSERT INTO ""testonconflictdoupdateinfo"" AS _ftb_ (""id"", ""title"", ""time"") VALUES(400, 'title-400', '2000-01-01 00:00:00.000000'), (401, 'title-401', '2000-01-01 00:00:00.000000'), (402, 'title-402', '2000-01-01 00:00:00.000000') +//ON CONFLICT(""id"") DO UPDATE SET +//""time"" = '2020-01-01 00:00:00.000000', ""title"" = _ftb_.""title"" || '123'"); +// odku2.ExecuteAffrows(); + } + + [Fact] + public void SetRaw() + { + + } + + } +} diff --git a/FreeSql.Tests/FreeSql.Tests/UnitTest1.cs b/FreeSql.Tests/FreeSql.Tests/UnitTest1.cs index 287db677..c5321cd3 100644 --- a/FreeSql.Tests/FreeSql.Tests/UnitTest1.cs +++ b/FreeSql.Tests/FreeSql.Tests/UnitTest1.cs @@ -427,6 +427,10 @@ namespace FreeSql.Tests [Fact] public void Test1() { + + + g.sqlite.Update(1).NoneParameter().Set(a => a.title, null).ExecuteAffrows(); + var testExNewRet1 = g.sqlite.Delete().Where("1=1").ExecuteAffrows(); var testExNewRet2 = g.sqlite.Insert(new TestAddEnumEx { Id = 1, Type = TestAddEnumType.中国人 }).ExecuteAffrows(); var testExNewRet3 = g.sqlite.Insert(new TestAddEnumEx { Id = 2, Type = TestAddEnumType.日本人 }).ExecuteAffrows(); @@ -858,8 +862,6 @@ namespace FreeSql.Tests - - var ttt1 = g.sqlite.Select().Where(a => a.Childs.AsSelect().Any(b => b.Title == "111")).ToList(); diff --git a/FreeSql.Tests/FreeSql.Tests/UnitTest2.cs b/FreeSql.Tests/FreeSql.Tests/UnitTest2.cs index ba939607..3f1299b3 100644 --- a/FreeSql.Tests/FreeSql.Tests/UnitTest2.cs +++ b/FreeSql.Tests/FreeSql.Tests/UnitTest2.cs @@ -21,7 +21,8 @@ namespace FreeSql.Tests /// /// 菜单权限ID /// - [Column(IsPrimary = true)] public String SysModulePermissionId { get; set; } + [Column(IsPrimary = true, OldName = "SysModulePermissionId")] + public String Id { get; set; } /// /// 菜单主键ID @@ -43,8 +44,8 @@ namespace FreeSql.Tests /// /// 主键 /// - [Column(IsPrimary = true)] - public String SysModuleId { get; set; } + [Column(IsPrimary = true, OldName = "SysModuleId")] + public String Id { get; set; } /// /// 父级ID @@ -92,8 +93,8 @@ namespace FreeSql.Tests /// /// 按钮主键 /// - [Column(IsPrimary = true)] - public String SysModuleButtonId { get; set; } + [Column(IsPrimary = true, OldName = "SysModuleButtonId")] + public String Id { get; set; } /// /// 名称 @@ -127,10 +128,12 @@ namespace FreeSql.Tests } partial class SysModulePermission { + [Navigate("SysModuleButtonId")] public SysModuleButton Button { get; set; } } partial class SysModule { + [Navigate("SysModuleId")] public List Permissions { get; set; } } partial class SysModuleButton @@ -164,10 +167,25 @@ namespace FreeSql.Tests [Fact] public void Test02() { + var list111 = g.sqlite.Select() + .Page(1, 10) + .ToList(a => new { Id = a.Id }) + .Select(a => new SysModule { Id = a.Id }).ToList() + .IncludeMany(g.sqlite, a => a.Permissions, then => then.Include(a => a.Button)); + + var list222 = g.sqlite.Select() + .IncludeMany(m => m.Permissions, then => then.Include(a => a.Button)) + .Page(1, 10) + .ToList(); + var comments1 = g.mysql.Select() - .LeftJoin((a, b) => a.Id == b.SubjectId) - .ToList((a, b) => new { comment = a, b.SubjectId, user = a.UserInfo }); + .LeftJoin((a, b) => a.Id == b.SubjectId) + .ToList((a, b) => new { comment = a, b.SubjectId, user = a.UserInfo }); + + + + var comments2 = g.mysql.Select() .Include(r => r.UserInfo) @@ -178,36 +196,32 @@ namespace FreeSql.Tests g.sqlite.Delete().Where("1=1").ExecuteAffrows(); g.sqlite.Delete().Where("1=1").ExecuteAffrows(); - var menu1 = new SysModule { SysModuleId = "menu1", Name = "菜单1" }; - var menu2 = new SysModule { SysModuleId = "menu2", Name = "菜单2" }; + var menu1 = new SysModule { Id = "menu1", Name = "菜单1" }; + var menu2 = new SysModule { Id = "menu2", Name = "菜单2" }; g.sqlite.Insert(new[] { menu1, menu2 }).ExecuteAffrows(); - var button1 = new SysModuleButton { SysModuleButtonId = "button1", Name = "添加" }; - var button2 = new SysModuleButton { SysModuleButtonId = "button2", Name = "修改" }; - var button3 = new SysModuleButton { SysModuleButtonId = "button3", Name = "删除" }; - var button4 = new SysModuleButton { SysModuleButtonId = "button4", Name = "查询" }; + var button1 = new SysModuleButton { Id = "button1", Name = "添加" }; + var button2 = new SysModuleButton { Id = "button2", Name = "修改" }; + var button3 = new SysModuleButton { Id = "button3", Name = "删除" }; + var button4 = new SysModuleButton { Id = "button4", Name = "查询" }; g.sqlite.Insert(new[] { button1, button2, button3, button4 }).ExecuteAffrows(); g.sqlite.Insert(new[] { - new SysModulePermission { SysModulePermissionId = "menu1_button1", SysModuleId = menu1.SysModuleId, SysModuleButtonId = button1.SysModuleButtonId }, - new SysModulePermission { SysModulePermissionId = "menu1_button2", SysModuleId = menu1.SysModuleId, SysModuleButtonId = button2.SysModuleButtonId }, - new SysModulePermission { SysModulePermissionId = "menu1_button3", SysModuleId = menu1.SysModuleId, SysModuleButtonId = button3.SysModuleButtonId }, - new SysModulePermission { SysModulePermissionId = "menu1_button4", SysModuleId = menu1.SysModuleId, SysModuleButtonId = button4.SysModuleButtonId }, + new SysModulePermission { Id = "menu1_button1", SysModuleId = menu1.Id, SysModuleButtonId = button1.Id }, + new SysModulePermission { Id = "menu1_button2", SysModuleId = menu1.Id, SysModuleButtonId = button2.Id }, + new SysModulePermission { Id = "menu1_button3", SysModuleId = menu1.Id, SysModuleButtonId = button3.Id }, + new SysModulePermission { Id = "menu1_button4", SysModuleId = menu1.Id, SysModuleButtonId = button4.Id }, - new SysModulePermission { SysModulePermissionId = "menu2_button1", SysModuleId = menu2.SysModuleId, SysModuleButtonId = button1.SysModuleButtonId }, - new SysModulePermission { SysModulePermissionId = "menu2_button2", SysModuleId = menu2.SysModuleId, SysModuleButtonId = button2.SysModuleButtonId }, - new SysModulePermission { SysModulePermissionId = "menu2_button3", SysModuleId = menu2.SysModuleId, SysModuleButtonId = button3.SysModuleButtonId }, - new SysModulePermission { SysModulePermissionId = "menu2_button4", SysModuleId = menu2.SysModuleId, SysModuleButtonId = button4.SysModuleButtonId }, + new SysModulePermission { Id = "menu2_button1", SysModuleId = menu2.Id, SysModuleButtonId = button1.Id }, + new SysModulePermission { Id = "menu2_button2", SysModuleId = menu2.Id, SysModuleButtonId = button2.Id }, + new SysModulePermission { Id = "menu2_button3", SysModuleId = menu2.Id, SysModuleButtonId = button3.Id }, + new SysModulePermission { Id = "menu2_button4", SysModuleId = menu2.Id, SysModuleButtonId = button4.Id }, }).ExecuteAffrows(); - //var list = g.sqlite.Select() - // .IncludeMany(m => m.Buttons) - // .Page(1, 10) - // .ToList(); - var list = g.sqlite.Select() - .IncludeMany(m => m.Permissions.Where(p => p.SysModuleId == m.SysModuleId), - then => then.LeftJoin(p => p.Button.SysModuleButtonId == p.SysModuleButtonId)) + var list123123 = g.sqlite.Select() + .IncludeMany(m => m.Permissions.Where(p => p.SysModuleId == m.Id), + then => then.LeftJoin(p => p.Button.Id == p.SysModuleButtonId)) .ToList(); } } diff --git a/Providers/FreeSql.Provider.MySql/Curd/MySqlUpdate.cs b/Providers/FreeSql.Provider.MySql/Curd/MySqlUpdate.cs index 97e17653..45ac940f 100644 --- a/Providers/FreeSql.Provider.MySql/Curd/MySqlUpdate.cs +++ b/Providers/FreeSql.Provider.MySql/Curd/MySqlUpdate.cs @@ -23,6 +23,7 @@ namespace FreeSql.MySql.Curd internal Dictionary InternalIgnore => _ignore; internal void InternalResetSource(List source) => _source = source; internal string InternalWhereCaseSource(string CsName, Func thenValue) => WhereCaseSource(CsName, thenValue); + internal void InternalToSqlCaseWhenEnd(StringBuilder sb, ColumnInfo col) => ToSqlCaseWhenEnd(sb, col); public override int ExecuteAffrows() => base.SplitExecuteAffrows(500, 3000); public override List ExecuteUpdated() => base.SplitExecuteUpdated(500, 3000); diff --git a/Providers/FreeSql.Provider.MySql/Curd/OnDuplicateKeyUpdate.cs b/Providers/FreeSql.Provider.MySql/Curd/OnDuplicateKeyUpdate.cs index 6b67e18d..9804ce92 100644 --- a/Providers/FreeSql.Provider.MySql/Curd/OnDuplicateKeyUpdate.cs +++ b/Providers/FreeSql.Provider.MySql/Curd/OnDuplicateKeyUpdate.cs @@ -94,7 +94,11 @@ namespace FreeSql.MySql.Curd sb.Append(field).Append(" = ").Append(field).Append(" + 1"); } else if (_mysqlInsert.InternalIgnore.ContainsKey(col.Attribute.Name)) - sb.Append(_mysqlUpdate.InternalWhereCaseSource(col.CsName, sqlval => sqlval).Trim()); + { + var caseWhen = _mysqlUpdate.InternalWhereCaseSource(col.CsName, sqlval => sqlval).Trim(); + sb.Append(caseWhen); + if (caseWhen.EndsWith(" END")) _mysqlUpdate.InternalToSqlCaseWhenEnd(sb, col); + } else { var field = _mysqlInsert.InternalCommonUtils.QuoteSqlName(col.Attribute.Name); diff --git a/Providers/FreeSql.Provider.PostgreSQL/Curd/OnConflictDoUpdate.cs b/Providers/FreeSql.Provider.PostgreSQL/Curd/OnConflictDoUpdate.cs new file mode 100644 index 00000000..dc2a08d4 --- /dev/null +++ b/Providers/FreeSql.Provider.PostgreSQL/Curd/OnConflictDoUpdate.cs @@ -0,0 +1,197 @@ +using FreeSql.Aop; +using FreeSql.Internal; +using FreeSql.Internal.Model; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace FreeSql.PostgreSQL.Curd +{ + public class OnConflictDoUpdate where T1 : class + { + internal PostgreSQLInsert _pgsqlInsert; + internal PostgreSQLUpdate _pgsqlUpdatePriv; + internal PostgreSQLUpdate _pgsqlUpdate => _pgsqlUpdatePriv ?? + (_pgsqlUpdatePriv = new PostgreSQLUpdate(_pgsqlInsert.InternalOrm, _pgsqlInsert.InternalCommonUtils, _pgsqlInsert.InternalCommonExpression, null) { InternalTableAlias = "EXCLUDED" } + .NoneParameter().SetSource(_pgsqlInsert.InternalSource) as PostgreSQLUpdate); + ColumnInfo[] _columns; + + public OnConflictDoUpdate(IInsert insert, Expression> columns = null) + { + _pgsqlInsert = insert as PostgreSQLInsert; + if (_pgsqlInsert == null) throw new Exception("OnConflictDoUpdate 是 FreeSql.Provider.PostgreSQL 特有的功能"); + + if (columns != null) + { + var colsList = new List(); + var cols = _pgsqlInsert.InternalCommonExpression.ExpressionSelectColumns_MemberAccess_New_NewArrayInit(null, columns?.Body, false, null).ToDictionary(a => a, a => true); + foreach (var col in _pgsqlInsert.InternalTable.Columns.Values) + if (cols.ContainsKey(col.Attribute.Name)) + colsList.Add(col); + _columns = colsList.ToArray(); + } + if (_columns == null || _columns.Any() == false) + _columns = _pgsqlInsert.InternalTable.Primarys; + if (_columns.Any() == false) throw new Exception("OnConflictDoUpdate 功能要求实体类必须设置 IsPrimary 属性"); + } + + protected void ClearData() + { + _pgsqlInsert.InternalClearData(); + _pgsqlUpdatePriv = null; + } + + public OnConflictDoUpdate IgnoreColumns(Expression> columns) + { + _pgsqlUpdate.IgnoreColumns(columns); + return this; + } + public OnConflictDoUpdate UpdateColumns(Expression> columns) + { + _pgsqlUpdate.UpdateColumns(columns); + return this; + } + public OnConflictDoUpdate IgnoreColumns(string[] columns) + { + _pgsqlUpdate.IgnoreColumns(columns); + return this; + } + public OnConflictDoUpdate UpdateColumns(string[] columns) + { + _pgsqlUpdate.UpdateColumns(columns); + return this; + } + + public OnConflictDoUpdate Set(Expression> column, TMember value) + { + _pgsqlUpdate.Set(column, value); + return this; + } + //由于表达式解析问题,ON CONFLICT("id") DO UPDATE SET 需要指定表别名,如 Set(a => a.Clicks + 1) 解析会失败 + //暂时不开放这个功能,如有需要使用 SetRaw("click = t.click + 1") 替代该操作 + //public OnConflictDoUpdate Set(Expression> exp) + //{ + // _pgsqlUpdate.Set(exp); + // return this; + //} + public OnConflictDoUpdate SetRaw(string sql) + { + _pgsqlUpdate.SetRaw(sql); + return this; + } + + public string ToSql() + { + var sb = new StringBuilder(); + var insertSql = _pgsqlInsert.ToSql(); + sb.Append(insertSql).Insert(insertSql.IndexOf('('), " AS _ftb_ ").Append("\r\nON CONFLICT("); + for (var a = 0; a < _columns.Length; a++) + { + if (a > 0) sb.Append(", "); + sb.Append(_pgsqlInsert.InternalCommonUtils.QuoteSqlName(_columns[a].Attribute.Name)); + } + sb.Append(") DO UPDATE SET\r\n"); + + var sbSetEmpty = _pgsqlUpdate.InternalSbSet.Length == 0; + var sbSetIncrEmpty = _pgsqlUpdate.InternalSbSetIncr.Length == 0; + if (sbSetEmpty == false || sbSetIncrEmpty == false) + { + if (sbSetEmpty == false) sb.Append(_pgsqlUpdate.InternalSbSet.ToString().Substring(2)); + if (sbSetIncrEmpty == false) sb.Append(sbSetEmpty ? _pgsqlUpdate.InternalSbSetIncr.ToString().Substring(2) : _pgsqlUpdate.InternalSbSetIncr.ToString()); + } + else + { + var colidx = 0; + foreach (var col in _pgsqlInsert.InternalTable.Columns.Values) + { + if (col.Attribute.IsPrimary || _pgsqlUpdate.InternalIgnore.ContainsKey(col.Attribute.Name)) continue; + + if (colidx > 0) sb.Append(", \r\n"); + + if (col.Attribute.IsVersion == true) + { + var field = _pgsqlInsert.InternalCommonUtils.QuoteSqlName(col.Attribute.Name); + sb.Append(field).Append(" = _ftb_.").Append(field).Append(" + 1"); + } + else if (_pgsqlInsert.InternalIgnore.ContainsKey(col.Attribute.Name)) + { + var caseWhen = _pgsqlUpdate.InternalWhereCaseSource(col.CsName, sqlval => sqlval).Trim(); + sb.Append(caseWhen); + if (caseWhen.EndsWith(" END")) _pgsqlUpdate.InternalToSqlCaseWhenEnd(sb, col); + } + else + { + var field = _pgsqlInsert.InternalCommonUtils.QuoteSqlName(col.Attribute.Name); + sb.Append(field).Append(" = EXCLUDED.").Append(field); + } + ++colidx; + } + } + + return sb.ToString(); + } + + public long ExecuteAffrows() + { + var sql = this.ToSql(); + if (string.IsNullOrEmpty(sql)) return 0; + + var before = new CurdBeforeEventArgs(_pgsqlInsert.InternalTable.Type, _pgsqlInsert.InternalTable, CurdType.Insert, sql, _pgsqlInsert.InternalParams); + _pgsqlInsert.InternalOrm.Aop.CurdBefore?.Invoke(_pgsqlInsert, before); + long ret = 0; + Exception exception = null; + try + { + ret = _pgsqlInsert.InternalOrm.Ado.ExecuteNonQuery(_pgsqlInsert.InternalConnection, _pgsqlInsert.InternalTransaction, CommandType.Text, sql, _pgsqlInsert.InternalParams); + } + catch (Exception ex) + { + exception = ex; + throw ex; + } + finally + { + var after = new CurdAfterEventArgs(before, exception, ret); + _pgsqlInsert.InternalOrm.Aop.CurdAfter?.Invoke(_pgsqlInsert, after); + ClearData(); + } + return ret; + } + +#if net40 +#else + async public Task ExecuteAffrowsAsync() + { + var sql = this.ToSql(); + if (string.IsNullOrEmpty(sql)) return 0; + + var before = new CurdBeforeEventArgs(_pgsqlInsert.InternalTable.Type, _pgsqlInsert.InternalTable, CurdType.Insert, sql, _pgsqlInsert.InternalParams); + _pgsqlInsert.InternalOrm.Aop.CurdBefore?.Invoke(_pgsqlInsert, before); + long ret = 0; + Exception exception = null; + try + { + ret = await _pgsqlInsert.InternalOrm.Ado.ExecuteNonQueryAsync(_pgsqlInsert.InternalConnection, _pgsqlInsert.InternalTransaction, CommandType.Text, sql, _pgsqlInsert.InternalParams); + } + catch (Exception ex) + { + exception = ex; + throw ex; + } + finally + { + var after = new CurdAfterEventArgs(before, exception, ret); + _pgsqlInsert.InternalOrm.Aop.CurdAfter?.Invoke(_pgsqlInsert, after); + ClearData(); + } + return ret; + } +#endif + } +} diff --git a/Providers/FreeSql.Provider.PostgreSQL/Curd/PostgreSQLInsert.cs b/Providers/FreeSql.Provider.PostgreSQL/Curd/PostgreSQLInsert.cs index af811633..af39699c 100644 --- a/Providers/FreeSql.Provider.PostgreSQL/Curd/PostgreSQLInsert.cs +++ b/Providers/FreeSql.Provider.PostgreSQL/Curd/PostgreSQLInsert.cs @@ -1,7 +1,9 @@ using FreeSql.Internal; +using FreeSql.Internal.Model; using System; using System.Collections.Generic; using System.Data; +using System.Data.Common; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -16,6 +18,17 @@ namespace FreeSql.PostgreSQL.Curd { } + internal IFreeSql InternalOrm => _orm; + internal TableInfo InternalTable => _table; + internal DbParameter[] InternalParams => _params; + internal DbConnection InternalConnection => _connection; + internal DbTransaction InternalTransaction => _transaction; + internal CommonUtils InternalCommonUtils => _commonUtils; + internal CommonExpression InternalCommonExpression => _commonExpression; + internal List InternalSource => _source; + internal Dictionary InternalIgnore => _ignore; + internal void InternalClearData() => ClearData(); + public override int ExecuteAffrows() => base.SplitExecuteAffrows(5000, 3000); public override long ExecuteIdentity() => base.SplitExecuteIdentity(5000, 3000); public override List ExecuteInserted() => base.SplitExecuteInserted(5000, 3000); diff --git a/Providers/FreeSql.Provider.PostgreSQL/Curd/PostgreSQLUpdate.cs b/Providers/FreeSql.Provider.PostgreSQL/Curd/PostgreSQLUpdate.cs index e56c125c..e2d85ce9 100644 --- a/Providers/FreeSql.Provider.PostgreSQL/Curd/PostgreSQLUpdate.cs +++ b/Providers/FreeSql.Provider.PostgreSQL/Curd/PostgreSQLUpdate.cs @@ -18,6 +18,14 @@ namespace FreeSql.PostgreSQL.Curd { } + internal string InternalTableAlias; + internal StringBuilder InternalSbSet => _set; + internal StringBuilder InternalSbSetIncr => _setIncr; + internal Dictionary InternalIgnore => _ignore; + internal void InternalResetSource(List source) => _source = source; + internal string InternalWhereCaseSource(string CsName, Func thenValue) => WhereCaseSource(CsName, thenValue); + internal void InternalToSqlCaseWhenEnd(StringBuilder sb, ColumnInfo col) => ToSqlCaseWhenEnd(sb, col); + public override int ExecuteAffrows() => base.SplitExecuteAffrows(500, 3000); public override List ExecuteUpdated() => base.SplitExecuteUpdated(500, 3000); @@ -64,6 +72,7 @@ namespace FreeSql.PostgreSQL.Curd { if (_table.Primarys.Length == 1) { + if (string.IsNullOrEmpty(InternalTableAlias) == false) caseWhen.Append(InternalTableAlias).Append("."); caseWhen.Append(_commonUtils.QuoteReadColumn(_table.Primarys.First().Attribute.MapType, _commonUtils.QuoteSqlName(_table.Primarys.First().Attribute.Name))); return; } @@ -72,6 +81,7 @@ namespace FreeSql.PostgreSQL.Curd foreach (var pk in _table.Primarys) { if (pkidx > 0) caseWhen.Append(" || "); + if (string.IsNullOrEmpty(InternalTableAlias) == false) caseWhen.Append(InternalTableAlias).Append("."); caseWhen.Append(_commonUtils.QuoteReadColumn(pk.Attribute.MapType, _commonUtils.QuoteSqlName(pk.Attribute.Name))).Append("::varchar"); ++pkidx; } diff --git a/Providers/FreeSql.Provider.PostgreSQL/PostgreSQLExtensions.cs b/Providers/FreeSql.Provider.PostgreSQL/PostgreSQLExtensions.cs index 9794e60f..5528b09c 100644 --- a/Providers/FreeSql.Provider.PostgreSQL/PostgreSQLExtensions.cs +++ b/Providers/FreeSql.Provider.PostgreSQL/PostgreSQLExtensions.cs @@ -1,4 +1,9 @@ -public static partial class FreeSqlPostgreSQLGlobalExtensions +using FreeSql; +using FreeSql.PostgreSQL.Curd; +using System; +using System.Linq.Expressions; + +public static partial class FreeSqlPostgreSQLGlobalExtensions { /// @@ -9,4 +14,14 @@ /// public static string FormatPostgreSQL(this string that, params object[] args) => _postgresqlAdo.Addslashes(that, args); static FreeSql.PostgreSQL.PostgreSQLAdo _postgresqlAdo = new FreeSql.PostgreSQL.PostgreSQLAdo(); + + /// + /// PostgreSQL9.5+ 特有的功能,On Conflict Do Update + /// 注意:此功能会开启插入【自增列】 + /// + /// + /// + /// 默认是以主键作为重复判断,也可以指定其他列:a => a.Name | a => new{a.Name,a.Time} | a => new[]{"name","time"} + /// + public static OnConflictDoUpdate OnConflictDoUpdate(this IInsert that, Expression> columns = null) where T1 : class => new FreeSql.PostgreSQL.Curd.OnConflictDoUpdate(that.InsertIdentity(), columns); }