- 增加 ISelect.AsCteTree() 递归查询树表所有子记录;

This commit is contained in:
28810
2020-06-27 04:21:00 +08:00
parent dc8f575b18
commit 4cde2a3280
20 changed files with 395 additions and 33 deletions

View File

@ -385,4 +385,80 @@ public static partial class FreeSqlGlobalExtensions
}
#endif
#endregion
#region WhereTree(..)
/// <summary>
/// 使用递归 CTE 查询树型的所有子数据。<para></para>
/// 通过测试的数据库MySql8.0、SqlServer、PostgreSQL、Oracle、Sqlite、达梦、人大金仓
/// </summary>
/// <typeparam name="T1"></typeparam>
/// <param name="that"></param>
/// <param name="depth">深度</param>
/// <returns></returns>
public static ISelect<T1> AsCteTree<T1>(this ISelect<T1> that, int depth = -1) where T1 : class
{
var select = that as Select1Provider<T1>;
var tb = select._tables[0].Table;
var navs = tb.Properties.Select(a => tb.GetTableRef(a.Key, false))
.Where(a => a != null &&
a.RefType == FreeSql.Internal.Model.TableRefType.OneToMany &&
a.RefEntityType == tb.Type).ToArray();
if (navs.Length != 1) throw new ArgumentException($"{tb.Type.FullName} 不是父子关系,无法使用该功能");
var tbref = navs[0];
var cteName = "as_cte_tree";
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();
select._where.Clear();
select.As("wct2");
var sql2Field = select.GetAllFieldExpressionTreeLevel2().Field;
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();
var newSelect = select._orm.Select<T1>()
.AsType(tb.Type)
.AsTable((type, old) => type == tb.Type ? cteName : old)
.WhereIf(depth > 0, $"a.as_cte_tree_depth < {depth + 1}") as Select1Provider<T1>;
var nsselsb = new StringBuilder();
if (AdoProvider.IsFromSlave(select._select) == false) nsselsb.Append(" "); //读写分离规则,如果强制读主库,则在前面加个空格
nsselsb.Append("WITH ");
switch (select._orm.Ado.DataType)
{
case DataType.PostgreSQL:
case DataType.OdbcPostgreSQL:
case DataType.OdbcKingbaseES:
case DataType.ShenTong: //神通测试未通过
case DataType.MySql:
case DataType.OdbcMySql:
nsselsb.Append("RECURSIVE ");
break;
}
nsselsb.Append(select._commonUtils.QuoteSqlName(cteName));
switch (select._orm.Ado.DataType)
{
case DataType.Oracle: //[Err] ORA-32039: recursive WITH clause must have column alias list
case DataType.OdbcOracle:
case DataType.Dameng: //递归 WITH 子句必须具有列别名列表
case DataType.OdbcDameng:
nsselsb.Append($"(as_cte_tree_depth, {sql2Field.Replace("wct2.", "")})");
break;
}
nsselsb.Append(@"
as
(
").Append(sql1).Append("\r\n\r\nunion all\r\n\r\n").Append(sql2).Append(@"
)
SELECT ");
newSelect._select = nsselsb.ToString();
nsselsb.Clear();
return newSelect;
}
#endregion
}

View File

@ -3653,6 +3653,16 @@
<param name="that"></param>
<returns></returns>
</member>
<member name="M:FreeSqlGlobalExtensions.AsCteTree``1(FreeSql.ISelect{``0},System.Int32)">
<summary>
使用递归 CTE 查询树型的所有子数据。<para></para>
通过测试的数据库MySql8.0、SqlServer、PostgreSQL、Oracle、Sqlite、达梦、人大金仓
</summary>
<typeparam name="T1"></typeparam>
<param name="that"></param>
<param name="depth">深度</param>
<returns></returns>
</member>
<member name="M:System.Linq.Expressions.LambadaExpressionExtensions.And``1(System.Linq.Expressions.Expression{System.Func{``0,System.Boolean}},System.Linq.Expressions.Expression{System.Func{``0,System.Boolean}})">
<summary>
使用 and 拼接两个 lambda 表达式

View File

@ -533,7 +533,7 @@ namespace FreeSql.Internal.CommonProvider
if (transaction == null && connection == null)
{
//读写分离规则
if (this.SlavePools.Any() && cmdText.StartsWith("SELECT ", StringComparison.CurrentCultureIgnoreCase))
if (this.SlavePools.Any() && IsFromSlave(cmdText))
{
var availables = slaveUnavailables == 0 ?
//查从库

View File

@ -458,7 +458,7 @@ namespace FreeSql.Internal.CommonProvider
if (transaction == null && connection == null)
{
//读写分离规则
if (this.SlavePools.Any() && cmdText.StartsWith("SELECT ", StringComparison.CurrentCultureIgnoreCase))
if (this.SlavePools.Any() && IsFromSlave(cmdText))
{
var availables = slaveUnavailables == 0 ?
//查从库

View File

@ -45,5 +45,11 @@ namespace FreeSql.Internal.CommonProvider
return sb.Length == 0 ? "(NULL)" : sb.Remove(0, 1).Insert(0, "(").Append(")").ToString();
}
public static bool IsFromSlave(string cmdText)
{
return cmdText.StartsWith("SELECT ", StringComparison.CurrentCultureIgnoreCase) ||
cmdText.StartsWith("WITH ", StringComparison.CurrentCultureIgnoreCase);
}
}
}

View File

@ -596,7 +596,7 @@ namespace FreeSql.Internal.CommonProvider
public int FieldCount { get; set; }
public Func<IFreeSql, DbDataReader, T1> Read { get; set; }
}
protected GetAllFieldExpressionTreeInfo GetAllFieldExpressionTreeLevelAll()
public GetAllFieldExpressionTreeInfo GetAllFieldExpressionTreeLevelAll()
{
return _dicGetAllFieldExpressionTree.GetOrAdd($"*{string.Join("+", _tables.Select(a => $"{_orm.Ado.DataType}-{a.Table.DbName}-{a.Alias}-{a.Type}"))}", s =>
{
@ -735,7 +735,7 @@ namespace FreeSql.Internal.CommonProvider
};
});
}
protected GetAllFieldExpressionTreeInfo GetAllFieldExpressionTreeLevel2()
public GetAllFieldExpressionTreeInfo GetAllFieldExpressionTreeLevel2()
{
return _dicGetAllFieldExpressionTree.GetOrAdd(string.Join("+", _tables.Select(a => $"{_orm.Ado.DataType}-{a.Table.DbName}-{a.Alias}-{a.Type}")), s =>
{