From 838dd83e479562e3f153907d8e5eb5157668c2ad Mon Sep 17 00:00:00 2001 From: 28810 <28810@YEXIANGQIN> Date: Sat, 27 Jun 2020 17:01:51 +0800 Subject: [PATCH] =?UTF-8?q?-=20=E5=A2=9E=E5=8A=A0=20ISelect.AsTreeCte()=20?= =?UTF-8?q?=E9=80=92=E5=BD=92=E6=9F=A5=E8=AF=A2=E6=A0=91=E8=A1=A8=EF=BC=88?= =?UTF-8?q?=E5=90=91=E4=B8=8B=E6=88=96=E5=90=91=E4=B8=8B=EF=BC=89=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Dameng/Curd/DamengSelectTest.cs | 30 +++++++- .../KingbaseES/Curd/KingbaseESSelectTest.cs | 32 +++++++-- .../Oracle/Curd/OracleSelectTest.cs | 30 +++++++- .../PostgreSQL/Curd/PostgreSQLSelectTest.cs | 32 +++++++-- .../SqlServer/Curd/SqlServerSelectTest.cs | 30 +++++++- .../Dameng/Curd/DamengSelectTest.cs | 30 +++++++- .../Oracle/Curd/OracleSelectTest.cs | 30 +++++++- .../PostgreSQL/Curd/PostgreSQLSelectTest.cs | 32 +++++++-- .../SqlServer/Curd/SqlServerSelectTest.cs | 30 +++++++- .../Sqlite/Curd/SqliteSelectTest.cs | 30 +++++++- FreeSql/Extensions/FreeSqlGlobalExtensions.cs | 72 ++++++++++++++++--- FreeSql/FreeSql.xml | 12 ++-- .../KingbaseES/OdbcKingbaseESExpression.cs | 14 ++-- .../PostgreSQL/OdbcPostgreSQLExpression.cs | 14 ++-- .../SqlServer/OdbcSqlServerUtils.cs | 3 +- .../PostgreSQLExpression.cs | 16 ++--- .../SqlServerUtils.cs | 3 +- 17 files changed, 368 insertions(+), 72 deletions(-) diff --git a/FreeSql.Tests/FreeSql.Tests.Provider.Odbc/Dameng/Curd/DamengSelectTest.cs b/FreeSql.Tests/FreeSql.Tests.Provider.Odbc/Dameng/Curd/DamengSelectTest.cs index 6eeafa37..0f6cc2f5 100644 --- a/FreeSql.Tests/FreeSql.Tests.Provider.Odbc/Dameng/Curd/DamengSelectTest.cs +++ b/FreeSql.Tests/FreeSql.Tests.Provider.Odbc/Dameng/Curd/DamengSelectTest.cs @@ -1754,7 +1754,7 @@ WHERE (((to_char(a.""ID"")) in (SELECT b.""TITLE"" Assert.Equal("110100", t3[0].Childs[0].Childs[0].Code); Assert.Equal("110101", t3[0].Childs[0].Childs[1].Code); - t3 = fsql.Select().Where(a => a.Name == "中国").AsCteTree().OrderBy(a => a.Code).ToTreeList(); + t3 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte().OrderBy(a => a.Code).ToTreeList(); Assert.Single(t3); Assert.Equal("100000", t3[0].Code); Assert.Single(t3[0].Childs); @@ -1763,18 +1763,42 @@ WHERE (((to_char(a.""ID"")) in (SELECT b.""TITLE"" Assert.Equal("110100", t3[0].Childs[0].Childs[0].Code); Assert.Equal("110101", t3[0].Childs[0].Childs[1].Code); - t3 = fsql.Select().Where(a => a.Name == "中国").AsCteTree().OrderBy(a => a.Code).ToList(); + t3 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte().OrderBy(a => a.Code).ToList(); Assert.Equal(4, t3.Count); Assert.Equal("100000", t3[0].Code); Assert.Equal("110000", t3[1].Code); Assert.Equal("110100", t3[2].Code); Assert.Equal("110101", t3[3].Code); - t3 = fsql.Select().Where(a => a.Name == "北京").AsCteTree().OrderBy(a => a.Code).ToList(); + t3 = fsql.Select().Where(a => a.Name == "北京").AsTreeCte().OrderBy(a => a.Code).ToList(); Assert.Equal(3, t3.Count); Assert.Equal("110000", t3[0].Code); Assert.Equal("110100", t3[1].Code); Assert.Equal("110101", t3[2].Code); + + var t4 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte(a => a.Name).OrderBy(a => a.Code) + .ToList(a => new { item = a, level = Convert.ToInt32("a.cte_level"), path = "a.cte_path" }); + Assert.Equal(4, t4.Count); + Assert.Equal("100000", t4[0].item.Code); + Assert.Equal("110000", t4[1].item.Code); + Assert.Equal("110100", t4[2].item.Code); + Assert.Equal("110101", t4[3].item.Code); + Assert.Equal("中国", t4[0].path); + Assert.Equal("中国 -> 北京", t4[1].path); + Assert.Equal("中国 -> 北京 -> 北京市", t4[2].path); + Assert.Equal("中国 -> 北京 -> 东城区", t4[3].path); + + t4 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte(a => a.Name + "[" + a.Code + "]").OrderBy(a => a.Code) + .ToList(a => new { item = a, level = Convert.ToInt32("a.cte_level"), path = "a.cte_path" }); + Assert.Equal(4, t4.Count); + Assert.Equal("100000", t4[0].item.Code); + Assert.Equal("110000", t4[1].item.Code); + Assert.Equal("110100", t4[2].item.Code); + Assert.Equal("110101", t4[3].item.Code); + Assert.Equal("中国[100000]", t4[0].path); + Assert.Equal("中国[100000] -> 北京[110000]", t4[1].path); + Assert.Equal("中国[100000] -> 北京[110000] -> 北京市[110100]", t4[2].path); + Assert.Equal("中国[100000] -> 北京[110000] -> 东城区[110101]", t4[3].path); } [Table(Name = "D_District")] diff --git a/FreeSql.Tests/FreeSql.Tests.Provider.Odbc/KingbaseES/Curd/KingbaseESSelectTest.cs b/FreeSql.Tests/FreeSql.Tests.Provider.Odbc/KingbaseES/Curd/KingbaseESSelectTest.cs index 0e0d8697..5e0b9a0d 100644 --- a/FreeSql.Tests/FreeSql.Tests.Provider.Odbc/KingbaseES/Curd/KingbaseESSelectTest.cs +++ b/FreeSql.Tests/FreeSql.Tests.Provider.Odbc/KingbaseES/Curd/KingbaseESSelectTest.cs @@ -889,7 +889,7 @@ FROM ""TB_TOPIC22"" a", subquery); var subquery = select.Where(a => select.As("b").ToList(b => b.Title).Contains(a.Id.ToString())).ToSql(); Assert.Equal(@"SELECT a.""ID"", a.""CLICKS"", a.""TYPEGUID"", a.""TITLE"", a.""CREATETIME"" FROM ""TB_TOPIC22"" a -WHERE ((((a.""ID"")::varchar) in (SELECT b.""TITLE"" +WHERE ((((a.""ID"")::text) in (SELECT b.""TITLE"" FROM ""TB_TOPIC22"" b)))", subquery); var subqueryList = select.Where(a => select.As("b").ToList(b => b.Title).Contains(a.Id.ToString())).ToList(); } @@ -1719,7 +1719,7 @@ WHERE ((((a.""ID"")::varchar) in (SELECT b.""TITLE"" Assert.Equal("110100", t3[0].Childs[0].Childs[0].Code); Assert.Equal("110101", t3[0].Childs[0].Childs[1].Code); - t3 = fsql.Select().Where(a => a.Name == "中国").AsCteTree().OrderBy(a => a.Code).ToTreeList(); + t3 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte().OrderBy(a => a.Code).ToTreeList(); Assert.Single(t3); Assert.Equal("100000", t3[0].Code); Assert.Single(t3[0].Childs); @@ -1728,18 +1728,42 @@ WHERE ((((a.""ID"")::varchar) in (SELECT b.""TITLE"" Assert.Equal("110100", t3[0].Childs[0].Childs[0].Code); Assert.Equal("110101", t3[0].Childs[0].Childs[1].Code); - t3 = fsql.Select().Where(a => a.Name == "中国").AsCteTree().OrderBy(a => a.Code).ToList(); + t3 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte().OrderBy(a => a.Code).ToList(); Assert.Equal(4, t3.Count); Assert.Equal("100000", t3[0].Code); Assert.Equal("110000", t3[1].Code); Assert.Equal("110100", t3[2].Code); Assert.Equal("110101", t3[3].Code); - t3 = fsql.Select().Where(a => a.Name == "北京").AsCteTree().OrderBy(a => a.Code).ToList(); + t3 = fsql.Select().Where(a => a.Name == "北京").AsTreeCte().OrderBy(a => a.Code).ToList(); Assert.Equal(3, t3.Count); Assert.Equal("110000", t3[0].Code); Assert.Equal("110100", t3[1].Code); Assert.Equal("110101", t3[2].Code); + + var t4 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte(a => a.Name).OrderBy(a => a.Code) + .ToList(a => new { item = a, level = Convert.ToInt32("a.cte_level"), path = "a.cte_path" }); + Assert.Equal(4, t4.Count); + Assert.Equal("100000", t4[0].item.Code); + Assert.Equal("110000", t4[1].item.Code); + Assert.Equal("110100", t4[2].item.Code); + Assert.Equal("110101", t4[3].item.Code); + Assert.Equal("中国", t4[0].path); + Assert.Equal("中国 -> 北京", t4[1].path); + Assert.Equal("中国 -> 北京 -> 北京市", t4[2].path); + Assert.Equal("中国 -> 北京 -> 东城区", t4[3].path); + + t4 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte(a => a.Name + "[" + a.Code + "]").OrderBy(a => a.Code) + .ToList(a => new { item = a, level = Convert.ToInt32("a.cte_level"), path = "a.cte_path" }); + Assert.Equal(4, t4.Count); + Assert.Equal("100000", t4[0].item.Code); + Assert.Equal("110000", t4[1].item.Code); + Assert.Equal("110100", t4[2].item.Code); + Assert.Equal("110101", t4[3].item.Code); + Assert.Equal("中国[100000]", t4[0].path); + Assert.Equal("中国[100000] -> 北京[110000]", t4[1].path); + Assert.Equal("中国[100000] -> 北京[110000] -> 北京市[110100]", t4[2].path); + Assert.Equal("中国[100000] -> 北京[110000] -> 东城区[110101]", t4[3].path); } [Table(Name = "D_District")] diff --git a/FreeSql.Tests/FreeSql.Tests.Provider.Odbc/Oracle/Curd/OracleSelectTest.cs b/FreeSql.Tests/FreeSql.Tests.Provider.Odbc/Oracle/Curd/OracleSelectTest.cs index 8b849b1a..827fa198 100644 --- a/FreeSql.Tests/FreeSql.Tests.Provider.Odbc/Oracle/Curd/OracleSelectTest.cs +++ b/FreeSql.Tests/FreeSql.Tests.Provider.Odbc/Oracle/Curd/OracleSelectTest.cs @@ -1755,7 +1755,7 @@ WHERE (((to_char(a.""ID"")) in (SELECT b.""TITLE"" Assert.Equal("110100", t3[0].Childs[0].Childs[0].Code); Assert.Equal("110101", t3[0].Childs[0].Childs[1].Code); - t3 = fsql.Select().Where(a => a.Name == "中国").AsCteTree().OrderBy(a => a.Code).ToTreeList(); + t3 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte().OrderBy(a => a.Code).ToTreeList(); Assert.Single(t3); Assert.Equal("100000", t3[0].Code); Assert.Single(t3[0].Childs); @@ -1764,18 +1764,42 @@ WHERE (((to_char(a.""ID"")) in (SELECT b.""TITLE"" Assert.Equal("110100", t3[0].Childs[0].Childs[0].Code); Assert.Equal("110101", t3[0].Childs[0].Childs[1].Code); - t3 = fsql.Select().Where(a => a.Name == "中国").AsCteTree().OrderBy(a => a.Code).ToList(); + t3 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte().OrderBy(a => a.Code).ToList(); Assert.Equal(4, t3.Count); Assert.Equal("100000", t3[0].Code); Assert.Equal("110000", t3[1].Code); Assert.Equal("110100", t3[2].Code); Assert.Equal("110101", t3[3].Code); - t3 = fsql.Select().Where(a => a.Name == "北京").AsCteTree().OrderBy(a => a.Code).ToList(); + t3 = fsql.Select().Where(a => a.Name == "北京").AsTreeCte().OrderBy(a => a.Code).ToList(); Assert.Equal(3, t3.Count); Assert.Equal("110000", t3[0].Code); Assert.Equal("110100", t3[1].Code); Assert.Equal("110101", t3[2].Code); + + var t4 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte(a => a.Name).OrderBy(a => a.Code) + .ToList(a => new { item = a, level = Convert.ToInt32("a.cte_level"), path = "a.cte_path" }); + Assert.Equal(4, t4.Count); + Assert.Equal("100000", t4[0].item.Code); + Assert.Equal("110000", t4[1].item.Code); + Assert.Equal("110100", t4[2].item.Code); + Assert.Equal("110101", t4[3].item.Code); + Assert.Equal("中国", t4[0].path); + Assert.Equal("中国 -> 北京", t4[1].path); + Assert.Equal("中国 -> 北京 -> 北京市", t4[2].path); + Assert.Equal("中国 -> 北京 -> 东城区", t4[3].path); + + t4 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte(a => a.Name + "[" + a.Code + "]").OrderBy(a => a.Code) + .ToList(a => new { item = a, level = Convert.ToInt32("a.cte_level"), path = "a.cte_path" }); + Assert.Equal(4, t4.Count); + Assert.Equal("100000", t4[0].item.Code); + Assert.Equal("110000", t4[1].item.Code); + Assert.Equal("110100", t4[2].item.Code); + Assert.Equal("110101", t4[3].item.Code); + Assert.Equal("中国[100000]", t4[0].path); + Assert.Equal("中国[100000] -> 北京[110000]", t4[1].path); + Assert.Equal("中国[100000] -> 北京[110000] -> 北京市[110100]", t4[2].path); + Assert.Equal("中国[100000] -> 北京[110000] -> 东城区[110101]", t4[3].path); } [Table(Name = "D_District")] diff --git a/FreeSql.Tests/FreeSql.Tests.Provider.Odbc/PostgreSQL/Curd/PostgreSQLSelectTest.cs b/FreeSql.Tests/FreeSql.Tests.Provider.Odbc/PostgreSQL/Curd/PostgreSQLSelectTest.cs index 96cec0f8..00e356e7 100644 --- a/FreeSql.Tests/FreeSql.Tests.Provider.Odbc/PostgreSQL/Curd/PostgreSQLSelectTest.cs +++ b/FreeSql.Tests/FreeSql.Tests.Provider.Odbc/PostgreSQL/Curd/PostgreSQLSelectTest.cs @@ -985,7 +985,7 @@ FROM ""tb_topic"" a", subquery); var subquery = select.Where(a => select.As("b").ToList(b => b.Title).Contains(a.Id.ToString())).ToSql(); Assert.Equal(@"SELECT a.""id"", a.""clicks"", a.""typeguid"", a.""title"", a.""createtime"" FROM ""tb_topic"" a -WHERE ((((a.""id"")::varchar) in (SELECT b.""title"" +WHERE ((((a.""id"")::text) in (SELECT b.""title"" FROM ""tb_topic"" b)))", subquery); var subqueryList = select.Where(a => select.As("b").ToList(b => b.Title).Contains(a.Id.ToString())).ToList(); } @@ -1814,7 +1814,7 @@ WHERE ((((a.""id"")::varchar) in (SELECT b.""title"" Assert.Equal("110100", t3[0].Childs[0].Childs[0].Code); Assert.Equal("110101", t3[0].Childs[0].Childs[1].Code); - t3 = fsql.Select().Where(a => a.Name == "中国").AsCteTree().OrderBy(a => a.Code).ToTreeList(); + t3 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte().OrderBy(a => a.Code).ToTreeList(); Assert.Single(t3); Assert.Equal("100000", t3[0].Code); Assert.Single(t3[0].Childs); @@ -1823,18 +1823,42 @@ WHERE ((((a.""id"")::varchar) in (SELECT b.""title"" Assert.Equal("110100", t3[0].Childs[0].Childs[0].Code); Assert.Equal("110101", t3[0].Childs[0].Childs[1].Code); - t3 = fsql.Select().Where(a => a.Name == "中国").AsCteTree().OrderBy(a => a.Code).ToList(); + t3 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte().OrderBy(a => a.Code).ToList(); Assert.Equal(4, t3.Count); Assert.Equal("100000", t3[0].Code); Assert.Equal("110000", t3[1].Code); Assert.Equal("110100", t3[2].Code); Assert.Equal("110101", t3[3].Code); - t3 = fsql.Select().Where(a => a.Name == "北京").AsCteTree().OrderBy(a => a.Code).ToList(); + t3 = fsql.Select().Where(a => a.Name == "北京").AsTreeCte().OrderBy(a => a.Code).ToList(); Assert.Equal(3, t3.Count); Assert.Equal("110000", t3[0].Code); Assert.Equal("110100", t3[1].Code); Assert.Equal("110101", t3[2].Code); + + var t4 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte(a => a.Name).OrderBy(a => a.Code) + .ToList(a => new { item = a, level = Convert.ToInt32("a.cte_level"), path = "a.cte_path" }); + Assert.Equal(4, t4.Count); + Assert.Equal("100000", t4[0].item.Code); + Assert.Equal("110000", t4[1].item.Code); + Assert.Equal("110100", t4[2].item.Code); + Assert.Equal("110101", t4[3].item.Code); + Assert.Equal("中国", t4[0].path); + Assert.Equal("中国 -> 北京", t4[1].path); + Assert.Equal("中国 -> 北京 -> 北京市", t4[2].path); + Assert.Equal("中国 -> 北京 -> 东城区", t4[3].path); + + t4 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte(a => a.Name + "[" + a.Code + "]").OrderBy(a => a.Code) + .ToList(a => new { item = a, level = Convert.ToInt32("a.cte_level"), path = "a.cte_path" }); + Assert.Equal(4, t4.Count); + Assert.Equal("100000", t4[0].item.Code); + Assert.Equal("110000", t4[1].item.Code); + Assert.Equal("110100", t4[2].item.Code); + Assert.Equal("110101", t4[3].item.Code); + Assert.Equal("中国[100000]", t4[0].path); + Assert.Equal("中国[100000] -> 北京[110000]", t4[1].path); + Assert.Equal("中国[100000] -> 北京[110000] -> 北京市[110100]", t4[2].path); + Assert.Equal("中国[100000] -> 北京[110000] -> 东城区[110101]", t4[3].path); } [Table(Name = "D_District")] diff --git a/FreeSql.Tests/FreeSql.Tests.Provider.Odbc/SqlServer/Curd/SqlServerSelectTest.cs b/FreeSql.Tests/FreeSql.Tests.Provider.Odbc/SqlServer/Curd/SqlServerSelectTest.cs index 92b4fe3f..4cd9858b 100644 --- a/FreeSql.Tests/FreeSql.Tests.Provider.Odbc/SqlServer/Curd/SqlServerSelectTest.cs +++ b/FreeSql.Tests/FreeSql.Tests.Provider.Odbc/SqlServer/Curd/SqlServerSelectTest.cs @@ -1705,7 +1705,7 @@ WHERE (((cast(a.[Id] as nvarchar(100))) in (SELECT b.[Title] Assert.Equal("110100", t3[0].Childs[0].Childs[0].Code); Assert.Equal("110101", t3[0].Childs[0].Childs[1].Code); - t3 = fsql.Select().Where(a => a.Name == "中国").AsCteTree().OrderBy(a => a.Code).ToTreeList(); + t3 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte().OrderBy(a => a.Code).ToTreeList(); Assert.Single(t3); Assert.Equal("100000", t3[0].Code); Assert.Single(t3[0].Childs); @@ -1714,18 +1714,42 @@ WHERE (((cast(a.[Id] as nvarchar(100))) in (SELECT b.[Title] Assert.Equal("110100", t3[0].Childs[0].Childs[0].Code); Assert.Equal("110101", t3[0].Childs[0].Childs[1].Code); - t3 = fsql.Select().Where(a => a.Name == "中国").AsCteTree().OrderBy(a => a.Code).ToList(); + t3 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte().OrderBy(a => a.Code).ToList(); Assert.Equal(4, t3.Count); Assert.Equal("100000", t3[0].Code); Assert.Equal("110000", t3[1].Code); Assert.Equal("110100", t3[2].Code); Assert.Equal("110101", t3[3].Code); - t3 = fsql.Select().Where(a => a.Name == "北京").AsCteTree().OrderBy(a => a.Code).ToList(); + t3 = fsql.Select().Where(a => a.Name == "北京").AsTreeCte().OrderBy(a => a.Code).ToList(); Assert.Equal(3, t3.Count); Assert.Equal("110000", t3[0].Code); Assert.Equal("110100", t3[1].Code); Assert.Equal("110101", t3[2].Code); + + var t4 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte(a => a.Name).OrderBy(a => a.Code) + .ToList(a => new { item = a, level = Convert.ToInt32("a.cte_level"), path = "a.cte_path" }); + Assert.Equal(4, t4.Count); + Assert.Equal("100000", t4[0].item.Code); + Assert.Equal("110000", t4[1].item.Code); + Assert.Equal("110100", t4[2].item.Code); + Assert.Equal("110101", t4[3].item.Code); + Assert.Equal("中国", t4[0].path); + Assert.Equal("中国 -> 北京", t4[1].path); + Assert.Equal("中国 -> 北京 -> 北京市", t4[2].path); + Assert.Equal("中国 -> 北京 -> 东城区", t4[3].path); + + t4 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte(a => a.Name + "[" + a.Code + "]").OrderBy(a => a.Code) + .ToList(a => new { item = a, level = Convert.ToInt32("a.cte_level"), path = "a.cte_path" }); + Assert.Equal(4, t4.Count); + Assert.Equal("100000", t4[0].item.Code); + Assert.Equal("110000", t4[1].item.Code); + Assert.Equal("110100", t4[2].item.Code); + Assert.Equal("110101", t4[3].item.Code); + Assert.Equal("中国[100000]", t4[0].path); + Assert.Equal("中国[100000] -> 北京[110000]", t4[1].path); + Assert.Equal("中国[100000] -> 北京[110000] -> 北京市[110100]", t4[2].path); + Assert.Equal("中国[100000] -> 北京[110000] -> 东城区[110101]", t4[3].path); } [Table(Name = "D_District")] diff --git a/FreeSql.Tests/FreeSql.Tests/Dameng/Curd/DamengSelectTest.cs b/FreeSql.Tests/FreeSql.Tests/Dameng/Curd/DamengSelectTest.cs index c6e2751f..c5597f2a 100644 --- a/FreeSql.Tests/FreeSql.Tests/Dameng/Curd/DamengSelectTest.cs +++ b/FreeSql.Tests/FreeSql.Tests/Dameng/Curd/DamengSelectTest.cs @@ -1755,7 +1755,7 @@ WHERE (((to_char(a.""ID"")) in (SELECT b.""TITLE"" Assert.Equal("110100", t3[0].Childs[0].Childs[0].Code); Assert.Equal("110101", t3[0].Childs[0].Childs[1].Code); - t3 = fsql.Select().Where(a => a.Name == "中国").AsCteTree().OrderBy(a => a.Code).ToTreeList(); + t3 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte().OrderBy(a => a.Code).ToTreeList(); Assert.Single(t3); Assert.Equal("100000", t3[0].Code); Assert.Single(t3[0].Childs); @@ -1764,18 +1764,42 @@ WHERE (((to_char(a.""ID"")) in (SELECT b.""TITLE"" Assert.Equal("110100", t3[0].Childs[0].Childs[0].Code); Assert.Equal("110101", t3[0].Childs[0].Childs[1].Code); - t3 = fsql.Select().Where(a => a.Name == "中国").AsCteTree().OrderBy(a => a.Code).ToList(); + t3 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte().OrderBy(a => a.Code).ToList(); Assert.Equal(4, t3.Count); Assert.Equal("100000", t3[0].Code); Assert.Equal("110000", t3[1].Code); Assert.Equal("110100", t3[2].Code); Assert.Equal("110101", t3[3].Code); - t3 = fsql.Select().Where(a => a.Name == "北京").AsCteTree().OrderBy(a => a.Code).ToList(); + t3 = fsql.Select().Where(a => a.Name == "北京").AsTreeCte().OrderBy(a => a.Code).ToList(); Assert.Equal(3, t3.Count); Assert.Equal("110000", t3[0].Code); Assert.Equal("110100", t3[1].Code); Assert.Equal("110101", t3[2].Code); + + var t4 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte(a => a.Name).OrderBy(a => a.Code) + .ToList(a => new { item = a, level = Convert.ToInt32("a.cte_level"), path = "a.cte_path" }); + Assert.Equal(4, t4.Count); + Assert.Equal("100000", t4[0].item.Code); + Assert.Equal("110000", t4[1].item.Code); + Assert.Equal("110100", t4[2].item.Code); + Assert.Equal("110101", t4[3].item.Code); + Assert.Equal("中国", t4[0].path); + Assert.Equal("中国 -> 北京", t4[1].path); + Assert.Equal("中国 -> 北京 -> 北京市", t4[2].path); + Assert.Equal("中国 -> 北京 -> 东城区", t4[3].path); + + t4 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte(a => a.Name + "[" + a.Code + "]").OrderBy(a => a.Code) + .ToList(a => new { item = a, level = Convert.ToInt32("a.cte_level"), path = "a.cte_path" }); + Assert.Equal(4, t4.Count); + Assert.Equal("100000", t4[0].item.Code); + Assert.Equal("110000", t4[1].item.Code); + Assert.Equal("110100", t4[2].item.Code); + Assert.Equal("110101", t4[3].item.Code); + Assert.Equal("中国[100000]", t4[0].path); + Assert.Equal("中国[100000] -> 北京[110000]", t4[1].path); + Assert.Equal("中国[100000] -> 北京[110000] -> 北京市[110100]", t4[2].path); + Assert.Equal("中国[100000] -> 北京[110000] -> 东城区[110101]", t4[3].path); } [Table(Name = "D_District")] diff --git a/FreeSql.Tests/FreeSql.Tests/Oracle/Curd/OracleSelectTest.cs b/FreeSql.Tests/FreeSql.Tests/Oracle/Curd/OracleSelectTest.cs index 3a922613..0b7888c2 100644 --- a/FreeSql.Tests/FreeSql.Tests/Oracle/Curd/OracleSelectTest.cs +++ b/FreeSql.Tests/FreeSql.Tests/Oracle/Curd/OracleSelectTest.cs @@ -1755,7 +1755,7 @@ WHERE (((to_char(a.""ID"")) in (SELECT b.""TITLE"" Assert.Equal("110100", t3[0].Childs[0].Childs[0].Code); Assert.Equal("110101", t3[0].Childs[0].Childs[1].Code); - t3 = fsql.Select().Where(a => a.Name == "中国").AsCteTree().OrderBy(a => a.Code).ToTreeList(); + t3 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte().OrderBy(a => a.Code).ToTreeList(); Assert.Single(t3); Assert.Equal("100000", t3[0].Code); Assert.Single(t3[0].Childs); @@ -1764,18 +1764,42 @@ WHERE (((to_char(a.""ID"")) in (SELECT b.""TITLE"" Assert.Equal("110100", t3[0].Childs[0].Childs[0].Code); Assert.Equal("110101", t3[0].Childs[0].Childs[1].Code); - t3 = fsql.Select().Where(a => a.Name == "中国").AsCteTree().OrderBy(a => a.Code).ToList(); + t3 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte().OrderBy(a => a.Code).ToList(); Assert.Equal(4, t3.Count); Assert.Equal("100000", t3[0].Code); Assert.Equal("110000", t3[1].Code); Assert.Equal("110100", t3[2].Code); Assert.Equal("110101", t3[3].Code); - t3 = fsql.Select().Where(a => a.Name == "北京").AsCteTree().OrderBy(a => a.Code).ToList(); + t3 = fsql.Select().Where(a => a.Name == "北京").AsTreeCte().OrderBy(a => a.Code).ToList(); Assert.Equal(3, t3.Count); Assert.Equal("110000", t3[0].Code); Assert.Equal("110100", t3[1].Code); Assert.Equal("110101", t3[2].Code); + + var t4 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte(a => a.Name).OrderBy(a => a.Code) + .ToList(a => new { item = a, level = Convert.ToInt32("a.cte_level"), path = "a.cte_path" }); + Assert.Equal(4, t4.Count); + Assert.Equal("100000", t4[0].item.Code); + Assert.Equal("110000", t4[1].item.Code); + Assert.Equal("110100", t4[2].item.Code); + Assert.Equal("110101", t4[3].item.Code); + Assert.Equal("中国", t4[0].path); + Assert.Equal("中国 -> 北京", t4[1].path); + Assert.Equal("中国 -> 北京 -> 北京市", t4[2].path); + Assert.Equal("中国 -> 北京 -> 东城区", t4[3].path); + + t4 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte(a => a.Name + "[" + a.Code + "]").OrderBy(a => a.Code) + .ToList(a => new { item = a, level = Convert.ToInt32("a.cte_level"), path = "a.cte_path" }); + Assert.Equal(4, t4.Count); + Assert.Equal("100000", t4[0].item.Code); + Assert.Equal("110000", t4[1].item.Code); + Assert.Equal("110100", t4[2].item.Code); + Assert.Equal("110101", t4[3].item.Code); + Assert.Equal("中国[100000]", t4[0].path); + Assert.Equal("中国[100000] -> 北京[110000]", t4[1].path); + Assert.Equal("中国[100000] -> 北京[110000] -> 北京市[110100]", t4[2].path); + Assert.Equal("中国[100000] -> 北京[110000] -> 东城区[110101]", t4[3].path); } [Table(Name = "D_District")] diff --git a/FreeSql.Tests/FreeSql.Tests/PostgreSQL/Curd/PostgreSQLSelectTest.cs b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/Curd/PostgreSQLSelectTest.cs index 4ce6ea32..2bede6a8 100644 --- a/FreeSql.Tests/FreeSql.Tests/PostgreSQL/Curd/PostgreSQLSelectTest.cs +++ b/FreeSql.Tests/FreeSql.Tests/PostgreSQL/Curd/PostgreSQLSelectTest.cs @@ -1002,7 +1002,7 @@ FROM ""tb_topic"" a", subquery); var subquery = select.Where(a => select.As("b").ToList(b => b.Title).Contains(a.Id.ToString())).ToSql(); Assert.Equal(@"SELECT a.""id"", a.""clicks"", a.""typeguid"", a.""title"", a.""createtime"" FROM ""tb_topic"" a -WHERE ((((a.""id"")::varchar) in (SELECT b.""title"" +WHERE ((((a.""id"")::text) in (SELECT b.""title"" FROM ""tb_topic"" b)))", subquery); var subqueryList = select.Where(a => select.As("b").ToList(b => b.Title).Contains(a.Id.ToString())).ToList(); } @@ -1830,7 +1830,7 @@ WHERE ((((a.""id"")::varchar) in (SELECT b.""title"" Assert.Equal("110100", t3[0].Childs[0].Childs[0].Code); Assert.Equal("110101", t3[0].Childs[0].Childs[1].Code); - t3 = fsql.Select().Where(a => a.Name == "中国").AsCteTree().OrderBy(a => a.Code).ToTreeList(); + t3 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte().OrderBy(a => a.Code).ToTreeList(); Assert.Single(t3); Assert.Equal("100000", t3[0].Code); Assert.Single(t3[0].Childs); @@ -1839,18 +1839,42 @@ WHERE ((((a.""id"")::varchar) in (SELECT b.""title"" Assert.Equal("110100", t3[0].Childs[0].Childs[0].Code); Assert.Equal("110101", t3[0].Childs[0].Childs[1].Code); - t3 = fsql.Select().Where(a => a.Name == "中国").AsCteTree().OrderBy(a => a.Code).ToList(); + t3 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte().OrderBy(a => a.Code).ToList(); Assert.Equal(4, t3.Count); Assert.Equal("100000", t3[0].Code); Assert.Equal("110000", t3[1].Code); Assert.Equal("110100", t3[2].Code); Assert.Equal("110101", t3[3].Code); - t3 = fsql.Select().Where(a => a.Name == "北京").AsCteTree().OrderBy(a => a.Code).ToList(); + t3 = fsql.Select().Where(a => a.Name == "北京").AsTreeCte().OrderBy(a => a.Code).ToList(); Assert.Equal(3, t3.Count); Assert.Equal("110000", t3[0].Code); Assert.Equal("110100", t3[1].Code); Assert.Equal("110101", t3[2].Code); + + var t4 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte(a => a.Name).OrderBy(a => a.Code) + .ToList(a => new { item = a, level = Convert.ToInt32("a.cte_level"), path = "a.cte_path" }); + Assert.Equal(4, t4.Count); + Assert.Equal("100000", t4[0].item.Code); + Assert.Equal("110000", t4[1].item.Code); + Assert.Equal("110100", t4[2].item.Code); + Assert.Equal("110101", t4[3].item.Code); + Assert.Equal("中国", t4[0].path); + Assert.Equal("中国 -> 北京", t4[1].path); + Assert.Equal("中国 -> 北京 -> 北京市", t4[2].path); + Assert.Equal("中国 -> 北京 -> 东城区", t4[3].path); + + t4 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte(a => a.Name + "[" + a.Code + "]").OrderBy(a => a.Code) + .ToList(a => new { item = a, level = Convert.ToInt32("a.cte_level"), path = "a.cte_path" }); + Assert.Equal(4, t4.Count); + Assert.Equal("100000", t4[0].item.Code); + Assert.Equal("110000", t4[1].item.Code); + Assert.Equal("110100", t4[2].item.Code); + Assert.Equal("110101", t4[3].item.Code); + Assert.Equal("中国[100000]", t4[0].path); + Assert.Equal("中国[100000] -> 北京[110000]", t4[1].path); + Assert.Equal("中国[100000] -> 北京[110000] -> 北京市[110100]", t4[2].path); + Assert.Equal("中国[100000] -> 北京[110000] -> 东城区[110101]", t4[3].path); } [Table(Name = "D_District")] diff --git a/FreeSql.Tests/FreeSql.Tests/SqlServer/Curd/SqlServerSelectTest.cs b/FreeSql.Tests/FreeSql.Tests/SqlServer/Curd/SqlServerSelectTest.cs index 3efaedbf..de245c0c 100644 --- a/FreeSql.Tests/FreeSql.Tests/SqlServer/Curd/SqlServerSelectTest.cs +++ b/FreeSql.Tests/FreeSql.Tests/SqlServer/Curd/SqlServerSelectTest.cs @@ -1786,7 +1786,7 @@ WHERE (((cast(a.[Id] as nvarchar(100))) in (SELECT b.[Title] Assert.Equal("110100", t3[0].Childs[0].Childs[0].Code); Assert.Equal("110101", t3[0].Childs[0].Childs[1].Code); - t3 = fsql.Select().Where(a => a.Name == "中国").AsCteTree().OrderBy(a => a.Code).ToTreeList(); + t3 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte().OrderBy(a => a.Code).ToTreeList(); Assert.Single(t3); Assert.Equal("100000", t3[0].Code); Assert.Single(t3[0].Childs); @@ -1795,18 +1795,42 @@ WHERE (((cast(a.[Id] as nvarchar(100))) in (SELECT b.[Title] Assert.Equal("110100", t3[0].Childs[0].Childs[0].Code); Assert.Equal("110101", t3[0].Childs[0].Childs[1].Code); - t3 = fsql.Select().Where(a => a.Name == "中国").AsCteTree().OrderBy(a => a.Code).ToList(); + t3 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte().OrderBy(a => a.Code).ToList(); Assert.Equal(4, t3.Count); Assert.Equal("100000", t3[0].Code); Assert.Equal("110000", t3[1].Code); Assert.Equal("110100", t3[2].Code); Assert.Equal("110101", t3[3].Code); - t3 = fsql.Select().Where(a => a.Name == "北京").AsCteTree().OrderBy(a => a.Code).ToList(); + t3 = fsql.Select().Where(a => a.Name == "北京").AsTreeCte().OrderBy(a => a.Code).ToList(); Assert.Equal(3, t3.Count); Assert.Equal("110000", t3[0].Code); Assert.Equal("110100", t3[1].Code); Assert.Equal("110101", t3[2].Code); + + var t4 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte(a => a.Name).OrderBy(a => a.Code) + .ToList(a => new { item = a, level = Convert.ToInt32("a.cte_level"), path = "a.cte_path" }); + Assert.Equal(4, t4.Count); + Assert.Equal("100000", t4[0].item.Code); + Assert.Equal("110000", t4[1].item.Code); + Assert.Equal("110100", t4[2].item.Code); + Assert.Equal("110101", t4[3].item.Code); + Assert.Equal("中国", t4[0].path); + Assert.Equal("中国 -> 北京", t4[1].path); + Assert.Equal("中国 -> 北京 -> 北京市", t4[2].path); + Assert.Equal("中国 -> 北京 -> 东城区", t4[3].path); + + t4 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte(a => a.Name + "[" + a.Code + "]").OrderBy(a => a.Code) + .ToList(a => new { item = a, level = Convert.ToInt32("a.cte_level"), path = "a.cte_path" }); + Assert.Equal(4, t4.Count); + Assert.Equal("100000", t4[0].item.Code); + Assert.Equal("110000", t4[1].item.Code); + Assert.Equal("110100", t4[2].item.Code); + Assert.Equal("110101", t4[3].item.Code); + Assert.Equal("中国[100000]", t4[0].path); + Assert.Equal("中国[100000] -> 北京[110000]", t4[1].path); + Assert.Equal("中国[100000] -> 北京[110000] -> 北京市[110100]", t4[2].path); + Assert.Equal("中国[100000] -> 北京[110000] -> 东城区[110101]", t4[3].path); } [Table(Name = "D_District")] diff --git a/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteSelectTest.cs b/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteSelectTest.cs index 9631464f..7ec386cc 100644 --- a/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteSelectTest.cs +++ b/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteSelectTest.cs @@ -1904,7 +1904,7 @@ WHERE (((cast(a.""Id"" as character)) in (SELECT b.""Title"" Assert.Equal("110100", t3[0].Childs[0].Childs[0].Code); Assert.Equal("110101", t3[0].Childs[0].Childs[1].Code); - t3 = fsql.Select().Where(a => a.Name == "中国").AsCteTree().OrderBy(a => a.Code).ToTreeList(); + t3 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte().OrderBy(a => a.Code).ToTreeList(); Assert.Single(t3); Assert.Equal("100000", t3[0].Code); Assert.Single(t3[0].Childs); @@ -1913,18 +1913,42 @@ WHERE (((cast(a.""Id"" as character)) in (SELECT b.""Title"" Assert.Equal("110100", t3[0].Childs[0].Childs[0].Code); Assert.Equal("110101", t3[0].Childs[0].Childs[1].Code); - t3 = fsql.Select().Where(a => a.Name == "中国").AsCteTree().OrderBy(a => a.Code).ToList(); + t3 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte().OrderBy(a => a.Code).ToList(); Assert.Equal(4, t3.Count); Assert.Equal("100000", t3[0].Code); Assert.Equal("110000", t3[1].Code); Assert.Equal("110100", t3[2].Code); Assert.Equal("110101", t3[3].Code); - t3 = fsql.Select().Where(a => a.Name == "北京").AsCteTree().OrderBy(a => a.Code).ToList(); + t3 = fsql.Select().Where(a => a.Name == "北京").AsTreeCte().OrderBy(a => a.Code).ToList(); Assert.Equal(3, t3.Count); Assert.Equal("110000", t3[0].Code); Assert.Equal("110100", t3[1].Code); Assert.Equal("110101", t3[2].Code); + + var t4 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte(a => a.Name).OrderBy(a => a.Code) + .ToList(a => new { item = a, level = Convert.ToInt32("a.cte_level"), path = "a.cte_path" }); + Assert.Equal(4, t4.Count); + Assert.Equal("100000", t4[0].item.Code); + Assert.Equal("110000", t4[1].item.Code); + Assert.Equal("110100", t4[2].item.Code); + Assert.Equal("110101", t4[3].item.Code); + Assert.Equal("中国", t4[0].path); + Assert.Equal("中国 -> 北京", t4[1].path); + Assert.Equal("中国 -> 北京 -> 北京市", t4[2].path); + Assert.Equal("中国 -> 北京 -> 东城区", t4[3].path); + + t4 = fsql.Select().Where(a => a.Name == "中国").AsTreeCte(a => a.Name + "[" + a.Code + "]").OrderBy(a => a.Code) + .ToList(a => new { item = a, level = Convert.ToInt32("a.cte_level"), path = "a.cte_path" }); + Assert.Equal(4, t4.Count); + Assert.Equal("100000", t4[0].item.Code); + Assert.Equal("110000", t4[1].item.Code); + Assert.Equal("110100", t4[2].item.Code); + Assert.Equal("110101", t4[3].item.Code); + Assert.Equal("中国[100000]", t4[0].path); + Assert.Equal("中国[100000] -> 北京[110000]", t4[1].path); + Assert.Equal("中国[100000] -> 北京[110000] -> 北京市[110100]", t4[2].path); + Assert.Equal("中国[100000] -> 北京[110000] -> 东城区[110101]", t4[3].path); } [Table(Name = "D_District")] diff --git a/FreeSql/Extensions/FreeSqlGlobalExtensions.cs b/FreeSql/Extensions/FreeSqlGlobalExtensions.cs index 9b16690d..a1a56715 100644 --- a/FreeSql/Extensions/FreeSqlGlobalExtensions.cs +++ b/FreeSql/Extensions/FreeSqlGlobalExtensions.cs @@ -386,16 +386,24 @@ public static partial class FreeSqlGlobalExtensions #endif #endregion - #region WhereTree(..) 递归查询 + #region AsTreeCte(..) 递归查询 /// - /// 使用递归 CTE 查询树型的所有子数据。 - /// 通过测试的数据库:MySql8.0、SqlServer、PostgreSQL、Oracle、Sqlite、达梦、人大金仓 + /// 使用递归 CTE 查询树型的所有子记录,或者所有父记录。 + /// 通过测试的数据库:MySql8.0、SqlServer、PostgreSQL、Oracle、Sqlite、达梦、人大金仓 + /// 返回隐藏字段:.ToList(a => new { item = a, level = "a.cte_level", path = "a.cte_path" }) /// /// /// - /// 深度 + /// false(默认):由父级向子级的递归查询true:由子级向父级的递归查询 + /// 路径内容选择 + /// 连接路径内容 + /// 递归层级 /// - public static ISelect AsCteTree(this ISelect that, int depth = -1) where T1 : class + public static ISelect AsTreeCte(this ISelect that, + Expression> pathSelector = null, + bool up = false, + string pathSeparator = " -> ", + int level = -1) where T1 : class { var select = that as Select1Provider; var tb = select._tables[0].Table; @@ -407,24 +415,66 @@ public static partial class FreeSqlGlobalExtensions if (navs.Length != 1) throw new ArgumentException($"{tb.Type.FullName} 不是父子关系,无法使用该功能"); var tbref = navs[0]; - var cteName = "as_cte_tree"; + var cteName = "as_tree_cte"; if (select._orm.CodeFirst.IsSyncStructureToLower) cteName = cteName.ToLower(); if (select._orm.CodeFirst.IsSyncStructureToUpper) cteName = cteName.ToUpper(); - var sql1 = select.ToSql($"0 as as_cte_tree_depth, {select.GetAllFieldExpressionTreeLevel2().Field}").Trim(); + var sql1ctePath = ""; + if (pathSelector != null) + { + select._tables[0].Parameter = pathSelector?.Parameters[0]; + switch (select._orm.Ado.DataType) + { + case DataType.PostgreSQL: + case DataType.OdbcPostgreSQL: + case DataType.OdbcKingbaseES: + case DataType.ShenTong: //神通测试未通过 + case DataType.SqlServer: + case DataType.OdbcSqlServer: + sql1ctePath = select._commonExpression.ExpressionWhereLambda(select._tables, Expression.Call(typeof(Convert).GetMethod("ToString", new Type[] { typeof(string) }), pathSelector?.Body), null, null, null); + break; + default: + sql1ctePath = select._commonExpression.ExpressionWhereLambda(select._tables, pathSelector?.Body, null, null, null); + break; + } + sql1ctePath = $"{sql1ctePath} as cte_path, "; + } + var sql1 = select.ToSql($"0 as cte_level, {sql1ctePath}{select.GetAllFieldExpressionTreeLevel2().Field}").Trim(); select._where.Clear(); select.As("wct2"); var sql2Field = select.GetAllFieldExpressionTreeLevel2().Field; + var sql2InnerJoinOn = up == false ? + string.Join(" and ", tbref.Columns.Select((a, z) => $"wct2.{select._commonUtils.QuoteSqlName(tbref.RefColumns[z].Attribute.Name)} = wct1.{select._commonUtils.QuoteSqlName(a.Attribute.Name)}")) : + string.Join(" and ", tbref.Columns.Select((a, z) => $"wct2.{select._commonUtils.QuoteSqlName(a.Attribute.Name)} = wct1.{select._commonUtils.QuoteSqlName(tbref.RefColumns[z].Attribute.Name)}")); + + var sql2ctePath = ""; + if (pathSelector != null) + { + select._tables[0].Parameter = pathSelector?.Parameters[0]; + var wct2ctePath = select._commonExpression.ExpressionWhereLambda(select._tables, pathSelector?.Body, null, null, null); + sql2ctePath = select._commonUtils.StringConcat( + new string[] { + up == false ? "wct1.cte_path" : wct2ctePath, + select._commonUtils.FormatSql("{0}", pathSeparator), + up == false ? wct2ctePath : "wct1.cte_path" + }, new Type[] { + typeof(string), + typeof(string), + typeof(string) + }); + sql2ctePath = $"{sql2ctePath} as cte_path, "; + } var sql2 = select .AsAlias((type, old) => type == tb.Type ? old.Replace("wct2", "wct1") : old) .AsTable((type, old) => type == tb.Type ? cteName : old) - .InnerJoin($"{select._commonUtils.QuoteSqlName(tb.DbName)} wct2 ON {string.Join(" and ", tbref.Columns.Select((a,z) => $"wct2.{select._commonUtils.QuoteSqlName(tbref.RefColumns[z].Attribute.Name)} = wct1.{select._commonUtils.QuoteSqlName(a.Attribute.Name)}"))}") - .ToSql($"wct1.as_cte_tree_depth + 1 as as_cte_tree_depth, {sql2Field}").Trim(); + .InnerJoin($"{select._commonUtils.QuoteSqlName(tb.DbName)} wct2 ON {sql2InnerJoinOn}") + .ToSql($"wct1.cte_level + 1 as cte_level, {sql2ctePath}{sql2Field}").Trim(); var newSelect = select._orm.Select() .AsType(tb.Type) .AsTable((type, old) => type == tb.Type ? cteName : old) - .WhereIf(depth > 0, $"a.as_cte_tree_depth < {depth + 1}") as Select1Provider; + .WhereIf(level > 0, $"a.cte_level < {level + 1}") + .OrderBy(up, "a.cte_level desc") as Select1Provider; var nsselsb = new StringBuilder(); if (AdoProvider.IsFromSlave(select._select) == false) nsselsb.Append(" "); //读写分离规则,如果强制读主库,则在前面加个空格 @@ -447,7 +497,7 @@ public static partial class FreeSqlGlobalExtensions case DataType.OdbcOracle: case DataType.Dameng: //递归 WITH 子句必须具有列别名列表 case DataType.OdbcDameng: - nsselsb.Append($"(as_cte_tree_depth, {sql2Field.Replace("wct2.", "")})"); + nsselsb.Append($"(cte_level, {(pathSelector == null ? "" : "cte_path, ")}{sql2Field.Replace("wct2.", "")})"); break; } nsselsb.Append(@" diff --git a/FreeSql/FreeSql.xml b/FreeSql/FreeSql.xml index 8738cbb9..1fa49bdd 100644 --- a/FreeSql/FreeSql.xml +++ b/FreeSql/FreeSql.xml @@ -3653,14 +3653,18 @@ - + - 使用递归 CTE 查询树型的所有子数据。 - 通过测试的数据库:MySql8.0、SqlServer、PostgreSQL、Oracle、Sqlite、达梦、人大金仓 + 使用递归 CTE 查询树型的所有子记录,或者所有父记录。 + 通过测试的数据库:MySql8.0、SqlServer、PostgreSQL、Oracle、Sqlite、达梦、人大金仓 + 返回隐藏字段:.ToList(a => new { item = a, level = "a.cte_level", path = "a.cte_path" }) - 深度 + false(默认):由父级向子级的递归查询true:由子级向父级的递归查询 + 路径内容选择 + 连接路径内容 + 递归层级 diff --git a/Providers/FreeSql.Provider.Odbc/KingbaseES/OdbcKingbaseESExpression.cs b/Providers/FreeSql.Provider.Odbc/KingbaseES/OdbcKingbaseESExpression.cs index f3a2155d..d92d1bb3 100644 --- a/Providers/FreeSql.Provider.Odbc/KingbaseES/OdbcKingbaseESExpression.cs +++ b/Providers/FreeSql.Provider.Odbc/KingbaseES/OdbcKingbaseESExpression.cs @@ -37,7 +37,7 @@ namespace FreeSql.Odbc.KingbaseES case "System.Int64": return $"({getExp(operandExp)})::int8"; case "System.SByte": return $"({getExp(operandExp)})::int2"; case "System.Single": return $"({getExp(operandExp)})::float4"; - case "System.String": return $"({getExp(operandExp)})::varchar"; + case "System.String": return $"({getExp(operandExp)})::text"; case "System.UInt16": return $"({getExp(operandExp)})::int2"; case "System.UInt32": return $"({getExp(operandExp)})::int4"; case "System.UInt64": return $"({getExp(operandExp)})::int8"; @@ -87,7 +87,7 @@ namespace FreeSql.Odbc.KingbaseES if (callExp.Method.DeclaringType.IsNumberType()) return "random()"; return null; case "ToString": - if (callExp.Object != null) return callExp.Arguments.Count == 0 ? $"({getExp(callExp.Object)})::varchar" : null; + if (callExp.Object != null) return callExp.Arguments.Count == 0 ? $"({getExp(callExp.Object)})::text" : null; return null; } @@ -332,10 +332,10 @@ namespace FreeSql.Odbc.KingbaseES if (exp.Arguments[1].Type == typeof(bool) || exp.Arguments[1].Type == typeof(StringComparison) && getExp(exp.Arguments[0]).Contains("IgnoreCase")) likeOpt = "ILIKE"; } - if (exp.Method.Name == "StartsWith") return $"({left}) {likeOpt} {(args0Value.EndsWith("'") ? args0Value.Insert(args0Value.Length - 1, "%") : $"(({args0Value})::varchar || '%')")}"; - if (exp.Method.Name == "EndsWith") return $"({left}) {likeOpt} {(args0Value.StartsWith("'") ? args0Value.Insert(1, "%") : $"('%' || ({args0Value})::varchar)")}"; + if (exp.Method.Name == "StartsWith") return $"({left}) {likeOpt} {(args0Value.EndsWith("'") ? args0Value.Insert(args0Value.Length - 1, "%") : $"(({args0Value})::text || '%')")}"; + if (exp.Method.Name == "EndsWith") return $"({left}) {likeOpt} {(args0Value.StartsWith("'") ? args0Value.Insert(1, "%") : $"('%' || ({args0Value})::text)")}"; if (args0Value.StartsWith("'") && args0Value.EndsWith("'")) return $"({left}) {likeOpt} {args0Value.Insert(1, "%").Insert(args0Value.Length, "%")}"; - return $"({left}) {likeOpt} ('%' || ({args0Value})::varchar || '%')"; + return $"({left}) {likeOpt} ('%' || ({args0Value})::text || '%')"; case "ToLower": return $"lower({left})"; case "ToUpper": return $"upper({left})"; case "Substring": @@ -378,7 +378,7 @@ namespace FreeSql.Odbc.KingbaseES return left; case "Replace": return $"replace({left}, {getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})"; case "CompareTo": return $"case when {left} = {getExp(exp.Arguments[0])} then 0 when {left} > {getExp(exp.Arguments[0])} then 1 else -1 end"; - case "Equals": return $"({left} = ({getExp(exp.Arguments[0])})::varchar)"; + case "Equals": return $"({left} = ({getExp(exp.Arguments[0])})::text)"; } } return null; @@ -573,7 +573,7 @@ namespace FreeSql.Odbc.KingbaseES case "ToInt64": return $"({getExp(exp.Arguments[0])})::int8"; case "ToSByte": return $"({getExp(exp.Arguments[0])})::int2"; case "ToSingle": return $"({getExp(exp.Arguments[0])})::float4"; - case "ToString": return $"({getExp(exp.Arguments[0])})::varchar"; + case "ToString": return $"({getExp(exp.Arguments[0])})::text"; case "ToUInt16": return $"({getExp(exp.Arguments[0])})::int2"; case "ToUInt32": return $"({getExp(exp.Arguments[0])})::int4"; case "ToUInt64": return $"({getExp(exp.Arguments[0])})::int8"; diff --git a/Providers/FreeSql.Provider.Odbc/PostgreSQL/OdbcPostgreSQLExpression.cs b/Providers/FreeSql.Provider.Odbc/PostgreSQL/OdbcPostgreSQLExpression.cs index e31e3aee..c3265c27 100644 --- a/Providers/FreeSql.Provider.Odbc/PostgreSQL/OdbcPostgreSQLExpression.cs +++ b/Providers/FreeSql.Provider.Odbc/PostgreSQL/OdbcPostgreSQLExpression.cs @@ -37,7 +37,7 @@ namespace FreeSql.Odbc.PostgreSQL case "System.Int64": return $"({getExp(operandExp)})::int8"; case "System.SByte": return $"({getExp(operandExp)})::int2"; case "System.Single": return $"({getExp(operandExp)})::float4"; - case "System.String": return $"({getExp(operandExp)})::varchar"; + case "System.String": return $"({getExp(operandExp)})::text"; case "System.UInt16": return $"({getExp(operandExp)})::int2"; case "System.UInt32": return $"({getExp(operandExp)})::int4"; case "System.UInt64": return $"({getExp(operandExp)})::int8"; @@ -87,7 +87,7 @@ namespace FreeSql.Odbc.PostgreSQL if (callExp.Method.DeclaringType.IsNumberType()) return "random()"; return null; case "ToString": - if (callExp.Object != null) return callExp.Arguments.Count == 0 ? $"({getExp(callExp.Object)})::varchar" : null; + if (callExp.Object != null) return callExp.Arguments.Count == 0 ? $"({getExp(callExp.Object)})::text" : null; return null; } @@ -354,10 +354,10 @@ namespace FreeSql.Odbc.PostgreSQL if (exp.Arguments[1].Type == typeof(bool) || exp.Arguments[1].Type == typeof(StringComparison) && getExp(exp.Arguments[0]).Contains("IgnoreCase")) likeOpt = "ILIKE"; } - if (exp.Method.Name == "StartsWith") return $"({left}) {likeOpt} {(args0Value.EndsWith("'") ? args0Value.Insert(args0Value.Length - 1, "%") : $"(({args0Value})::varchar || '%')")}"; - if (exp.Method.Name == "EndsWith") return $"({left}) {likeOpt} {(args0Value.StartsWith("'") ? args0Value.Insert(1, "%") : $"('%' || ({args0Value})::varchar)")}"; + if (exp.Method.Name == "StartsWith") return $"({left}) {likeOpt} {(args0Value.EndsWith("'") ? args0Value.Insert(args0Value.Length - 1, "%") : $"(({args0Value})::text || '%')")}"; + if (exp.Method.Name == "EndsWith") return $"({left}) {likeOpt} {(args0Value.StartsWith("'") ? args0Value.Insert(1, "%") : $"('%' || ({args0Value})::text)")}"; if (args0Value.StartsWith("'") && args0Value.EndsWith("'")) return $"({left}) {likeOpt} {args0Value.Insert(1, "%").Insert(args0Value.Length, "%")}"; - return $"({left}) {likeOpt} ('%' || ({args0Value})::varchar || '%')"; + return $"({left}) {likeOpt} ('%' || ({args0Value})::text || '%')"; case "ToLower": return $"lower({left})"; case "ToUpper": return $"upper({left})"; case "Substring": @@ -405,7 +405,7 @@ namespace FreeSql.Odbc.PostgreSQL return left; case "Replace": return $"replace({left}, {getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})"; case "CompareTo": return $"case when {left} = {getExp(exp.Arguments[0])} then 0 when {left} > {getExp(exp.Arguments[0])} then 1 else -1 end"; - case "Equals": return $"({left} = ({getExp(exp.Arguments[0])})::varchar)"; + case "Equals": return $"({left} = ({getExp(exp.Arguments[0])})::text)"; } } return null; @@ -598,7 +598,7 @@ namespace FreeSql.Odbc.PostgreSQL case "ToInt64": return $"({getExp(exp.Arguments[0])})::int8"; case "ToSByte": return $"({getExp(exp.Arguments[0])})::int2"; case "ToSingle": return $"({getExp(exp.Arguments[0])})::float4"; - case "ToString": return $"({getExp(exp.Arguments[0])})::varchar"; + case "ToString": return $"({getExp(exp.Arguments[0])})::text"; case "ToUInt16": return $"({getExp(exp.Arguments[0])})::int2"; case "ToUInt32": return $"({getExp(exp.Arguments[0])})::int4"; case "ToUInt64": return $"({getExp(exp.Arguments[0])})::int8"; diff --git a/Providers/FreeSql.Provider.Odbc/SqlServer/OdbcSqlServerUtils.cs b/Providers/FreeSql.Provider.Odbc/SqlServer/OdbcSqlServerUtils.cs index cfe4abdd..214a7d1b 100644 --- a/Providers/FreeSql.Provider.Odbc/SqlServer/OdbcSqlServerUtils.cs +++ b/Providers/FreeSql.Provider.Odbc/SqlServer/OdbcSqlServerUtils.cs @@ -73,7 +73,8 @@ namespace FreeSql.Odbc.SqlServer { if (types[a] == typeof(string)) news[a] = objs[a]; else if (types[a].NullableTypeOrThis() == typeof(Guid)) news[a] = $"cast({objs[a]} as char(36))"; - else news[a] = $"cast({objs[a]} as nvarchar)"; + else if (types[a].IsNumberType()) news[a] = $"cast({objs[a]} as varchar)"; + else news[a] = $"cast({objs[a]} as nvarchar(max))"; } return string.Join(" + ", news); } diff --git a/Providers/FreeSql.Provider.PostgreSQL/PostgreSQLExpression.cs b/Providers/FreeSql.Provider.PostgreSQL/PostgreSQLExpression.cs index 42d1a495..0e2c5fe1 100644 --- a/Providers/FreeSql.Provider.PostgreSQL/PostgreSQLExpression.cs +++ b/Providers/FreeSql.Provider.PostgreSQL/PostgreSQLExpression.cs @@ -38,7 +38,7 @@ namespace FreeSql.PostgreSQL case "System.Int64": return $"({getExp(operandExp)})::int8"; case "System.SByte": return $"({getExp(operandExp)})::int2"; case "System.Single": return $"({getExp(operandExp)})::float4"; - case "System.String": return $"({getExp(operandExp)})::varchar"; + case "System.String": return $"({getExp(operandExp)})::text"; case "System.UInt16": return $"({getExp(operandExp)})::int2"; case "System.UInt32": return $"({getExp(operandExp)})::int4"; case "System.UInt64": return $"({getExp(operandExp)})::int8"; @@ -88,7 +88,7 @@ namespace FreeSql.PostgreSQL if (callExp.Method.DeclaringType.IsNumberType()) return "random()"; return null; case "ToString": - if (callExp.Object != null) return callExp.Arguments.Count == 0 ? $"({getExp(callExp.Object)})::varchar" : null; + if (callExp.Object != null) return callExp.Arguments.Count == 0 ? $"({getExp(callExp.Object)})::text" : null; return null; } @@ -120,7 +120,7 @@ namespace FreeSql.PostgreSQL case "Contains": var json = getExp(callExp.Arguments[argIndex]); if (objType == typeof(JArray)) - return $"(coalesce({left},'[]') ? ({json})::varchar)"; + return $"(coalesce({left},'[]') ? ({json})::text)"; if (json.StartsWith("'") && json.EndsWith("'")) return $"(coalesce({left},'{{}}') @> {_common.FormatSql("{0}", JToken.Parse(json.Trim('\'')))})"; return $"(coalesce({left},'{{}}') @> ({json})::jsonb)"; @@ -385,10 +385,10 @@ namespace FreeSql.PostgreSQL if (exp.Arguments[1].Type == typeof(bool) || exp.Arguments[1].Type == typeof(StringComparison) && getExp(exp.Arguments[0]).Contains("IgnoreCase")) likeOpt = "ILIKE"; } - if (exp.Method.Name == "StartsWith") return $"({left}) {likeOpt} {(args0Value.EndsWith("'") ? args0Value.Insert(args0Value.Length - 1, "%") : $"(({args0Value})::varchar || '%')")}"; - if (exp.Method.Name == "EndsWith") return $"({left}) {likeOpt} {(args0Value.StartsWith("'") ? args0Value.Insert(1, "%") : $"('%' || ({args0Value})::varchar)")}"; + if (exp.Method.Name == "StartsWith") return $"({left}) {likeOpt} {(args0Value.EndsWith("'") ? args0Value.Insert(args0Value.Length - 1, "%") : $"(({args0Value})::text || '%')")}"; + if (exp.Method.Name == "EndsWith") return $"({left}) {likeOpt} {(args0Value.StartsWith("'") ? args0Value.Insert(1, "%") : $"('%' || ({args0Value})::text)")}"; if (args0Value.StartsWith("'") && args0Value.EndsWith("'")) return $"({left}) {likeOpt} {args0Value.Insert(1, "%").Insert(args0Value.Length, "%")}"; - return $"({left}) {likeOpt} ('%' || ({args0Value})::varchar || '%')"; + return $"({left}) {likeOpt} ('%' || ({args0Value})::text || '%')"; case "ToLower": return $"lower({left})"; case "ToUpper": return $"upper({left})"; case "Substring": @@ -436,7 +436,7 @@ namespace FreeSql.PostgreSQL return left; case "Replace": return $"replace({left}, {getExp(exp.Arguments[0])}, {getExp(exp.Arguments[1])})"; case "CompareTo": return $"case when {left} = {getExp(exp.Arguments[0])} then 0 when {left} > {getExp(exp.Arguments[0])} then 1 else -1 end"; - case "Equals": return $"({left} = ({getExp(exp.Arguments[0])})::varchar)"; + case "Equals": return $"({left} = ({getExp(exp.Arguments[0])})::text)"; } } return null; @@ -629,7 +629,7 @@ namespace FreeSql.PostgreSQL case "ToInt64": return $"({getExp(exp.Arguments[0])})::int8"; case "ToSByte": return $"({getExp(exp.Arguments[0])})::int2"; case "ToSingle": return $"({getExp(exp.Arguments[0])})::float4"; - case "ToString": return $"({getExp(exp.Arguments[0])})::varchar"; + case "ToString": return $"({getExp(exp.Arguments[0])})::text"; case "ToUInt16": return $"({getExp(exp.Arguments[0])})::int2"; case "ToUInt32": return $"({getExp(exp.Arguments[0])})::int4"; case "ToUInt64": return $"({getExp(exp.Arguments[0])})::int8"; diff --git a/Providers/FreeSql.Provider.SqlServer/SqlServerUtils.cs b/Providers/FreeSql.Provider.SqlServer/SqlServerUtils.cs index 8814ae3e..f05d3a8c 100644 --- a/Providers/FreeSql.Provider.SqlServer/SqlServerUtils.cs +++ b/Providers/FreeSql.Provider.SqlServer/SqlServerUtils.cs @@ -85,7 +85,8 @@ namespace FreeSql.SqlServer { if (types[a] == typeof(string)) news[a] = objs[a]; else if (types[a].NullableTypeOrThis() == typeof(Guid)) news[a] = $"cast({objs[a]} as char(36))"; - else news[a] = $"cast({objs[a]} as nvarchar)"; + else if (types[a].IsNumberType()) news[a] = $"cast({objs[a]} as varchar)"; + else news[a] = $"cast({objs[a]} as nvarchar(max))"; } return string.Join(" + ", news); }