From 0968f581561a96cb86bc0be3c46e5e440406b1f2 Mon Sep 17 00:00:00 2001 From: MarvelTiter_yaoqinglin Date: Wed, 11 Feb 2026 16:33:53 +0800 Subject: [PATCH] =?UTF-8?q?=E5=86=85=E5=AD=98=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LightORM.slnx | 1 + ...10\346\234\254\346\227\245\345\277\227.md" | 27 + src/LightORM/AssemblyInfo.cs | 1 + src/LightORM/Builder/DeleteBuilder.cs | 10 +- src/LightORM/Builder/InsertBuilder.cs | 4 +- src/LightORM/Builder/SelectBuilder.cs | 162 ++++- src/LightORM/Builder/SqlBuilder.cs | 9 +- src/LightORM/Builder/UpdateBuilder.cs | 8 +- .../ExpressionSql/ExpressionCoreSql.Union.cs | 2 +- .../ExpressionSql/ExpressionSqlBuilder.cs | 16 +- .../Extension/CustomDatabaseExtensions.cs | 23 +- .../Extension/ExpressionContextExtension.cs | 8 +- .../Extension/GroupSelectExtensions.cs | 4 +- .../Extension/IncludeContextExtensions.cs | 2 +- src/LightORM/Extension/SelectExtensions.cs | 4 +- .../Extension/SelectHandleExtensions.cs | 2 +- .../Interfaces/IExpressionContextSetup.cs | 1 + src/LightORM/Interfaces/IResetable.cs | 12 + src/LightORM/Models/ExpressionInfo.cs | 2 +- .../Models/ExpressionResolvedResult.cs | 28 +- .../Performances/ExpressionResolverPool.cs | 67 ++ .../Performances/ExpressionVisitorPool.cs | 32 + .../Performances/SelectBuilderPool.cs | 90 +++ src/LightORM/Performances/SlimList.cs | 72 +++ src/LightORM/Providers/GroupSelectProvider.cs | 3 +- .../Providers/Select/SelectProvider1.cs | 5 +- .../Providers/Select/SelectProvider2.cs | 5 +- .../Repository/LightOrmQueryProvider.cs | 3 +- src/LightORM/Utils/ExpressionResolver.cs | 274 ++++---- src/LightORM/Utils/ExpressionValueExtract.cs | 597 ++++++++++++++++- src/LightORM/Utils/ResolveHelper.cs | 88 +++ .../Utils/Vistors/ExpressionHasher.cs | 598 ++++++++++++++++++ .../Utils/{ => Vistors}/FlatGrouping.cs | 70 +- .../Utils/{ => Vistors}/FlatTypeSet.cs | 80 ++- .../{ => Vistors}/LinqExpressionFlattener.cs | 62 +- src/LightORM/Versions.props | 2 +- src/LightORM/usings.cs | 2 +- .../SelectProvidersGenerator.cs | 2 +- test/BenchmarkTest/BenchmarkTest.csproj | 20 + test/BenchmarkTest/Models/User.cs | 48 ++ test/BenchmarkTest/Program.cs | 7 + test/BenchmarkTest/SqlBuild.cs | 190 ++++++ test/LightORMTest/DbMethodTest.cs | 8 +- .../ResolverTest/ValueResolveTest.cs | 55 ++ test/LightORMTest/ResultTest/Select.cs | 2 +- test/LightORMTest/SqlGenerate/SelectSql.cs | 29 +- 46 files changed, 2408 insertions(+), 329 deletions(-) create mode 100644 src/LightORM/Interfaces/IResetable.cs create mode 100644 src/LightORM/Performances/ExpressionResolverPool.cs create mode 100644 src/LightORM/Performances/ExpressionVisitorPool.cs create mode 100644 src/LightORM/Performances/SelectBuilderPool.cs create mode 100644 src/LightORM/Performances/SlimList.cs create mode 100644 src/LightORM/Utils/ResolveHelper.cs create mode 100644 src/LightORM/Utils/Vistors/ExpressionHasher.cs rename src/LightORM/Utils/{ => Vistors}/FlatGrouping.cs (68%) rename src/LightORM/Utils/{ => Vistors}/FlatTypeSet.cs (54%) rename src/LightORM/Utils/{ => Vistors}/LinqExpressionFlattener.cs (52%) create mode 100644 test/BenchmarkTest/BenchmarkTest.csproj create mode 100644 test/BenchmarkTest/Models/User.cs create mode 100644 test/BenchmarkTest/Program.cs create mode 100644 test/BenchmarkTest/SqlBuild.cs diff --git a/LightORM.slnx b/LightORM.slnx index 0b2286fe..a51efafd 100644 --- a/LightORM.slnx +++ b/LightORM.slnx @@ -24,6 +24,7 @@ + diff --git "a/doc/\347\211\210\346\234\254\346\227\245\345\277\227.md" "b/doc/\347\211\210\346\234\254\346\227\245\345\277\227.md" index b78fa0e2..11e8b453 100644 --- "a/doc/\347\211\210\346\234\254\346\227\245\345\277\227.md" +++ "b/doc/\347\211\210\346\234\254\346\227\245\345\277\227.md" @@ -1,5 +1,32 @@ # 版本功能更新记录 +## v2026.02.11.1 +- ⚡️新增表达式解析缓存功能,默认启用,可以通过`IExpressionContextSetup.SetEnableExpressionCache`进行全局配置 +- ⚡️内部高频使用的对象新增对象池 +- ⚡️使用日期作为版本号 + + +#### 新版本(开启表达式解析缓存) +| Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated | +|----------------------- |----------:|----------:|----------:|--------:|-------:|----------:| +| SelectCTE | 89.498 us | 1.7850 us | 1.6697 us | 10.7422 | 0.4883 | 89.2 KB | +| SelectCTEOptimized | 84.509 us | 1.4743 us | 1.3791 us | 10.2539 | 0.7324 | 85.68 KB | +| SelectWithArrayAccess | 7.799 us | 0.1476 us | 0.1812 us | 0.9460 | 0.1831 | 7.75 KB | +| SelectWithContainArray | 8.465 us | 0.1680 us | 0.2665 us | 0.9155 | 0.1831 | 7.65 KB | +#### 新版本(关闭表达式解析缓存) +| Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated | +|----------------------- |-----------:|----------:|----------:|--------:|-------:|----------:| +| SelectCTE | 135.588 us | 2.6832 us | 4.2558 us | 12.2070 | 0.9766 | 103.57 KB | +| SelectCTEOptimized | 129.420 us | 2.5883 us | 3.8741 us | 12.2070 | 0.9766 | 100.88 KB | +| SelectWithArrayAccess | 5.580 us | 0.1010 us | 0.1081 us | 0.9155 | 0.0153 | 7.55 KB | +| SelectWithContainArray | 6.253 us | 0.1131 us | 0.1003 us | 0.8850 | - | 7.27 KB | +#### v3.2.1 +| Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated | +|----------------------- |-----------:|----------:|----------:|--------:|-------:|----------:| +| SelectCTE | 134.840 us | 2.6678 us | 3.2762 us | 13.6719 | 0.9766 | 115.39 KB | +| SelectCTEOptimized | 126.395 us | 2.4247 us | 2.4900 us | 13.1836 | 0.9766 | 111.5 KB | +| SelectWithArrayAccess | 5.606 us | 0.1063 us | 0.1181 us | 1.0071 | - | 8.3 KB | +| SelectWithContainArray | 6.316 us | 0.1251 us | 0.1489 us | 0.9766 | - | 8.1 KB | ## v3.2.1 - 🛠`DbParameterReader`优化 - 🛠解析表达式时,读取参数值的方式优化,表达式树编译缓存委托后直接调用 diff --git a/src/LightORM/AssemblyInfo.cs b/src/LightORM/AssemblyInfo.cs index 17ec5182..2bee5567 100644 --- a/src/LightORM/AssemblyInfo.cs +++ b/src/LightORM/AssemblyInfo.cs @@ -26,5 +26,6 @@ internal static class DebugControl { public static readonly bool ShowExpressionResolveDebugInfo = false; public static readonly bool ShowSqlExecutorDebugInfo = true; + public static readonly bool ShowExpressionHashCodeDebugInfo = true; } } \ No newline at end of file diff --git a/src/LightORM/Builder/DeleteBuilder.cs b/src/LightORM/Builder/DeleteBuilder.cs index 2bd4a2df..76530e42 100644 --- a/src/LightORM/Builder/DeleteBuilder.cs +++ b/src/LightORM/Builder/DeleteBuilder.cs @@ -15,7 +15,7 @@ internal record DeleteBuilder : SqlBuilder public List? BatchInfos { get; set; } protected override void HandleResult(ICustomDatabase database, ExpressionInfo expInfo, ExpressionResolvedResult result) { - if (expInfo.ResolveOptions?.SqlType == SqlPartial.Where) + if (expInfo.ResolveOptions.SqlType == SqlPartial.Where) { if (result.UseNavigate) { @@ -26,11 +26,9 @@ protected override void HandleResult(ICustomDatabase database, ExpressionInfo ex { continue; } - var navSqlBuilder = new SelectBuilder - { - IsSubQuery = true, - Level = 1 - }; + var navSqlBuilder = SelectBuilder.GetSelectBuilder(); + navSqlBuilder.IsSubQuery = true; + navSqlBuilder.Level = 1; navSqlBuilder.SelectedTables.Add(MainTable); var navInfo = navColumn.NavigateInfo!; var mainCol = MainTable.GetColumnInfo(navInfo.MainName!); diff --git a/src/LightORM/Builder/InsertBuilder.cs b/src/LightORM/Builder/InsertBuilder.cs index c28b48dc..18594bf8 100644 --- a/src/LightORM/Builder/InsertBuilder.cs +++ b/src/LightORM/Builder/InsertBuilder.cs @@ -14,11 +14,11 @@ internal record InsertBuilder : SqlBuilder public bool IsReturnIdentity { get; set; } protected override void HandleResult(ICustomDatabase database, ExpressionInfo expInfo, ExpressionResolvedResult result) { - if (expInfo.ResolveOptions?.SqlType == SqlPartial.Insert) + if (expInfo.ResolveOptions.SqlType == SqlPartial.Insert) { Members.AddRange(result.Members!); } - else if (expInfo.ResolveOptions?.SqlType == SqlPartial.Ignore) + else if (expInfo.ResolveOptions.SqlType == SqlPartial.Ignore) { IgnoreMembers.AddRange(result.Members!); } diff --git a/src/LightORM/Builder/SelectBuilder.cs b/src/LightORM/Builder/SelectBuilder.cs index 1ea3a4d7..3f011777 100644 --- a/src/LightORM/Builder/SelectBuilder.cs +++ b/src/LightORM/Builder/SelectBuilder.cs @@ -1,4 +1,5 @@ using LightORM.Extension; +using LightORM.Performances; using System.Text; namespace LightORM.Builder; @@ -19,10 +20,9 @@ internal record SelectBuilder : SqlBuilder, ISelectSqlBuilder { public SelectBuilder() { - //DbType = dbType; IncludeContext = new IncludeContext(); - //Indent = new Lazy(() => new string(' ', 4 * Level)); } + public static SelectBuilder GetSelectBuilder() => new();//SelectBuilderPool.Rent(); public string Id { get; } = $"{Guid.NewGuid():N}"; public int PageIndex { get; set; } public int PageSize { get; set; } @@ -46,7 +46,7 @@ public SelectBuilder() public List Joins { get; set; } = []; public List Having { get; set; } = []; public List Includes { get; set; } = []; - public IncludeContext IncludeContext { get; set; } = default!; + public IncludeContext IncludeContext { get; set; } public List GroupBy { get; set; } = []; public List OrderBy { get; set; } = []; @@ -87,7 +87,7 @@ protected override void BeforeResolveExpressions(ResolveContext context) } protected override void HandleResult(ICustomDatabase database, ExpressionInfo expInfo, ExpressionResolvedResult result) { - if (expInfo.ResolveOptions?.SqlType == SqlPartial.Where) + if (expInfo.ResolveOptions.SqlType == SqlPartial.Where) { if (result.UseNavigate) { @@ -116,7 +116,7 @@ protected override void HandleResult(ICustomDatabase database, ExpressionInfo ex Where.Add(result.SqlString!); } } - else if (expInfo.ResolveOptions?.SqlType == SqlPartial.Join) + else if (expInfo.ResolveOptions.SqlType == SqlPartial.Join) { var joinInfo = Joins.FirstOrDefault(j => j.ExpressionId == expInfo.Id); if (joinInfo != null) @@ -124,7 +124,7 @@ protected override void HandleResult(ICustomDatabase database, ExpressionInfo ex joinInfo.Where = result.SqlString!; } } - else if (expInfo.ResolveOptions?.SqlType == SqlPartial.Select) + else if (expInfo.ResolveOptions.SqlType == SqlPartial.Select) { if (result.UseNavigate) { @@ -146,16 +146,16 @@ protected override void HandleResult(ICustomDatabase database, ExpressionInfo ex SelectValue = result.SqlString!; } } - else if (expInfo.ResolveOptions?.SqlType == SqlPartial.GroupBy) + else if (expInfo.ResolveOptions.SqlType == SqlPartial.GroupBy) { GroupBy.Add(result.SqlString!); } - else if (expInfo.ResolveOptions?.SqlType == SqlPartial.OrderBy) + else if (expInfo.ResolveOptions.SqlType == SqlPartial.OrderBy) { OrderBy.Add(result.SqlString!); AdditionalValue = expInfo.AdditionalParameter; } - else if (expInfo.ResolveOptions?.SqlType == SqlPartial.Having) + else if (expInfo.ResolveOptions.SqlType == SqlPartial.Having) { Having.Add(result.SqlString!); } @@ -251,17 +251,21 @@ private void BuildFromString(StringBuilder sql, ICustomDatabase database) public override string ToSqlString(ICustomDatabase database) { //SubQuery?.ResolveExpressions(); - StringBuilder sql = new(); + var estimatedSize = EstimateSqlLength(); + StringBuilder sql = new(estimatedSize); Build(sql, database, Level); HandleSqlParameters(sql, database); sql.Trim(); - return sql.ToString(); + var sqlString = sql.ToString(); + //SelectBuilderPool.Return(this); + return sqlString; } public void Build(StringBuilder sql, ICustomDatabase database, int currentLevel) { ResolveExpressions(database); + var ident = new string(' ', 4 * currentLevel); if (InsertInfo.HasValue) { @@ -417,4 +421,140 @@ public void Build(StringBuilder sql, ICustomDatabase database, int currentLevel) } } } + + int EstimateSqlLength(int currentLevel = 0) + { + int total = 0; + const int indentPerLevel = 4; + var indent = indentPerLevel * currentLevel; + + // SELECT + total += indent + 10 + (IsDistinct ? 9 : 0); + if (SelectValue != null) total += SelectValue.Length; + total += 2; // \n + + // FROM / SubQuery + if (SubQuery != null) + { + total += indent + 7; + total += SubQuery.EstimateSqlLength(currentLevel + 1); + total += indent + 3; + if (MainTable.Alias != null) total += MainTable.Alias.Length; + total += 2; + } + else + { + total += indent + 5; + for (int i = 0; i < SelectedTables.Count; i++) + { + if (i > 0) total += 2; // ", " + var t = SelectedTables[i]; + total += t.TableName.Length + 2; + if (t.Alias != null) total += 1 + t.Alias.Length; + } + total += 1; // \n + } + + // JOINs + for (int i = 0; i < Joins.Count; i++) + { + var join = Joins[i]; + total += indent + 10; // "INNER JOIN " + if (join.IsSubQuery && join.SubQuery != null) + { + total += join.SubQuery.EstimateSqlLength(currentLevel + 1); + } + else if (join.EntityInfo != null) + { + total += join.EntityInfo.TableName.Length + 2; + if (join.EntityInfo.Alias != null) + total += 1 + join.EntityInfo.Alias.Length; + } + total += 4; // " ON " + if (join.Where != null) total += join.Where.Length; + else total += 10; + total += 2; // \n + } + + // WHERE + if (Where.Count > 0) + { + total += indent + 7; + for (int i = 0; i < Where.Count; i++) + { + if (i > 0) total += 5; // " AND " + total += Where[i].Length; + } + total += 1; // \n + } + + // GROUP BY + if (GroupBy.Count > 0) + { + total += indent + 12; + if (IsRollup) total += 9; + for (int i = 0; i < GroupBy.Count; i++) + { + if (i > 0) total += 2; // ", " + total += GroupBy[i].Length; + } + if (IsRollup) total += 1; + total += 1; // \n + } + + // ORDER BY + if (OrderBy.Count > 0) + { + total += indent + 10; + for (int i = 0; i < OrderBy.Count; i++) + { + if (i > 0) total += 2; + total += OrderBy[i].Length; + } + // ⚠️ AdditionalValue: 避免 ToString() + // 如果你知道它通常是 string/int,可特殊处理: + if (AdditionalValue is string s) total += s.Length; + else if (AdditionalValue is not null) + { + // 保守估计:最多 10 个字符(如 "DESC") + total += 10; + } + total += 1; // \n + } + + // Paging (Take/Skip) + if (Take > 0) + { + total += 50; // 保守估计 LIMIT/OFFSET/FETCH + } + + // CTE (WITH ...) + if (TempViews.Count > 0) + { + total += 5; // "WITH " + foreach (var view in TempViews) + { + total += view.EstimateSqlLength(currentLevel + 1); + total += 2; // ")," + } + } + + // UNION + foreach (var union in Unions) + { + total += indent + 12; // "UNION ALL\n" + total += union.SqlBuilder.EstimateSqlLength(currentLevel); + } + + // Temp wrapper: " name AS (\n ... \n)" + if (IsTemp) + { + total += (TempName?.Length ?? 10) + 6; // " name AS (" + total += 2; // "\n...\n)" + } + + // 安全边际 + return Math.Max(64, (int)(total * 1.2) + 50); + } } + diff --git a/src/LightORM/Builder/SqlBuilder.cs b/src/LightORM/Builder/SqlBuilder.cs index f6f525dd..3461bc97 100644 --- a/src/LightORM/Builder/SqlBuilder.cs +++ b/src/LightORM/Builder/SqlBuilder.cs @@ -8,7 +8,6 @@ namespace LightORM.Builder; internal abstract record SqlBuilder : ISqlBuilder { public static string N { get; } = Environment.NewLine; - public ExpressionInfoProvider Expressions { get; } = new ExpressionInfoProvider(); public TableInfo MainTable => SelectedTables[0]; public List SelectedTables { get; set; } = []; @@ -97,15 +96,17 @@ protected void ResolveExpressions(ICustomDatabase database) foreach (var item in Expressions.ExpressionInfos.Values.Where(item => !item.Completed)) { //item.ResolveOptions!.DbType = DbType; - item.ResolveOptions!.ParameterPartialIndex = index; - var result = item.Expression.Resolve(item.ResolveOptions!, ResolveCtx); + //item.ResolveOptions = item.ResolveOptions with { ParameterPartialIndex = index }; + item.ResolveOptions.ParameterPartialIndex = index; + var result = item.Expression.Resolve(item.ResolveOptions, ResolveCtx); item.Completed = true; if (!string.IsNullOrEmpty(item.Template)) { result.SqlString = string.Format(item.Template, result.SqlString); } HandleResult(database, item, result); - DbParameterInfos.AddRange(result.DbParameters ?? []); + if (result.DbParameters != null) + DbParameterInfos.AddRange(result.DbParameters); index++; } } diff --git a/src/LightORM/Builder/UpdateBuilder.cs b/src/LightORM/Builder/UpdateBuilder.cs index 70fd9109..fb1ca0e7 100644 --- a/src/LightORM/Builder/UpdateBuilder.cs +++ b/src/LightORM/Builder/UpdateBuilder.cs @@ -21,12 +21,12 @@ internal record UpdateBuilder : SqlBuilder protected override void HandleResult(ICustomDatabase database, ExpressionInfo expInfo, ExpressionResolvedResult result) { - if (expInfo.ResolveOptions?.SqlType == SqlPartial.Where) + if (expInfo.ResolveOptions.SqlType == SqlPartial.Where) { Where.Add(result.SqlString!); - WhereMembers.AddRange(result?.Members?.Distinct() ?? []); + WhereMembers.AddRange(result.Members?.Distinct() ?? []); } - else if (expInfo.ResolveOptions?.SqlType == SqlPartial.Update) + else if (expInfo.ResolveOptions.SqlType == SqlPartial.Update) { if (expInfo.AdditionalParameter is null) { @@ -46,7 +46,7 @@ protected override void HandleResult(ICustomDatabase database, ExpressionInfo ex } } } - else if (expInfo.ResolveOptions?.SqlType == SqlPartial.Ignore) + else if (expInfo.ResolveOptions.SqlType == SqlPartial.Ignore) { IgnoreMembers.AddRange(result.Members!); //IgnoreMembers = new(result.Members!) diff --git a/src/LightORM/ExpressionSql/ExpressionCoreSql.Union.cs b/src/LightORM/ExpressionSql/ExpressionCoreSql.Union.cs index e5251e4f..ab424360 100644 --- a/src/LightORM/ExpressionSql/ExpressionCoreSql.Union.cs +++ b/src/LightORM/ExpressionSql/ExpressionCoreSql.Union.cs @@ -11,7 +11,7 @@ public IExpSelect FromQuery(IExpSelect select) public IExpSelect FromTemp(IExpTemp temp) { - var builder = new SelectBuilder(); + var builder = SelectBuilder.GetSelectBuilder(); builder.HandleTempsRecursion(temp.SqlBuilder); builder.SelectedTables.Add(temp.ResultTable); return new SelectProvider1(Ado, builder); diff --git a/src/LightORM/ExpressionSql/ExpressionSqlBuilder.cs b/src/LightORM/ExpressionSql/ExpressionSqlBuilder.cs index f4ac2b4c..0c4c1863 100644 --- a/src/LightORM/ExpressionSql/ExpressionSqlBuilder.cs +++ b/src/LightORM/ExpressionSql/ExpressionSqlBuilder.cs @@ -32,10 +32,12 @@ internal partial class ExpressionSqlOptions private readonly static ConcurrentDictionary databaseHandlers = []; private readonly static ConcurrentDictionary stateLessInterceptors = []; private static int poolSize = Environment.ProcessorCount * 4; + private static int objectPoolSize = Environment.ProcessorCount * 8; internal static TableGenerateOption StaticTableGenOption { get; set; } = new(); private static string? defaultDbKey; private static bool useParameterized = true; + private static bool enableExpressionCache = true; static ExpressionSqlOptions() { Instance = new(() => new ExpressionSqlOptions()); @@ -85,12 +87,18 @@ public static void SetUseParameterized(bool use) { useParameterized = use; } + public static void SetEnableExpressionCache(bool enable) + { + enableExpressionCache = enable; + } } internal partial class ExpressionSqlOptions { public static Lazy Instance { get; } public int PoolSize => poolSize; + public int InternalObjectPoolSize => objectPoolSize; + public bool EnableExpressionCache => enableExpressionCache; public string DefaultDbKey { get @@ -105,7 +113,7 @@ public string DefaultDbKey } public bool UseParameterized => useParameterized; public IServiceProvider? Services { get; set; } - private ICollection allInterceptors; + private readonly ICollection allInterceptors; public ICollection Interceptors => allInterceptors; public ConcurrentDictionary DatabaseProviders { get; } public ConcurrentDictionary CustomDatabases { get; } @@ -154,6 +162,12 @@ public IExpressionContextSetup SetConnectionPoolSize(int poolSize) return this; } + public IExpressionContextSetup SetEnableExpressionCache(bool enable) + { + ExpressionSqlOptions.SetEnableExpressionCache(enable); + return this; + } + public IExpressionContextSetup SetDatabase(string? key, DbBaseType dbBaseType, IDatabaseProvider provider) { ExpressionSqlOptions.SetDatabase(key, dbBaseType, provider); diff --git a/src/LightORM/Extension/CustomDatabaseExtensions.cs b/src/LightORM/Extension/CustomDatabaseExtensions.cs index 0642eb96..c08f1e0c 100644 --- a/src/LightORM/Extension/CustomDatabaseExtensions.cs +++ b/src/LightORM/Extension/CustomDatabaseExtensions.cs @@ -1,4 +1,5 @@ -using System.Text; +using LightORM.Performances; +using System.Text; namespace LightORM.Extension; @@ -44,15 +45,25 @@ public static StringBuilder AppendEmphasis(this StringBuilder sql, string name, public static StringBuilder AppendJoined(this StringBuilder sql, List values, string separator) { - bool first = true; - foreach (var item in values) + if (values.Count == 0) return sql; + sql.Append(values[0]); + for (int i = 1; i < values.Count; i++) { - if (!first) + sql.Append(separator); + sql.Append(values[i]); + } + return sql; + } + + public static StringBuilder AppendJoined(this StringBuilder sql, ref SlimList values, string separator) + { + for (int i = 0; i < values.Count; i++) + { + if (i > 0) { sql.Append(separator); } - first = false; - sql.Append(item); + sql.Append(values[i]); } return sql; } diff --git a/src/LightORM/Extension/ExpressionContextExtension.cs b/src/LightORM/Extension/ExpressionContextExtension.cs index 9b459276..a2b0cabd 100644 --- a/src/LightORM/Extension/ExpressionContextExtension.cs +++ b/src/LightORM/Extension/ExpressionContextExtension.cs @@ -141,7 +141,7 @@ private static void HandleFromTemp(SelectBuilder sqlbuilder, params IExpTemp[] t public static IExpSelect FromTemp(this IExpressionContext context , IExpTemp temp1, IExpTemp temp2) { - var builder = new SelectBuilder(); + var builder = SelectBuilder.GetSelectBuilder(); HandleFromTemp(builder, temp1, temp2); return new SelectProvider2(context.Ado, builder); } @@ -149,7 +149,7 @@ public static IExpSelect FromTemp(this IExpressi public static IExpSelect FromTemp(this IExpressionContext context , IExpTemp temp1, IExpTemp temp2, IExpTemp temp3) { - var builder = new SelectBuilder(); + var builder = SelectBuilder.GetSelectBuilder(); HandleFromTemp(builder, temp1, temp2, temp3); return new SelectProvider3(context.Ado, builder); } @@ -157,7 +157,7 @@ public static IExpSelect FromTemp FromTemp(this IExpressionContext context , IExpTemp temp1, IExpTemp temp2, IExpTemp temp3, IExpTemp temp4) { - var builder = new SelectBuilder(); + var builder = SelectBuilder.GetSelectBuilder(); HandleFromTemp(builder, temp1, temp2, temp3, temp4); return new SelectProvider4(context.Ado, builder); } @@ -165,7 +165,7 @@ public static IExpSelect FromTemp FromTemp(this IExpressionContext context , IExpTemp temp1, IExpTemp temp2, IExpTemp temp3, IExpTemp temp4, IExpTemp temp5) { - var builder = new SelectBuilder(); + var builder = SelectBuilder.GetSelectBuilder(); HandleFromTemp(builder, temp1, temp2, temp3, temp4, temp5); return new SelectProvider5(context.Ado, builder); } diff --git a/src/LightORM/Extension/GroupSelectExtensions.cs b/src/LightORM/Extension/GroupSelectExtensions.cs index 6589e5c6..a80d02d5 100644 --- a/src/LightORM/Extension/GroupSelectExtensions.cs +++ b/src/LightORM/Extension/GroupSelectExtensions.cs @@ -1,4 +1,6 @@ -namespace LightORM; +using LightORM.Utils.Vistors; + +namespace LightORM; public static class GroupSelectExtensions { diff --git a/src/LightORM/Extension/IncludeContextExtensions.cs b/src/LightORM/Extension/IncludeContextExtensions.cs index c4cd522d..fd3f0b9a 100644 --- a/src/LightORM/Extension/IncludeContextExtensions.cs +++ b/src/LightORM/Extension/IncludeContextExtensions.cs @@ -82,7 +82,7 @@ public static SelectBuilder BuildIncludeSqlBuilder(ICustomDatabase database, obj private static SelectBuilder BuildSql(ICustomDatabase database, IncludeInfo include, object item) { - SelectBuilder selectSql = new(); + SelectBuilder selectSql = SelectBuilder.GetSelectBuilder(); var selectedType = include.NavigateInfo!.NavigateType; selectSql.SelectedTables.Add(TableInfo.Create(selectedType)); //selectSql.DbParameterStartIndex = include.ExpressionResolvedResult!.DbParameters?.Count ?? 0; diff --git a/src/LightORM/Extension/SelectExtensions.cs b/src/LightORM/Extension/SelectExtensions.cs index e15d7e04..498424be 100644 --- a/src/LightORM/Extension/SelectExtensions.cs +++ b/src/LightORM/Extension/SelectExtensions.cs @@ -1,4 +1,6 @@ -namespace LightORM; +using LightORM.Utils.Vistors; + +namespace LightORM; // public static partial class Select1Ex // { diff --git a/src/LightORM/Extension/SelectHandleExtensions.cs b/src/LightORM/Extension/SelectHandleExtensions.cs index def44270..c85391a1 100644 --- a/src/LightORM/Extension/SelectHandleExtensions.cs +++ b/src/LightORM/Extension/SelectHandleExtensions.cs @@ -149,7 +149,7 @@ internal static void HandleResult(this IExpSelect select, Expression? exp, strin internal static SelectProvider1 HandleSubQuery(this IExpSelect select, string? alias = null) { select.SqlBuilder.IsSubQuery = true; - var builder = new SelectBuilder(); + var builder = SelectBuilder.GetSelectBuilder(); var table = TableInfo.Create(); if (alias != null) { diff --git a/src/LightORM/Interfaces/IExpressionContextSetup.cs b/src/LightORM/Interfaces/IExpressionContextSetup.cs index fa52ac54..f4837cb5 100644 --- a/src/LightORM/Interfaces/IExpressionContextSetup.cs +++ b/src/LightORM/Interfaces/IExpressionContextSetup.cs @@ -7,6 +7,7 @@ public interface IExpressionContextSetup IExpressionContextSetup SetDefault(string key); IExpressionContextSetup SetUseParameterized(bool use); IExpressionContextSetup SetConnectionPoolSize(int poolSize); + IExpressionContextSetup SetEnableExpressionCache(bool enable); IExpressionContextSetup SetDatabase(string? key, DbBaseType dbBaseType, IDatabaseProvider provider); IExpressionContextSetup SetTableContext(ITableContext context); IExpressionContextSetup UseInterceptor() where T : AdoInterceptorBase; diff --git a/src/LightORM/Interfaces/IResetable.cs b/src/LightORM/Interfaces/IResetable.cs new file mode 100644 index 00000000..07916ef8 --- /dev/null +++ b/src/LightORM/Interfaces/IResetable.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LightORM.Interfaces; + +internal interface IResetable +{ + void Reset(); +} diff --git a/src/LightORM/Models/ExpressionInfo.cs b/src/LightORM/Models/ExpressionInfo.cs index 8dff517a..6ab264f8 100644 --- a/src/LightORM/Models/ExpressionInfo.cs +++ b/src/LightORM/Models/ExpressionInfo.cs @@ -11,7 +11,7 @@ public ExpressionInfo() { } /// /// 解析Sql选项 /// - public SqlResolveOptions? ResolveOptions { get; set; } + public SqlResolveOptions ResolveOptions { get; set; } /// /// 表达式 /// diff --git a/src/LightORM/Models/ExpressionResolvedResult.cs b/src/LightORM/Models/ExpressionResolvedResult.cs index 0ab72918..23f4df88 100644 --- a/src/LightORM/Models/ExpressionResolvedResult.cs +++ b/src/LightORM/Models/ExpressionResolvedResult.cs @@ -1,7 +1,18 @@ namespace LightORM.Models; -internal class ExpressionResolvedResult +internal record ExpressionResolvedResult { + public ExpressionResolvedResult(ExpressionResolver resolve) + { + SqlString = resolve.Sql.ToString(); + Members = resolve.ResolvedMembers; + MemberOfNavigateMember = resolve.MemberOfNavigateMember; + UseNavigate = resolve.UseNavigate; + NavigateDeep = resolve.NavigateDeep; + NavigateMembers = resolve.NavigateMembers; + WindowFnPartials = resolve.WindowFnPartials; + NavigateWhereExpression = resolve.NavigateWhereExpression; + } /// /// 解析生成的sql语句 /// @@ -13,17 +24,18 @@ internal class ExpressionResolvedResult /// /// 解析到的成员 /// - public List? Members { get; set; } + public List? Members { get; } /// /// 是否使用了导航属性 /// - public bool UseNavigate { get; set; } - public int NavigateDeep { get; set; } + public bool UseNavigate { get; } + public int NavigateDeep { get; set; } + public bool NeedToExtractValues { get; set; } /// /// 解析到的导航属性成员 /// - public List? NavigateMembers { get; set; } - public string? MemberOfNavigateMember { get; set; } - public Expression? NavigateWhereExpression { get; set; } - public List? WindowFnPartials { get; set; } + public List? NavigateMembers { get; } + public string? MemberOfNavigateMember { get; } + public Expression? NavigateWhereExpression { get; } + public List? WindowFnPartials { get; } } diff --git a/src/LightORM/Performances/ExpressionResolverPool.cs b/src/LightORM/Performances/ExpressionResolverPool.cs new file mode 100644 index 00000000..42472667 --- /dev/null +++ b/src/LightORM/Performances/ExpressionResolverPool.cs @@ -0,0 +1,67 @@ +using System.Collections.Concurrent; +using System.Text; +namespace LightORM.Performances; + +internal static class ExpressionResolverPool +{ + private static readonly ConcurrentStack pool = []; + public static ExpressionResolver Rent(SqlResolveOptions options, ResolveContext context) + { + if (pool.TryPop(out var resolver)) + { + // 重置状态而不是创建新实例 + ResetResolver(resolver, options, context); + return resolver; + } + return new ExpressionResolver(options, context); + } + + public static void Return(ExpressionResolver resolver) + { + if (pool.Count < ExpressionSqlOptions.Instance.Value.InternalObjectPoolSize) + { + ClearResolver(resolver); + pool.Push(resolver); + } + } + + private static void ResetResolver(ExpressionResolver resolver, SqlResolveOptions options, ResolveContext context) + { + // 重用StringBuilder,只需Clear + resolver.Sql.Clear(); + resolver.Sql.EnsureCapacity(128); // 恢复初始容量 + + // 清空列表,重用底层数组 + resolver.DbParameters.Clear(); + resolver.Members.Clear(); + resolver.ResolvedMembers.Clear(); + resolver.NavigateMembers?.Clear(); + resolver.WindowFnPartials?.Clear(); + + // 重置字段 + resolver.Options = options; + resolver.Context = context; + resolver.IsNot = false; + resolver.UseNavigate = false; + resolver.NavigateDeep = 0; + resolver.Parameters = null; + resolver.ParameterPositionIndex = 0; + resolver.ResolveNullValue = false; + resolver.UseAs = true; + resolver.IsVisitConvert = false; + resolver.ContainVariable = false; + } + + private static void ClearResolver(ExpressionResolver resolver) + { + // 清空但不释放大数组,如果太大,重建 + if (resolver.Sql.Capacity > 4096) + resolver.Sql = new StringBuilder(128); + + if (resolver.DbParameters.Capacity > 32) + resolver.DbParameters = new List(8); + + if (resolver.ResolvedMembers.Capacity > 16) + resolver.ResolvedMembers = new List(4); + } +} \ No newline at end of file diff --git a/src/LightORM/Performances/ExpressionVisitorPool.cs b/src/LightORM/Performances/ExpressionVisitorPool.cs new file mode 100644 index 00000000..e7b22760 --- /dev/null +++ b/src/LightORM/Performances/ExpressionVisitorPool.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LightORM.Performances; + +internal static class ExpressionVisitorPool + where T : ExpressionVisitor, IResetable, new() +{ + private static readonly ConcurrentStack pool = new(); + public static T Rent() + { + if (pool.TryPop(out var visitor)) + { + return visitor; + } + return new T(); + } + + public static void Return(T visitor) + { + if (pool.Count < ExpressionSqlOptions.Instance.Value.InternalObjectPoolSize) + { + visitor.Reset(); + pool.Push(visitor); + } + } + +} diff --git a/src/LightORM/Performances/SelectBuilderPool.cs b/src/LightORM/Performances/SelectBuilderPool.cs new file mode 100644 index 00000000..198b4268 --- /dev/null +++ b/src/LightORM/Performances/SelectBuilderPool.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace LightORM.Performances; + +internal class SelectBuilderPool +{ + private static readonly ConcurrentStack pool = new(); + public static SelectBuilder Rent() + { + if (pool.TryPop(out var builder)) + { + ResetBuilder(builder); + return builder; + } + return new SelectBuilder(); + } + + public static void Return(SelectBuilder builder) + { + if (builder == null) return; + + // 清理资源但不释放对象 + ClearBuilder(builder); + + if (pool.Count < ExpressionSqlOptions.Instance.Value.InternalObjectPoolSize) + pool.Push(builder); + } + + private static void ResetBuilder(SelectBuilder builder) + { + // 清空列表但保留容量 + builder.SelectedTables.Clear(); + builder.Where.Clear(); + builder.Joins.Clear(); + builder.GroupBy.Clear(); + builder.OrderBy.Clear(); + builder.Having.Clear(); + builder.Includes?.Clear(); + builder.TempViews.Clear(); + builder.Unions.Clear(); + builder.DbParameters.Clear(); + builder.Where.Clear(); + builder.DbParameterInfos.Clear(); + + // 重置其他字段 + builder.PageIndex = 0; + builder.PageSize = 0; + builder.Skip = 0; + builder.Take = 0; + builder.IsDistinct = false; + builder.IsRollup = false; + builder.SelectValue = "*"; + builder.Level = 0; + builder.TableIndexFix = 0; + builder.IsSubQuery = false; + builder.IsTemp = false; + builder.IsUnion = false; + builder.TempName = null; + builder.InsertInfo = null; + builder.SubQuery = null; + builder.AdditionalValue = null; + builder.TargetObject = null; + builder.IsParameterized = true; + + // 清空Expressions + builder.Expressions.ExpressionInfos.Clear(); + } + + private static void ClearBuilder(SelectBuilder builder) + { + // 如果列表容量过大,重建以释放内存 + ShrinkListIfLarge(builder.Where); + ShrinkListIfLarge(builder.GroupBy); + ShrinkListIfLarge(builder.OrderBy); + ShrinkListIfLarge(builder.Joins); + // ... 其他列表 + } + + private static void ShrinkListIfLarge(List list) + { + if (list.Capacity > 64) // 如果容量太大 + list = new List(8); // 重建为小容量 + } +} diff --git a/src/LightORM/Performances/SlimList.cs b/src/LightORM/Performances/SlimList.cs new file mode 100644 index 00000000..74dcf422 --- /dev/null +++ b/src/LightORM/Performances/SlimList.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LightORM.Performances; + +internal struct SlimList +{ + private T _item0, _item1, _item2, _item3; + private T[]? _overflow; + private int _count; + + + public void Add(T item) + { + if (_count < 4) + { + switch (_count) + { + case 0: + _item0 = item; + break; + case 1: + _item1 = item; + break; + case 2: + _item2 = item; + break; + case 3: + _item3 = item; + break; + default: + break; + } + } + else + { + // 溢出到数组 + _overflow ??= new T[8]; + if (_count - 4 >= _overflow.Length) + Array.Resize(ref _overflow, _overflow.Length * 2); + _overflow[_count - 4] = item; + } + _count++; + } + + + public int Count => _count; + + public T this[int index] + { + get + { + if (index < 0 || index >= _count) + throw new IndexOutOfRangeException(); + return index switch + { + < 4 => index switch + { + 0 => _item0, + 1 => _item1, + 2 => _item2, + 3 => _item3, + _ => throw new InvalidOperationException() + }, + _ => _overflow![index - 4] + }; + } + } +} diff --git a/src/LightORM/Providers/GroupSelectProvider.cs b/src/LightORM/Providers/GroupSelectProvider.cs index 609aad78..d401fe63 100644 --- a/src/LightORM/Providers/GroupSelectProvider.cs +++ b/src/LightORM/Providers/GroupSelectProvider.cs @@ -1,4 +1,5 @@ -using System.Text; +using LightORM.Utils.Vistors; +using System.Text; namespace LightORM.Providers { diff --git a/src/LightORM/Providers/Select/SelectProvider1.cs b/src/LightORM/Providers/Select/SelectProvider1.cs index edcfe162..b426a6a0 100644 --- a/src/LightORM/Providers/Select/SelectProvider1.cs +++ b/src/LightORM/Providers/Select/SelectProvider1.cs @@ -1,5 +1,6 @@ using LightORM.Extension; using LightORM.Implements; +using LightORM.Utils.Vistors; using System.Threading; namespace LightORM.Providers.Select; @@ -16,7 +17,7 @@ public SelectProvider1(ISqlExecutor executor, SelectBuilder? builder = null) { if (builder == null) { - SqlBuilder = new SelectBuilder(); + SqlBuilder = SelectBuilder.GetSelectBuilder(); //SqlBuilder.SelectedTables.Add(TableContext.GetTableInfo()); SqlBuilder.SelectedTables.Add(TableInfo.Create()); } @@ -25,7 +26,7 @@ public SelectProvider1(ISqlExecutor executor, SelectBuilder? builder = null) public SelectProvider1(string overriddenTableName, ISqlExecutor executor) : base(executor) { - SqlBuilder = new SelectBuilder(); + SqlBuilder = SelectBuilder.GetSelectBuilder(); SqlBuilder.SelectedTables.Add(TableInfo.Create(overriddenTableName)); } diff --git a/src/LightORM/Providers/Select/SelectProvider2.cs b/src/LightORM/Providers/Select/SelectProvider2.cs index 040f473d..853767bb 100644 --- a/src/LightORM/Providers/Select/SelectProvider2.cs +++ b/src/LightORM/Providers/Select/SelectProvider2.cs @@ -1,4 +1,5 @@ -using System.Threading; +using LightORM.Utils.Vistors; +using System.Threading; namespace LightORM.Providers.Select; @@ -9,7 +10,7 @@ public SelectProvider2(ISqlExecutor executor, SelectBuilder? builder = null) { if (builder == null) { - SqlBuilder = new SelectBuilder(); + SqlBuilder = SelectBuilder.GetSelectBuilder(); SqlBuilder.SelectedTables.Add(TableInfo.Create(0)); SqlBuilder.SelectedTables.Add(TableInfo.Create(1)); } diff --git a/src/LightORM/Repository/LightOrmQueryProvider.cs b/src/LightORM/Repository/LightOrmQueryProvider.cs index 11a8daee..115fa7ca 100644 --- a/src/LightORM/Repository/LightOrmQueryProvider.cs +++ b/src/LightORM/Repository/LightOrmQueryProvider.cs @@ -1,9 +1,10 @@ using LightORM.Extension; +using LightORM.Utils.Vistors; namespace LightORM.Repository; internal class LightOrmQueryProvider : IQueryProvider { - private readonly SelectBuilder select = new(); + private readonly SelectBuilder select = SelectBuilder.GetSelectBuilder(); private readonly ISqlExecutor ado; private LambdaExpression? keySelector; public LightOrmQueryProvider(ISqlExecutor ado, Type type) diff --git a/src/LightORM/Utils/ExpressionResolver.cs b/src/LightORM/Utils/ExpressionResolver.cs index a6e28322..6481e265 100644 --- a/src/LightORM/Utils/ExpressionResolver.cs +++ b/src/LightORM/Utils/ExpressionResolver.cs @@ -1,4 +1,5 @@ using LightORM.Extension; +using LightORM.Performances; using System.Collections; using System.Collections.Concurrent; using System.Collections.ObjectModel; @@ -14,22 +15,52 @@ namespace LightORM; internal static class ExpressionExtensions { + private static readonly ConcurrentDictionary expressionResolvedResultCache = new(); public static ExpressionResolvedResult Resolve(this Expression? expression, SqlResolveOptions options, ResolveContext context) { - var resolve = new ExpressionResolver(options, context); - resolve.Visit(expression); - return new ExpressionResolvedResult + + //resolve.Visit(expression); + //return new ExpressionResolvedResult(resolve); + //var resolve = new ExpressionResolver(options, context); + bool enableCache = ExpressionSqlOptions.Instance.Value.EnableExpressionCache; + ulong key = 0; + if (enableCache) { - SqlString = resolve.Sql.ToString(), - DbParameters = resolve.DbParameters, - Members = resolve.ResolvedMembers, - MemberOfNavigateMember = resolve.MemberOfNavigateMember, - UseNavigate = resolve.UseNavigate, - NavigateDeep = resolve.NavigateDeep, - NavigateMembers = resolve.NavigateMembers, - WindowFnPartials = resolve.WindowFnPartials, - NavigateWhereExpression = resolve.NavigateWhereExpression - }; + key = ExpressionHasher.Default.ComputeHash64(expression); + Debug.WriteLineIf(ShowExpressionHashCodeDebugInfo, $"hashcocde: {key}"); + } + if (enableCache && expressionResolvedResultCache.TryGetValue(key, out var result)) + { + //result.DbParameters + if (result.NeedToExtractValues) + { + var parameters = ExpressionValueExtract.Default.Extract(expression, options, context); + return result with { DbParameters = parameters }; + } + return result; + } + else + { + var resolver = ExpressionResolverPool.Rent(options, context); + try + { + resolver.Visit(expression); + result = new(resolver) + { + NeedToExtractValues = resolver.ContainVariable + }; + expressionResolvedResultCache.TryAdd(key, result); + if (resolver.DbParameters.Count > 0) + { + return result with { DbParameters = [.. resolver.DbParameters] }; + } + return result; + } + finally + { + ExpressionResolverPool.Return(resolver); + } + } } public static string OperatorParser(this ExpressionType expressionNodeType, bool isNull) @@ -56,8 +87,8 @@ ExpressionType.Or or } internal class ExpressionResolver(SqlResolveOptions options, ResolveContext context) : IExpressionResolver { - public SqlResolveOptions Options { get; } = options; - public ResolveContext Context { get; } = context; + public SqlResolveOptions Options { get; set; } = options; + public ResolveContext Context { get; set; } = context; public List DbParameters { get; set; } = []; public StringBuilder Sql { get; set; } = new StringBuilder(128); public Stack Members { get; set; } = []; @@ -74,7 +105,13 @@ internal class ExpressionResolver(SqlResolveOptions options, ResolveContext cont private ISqlMethodResolver MethodResolver => Context.Database.MethodResolver; private ICustomDatabase Database => Context.Database; public Expression? Body => bodyExpression; - public ReadOnlyCollection? Parameters => parametersExpression; + public bool ContainVariable { get; set; } + public ReadOnlyCollection? Parameters + { + get => parametersExpression; + set => parametersExpression = value; + } + public Expression? Visit(Expression? expression) { //Debug.Write($""); @@ -98,11 +135,9 @@ internal class ExpressionResolver(SqlResolveOptions options, ResolveContext cont private const string AS_LITERAL = " AS "; Expression? bodyExpression; ReadOnlyCollection? parametersExpression; - int parameterPositionIndex = 0; - bool resolveNullValue; //string? lastResolvedColumnName; bool useAs = true; - bool UseAs + public bool UseAs { get { @@ -116,7 +151,8 @@ bool UseAs set => useAs = value; } - bool ResolveNullValue + bool resolveNullValue; + public bool ResolveNullValue { get { @@ -131,6 +167,14 @@ bool ResolveNullValue } bool isVisitConvert; + public bool IsVisitConvert + { + get => isVisitConvert; + set => isVisitConvert = value; + } + + int parameterPositionIndex = 0; + public int ParameterPositionIndex { get => parameterPositionIndex; set => parameterPositionIndex = value; } Expression? VisitLambda(LambdaExpression exp) { @@ -153,12 +197,13 @@ bool ResolveNullValue // 数组访问 if (exp.NodeType == ExpressionType.ArrayIndex) { - var index = ExtractInstanceValue(exp.Right); - var array = ExtractInstanceValue(exp.Left); + var index = ResolveHelper.ExtractInstanceValue(exp.Right); + var array = ResolveHelper.ExtractInstanceValue(exp.Left); var arrayValue = array!.GetValue(index); - var pname = FormatDbParameterName(Context, Options, $"Arr{index}", ref parameterPositionIndex); + var pname = ResolveHelper.FormatDbParameterName(Context, Options, $"Arr{index}", ref parameterPositionIndex); Sql.Append(pname); DbParameters.Add(new(pname, arrayValue, ExpValueType.Other)); + ContainVariable = true; return null; } if (Options.SqlType == SqlPartial.Where || Options.SqlType == SqlPartial.Join || Options.SqlType == SqlPartial.Select) @@ -179,45 +224,7 @@ bool ResolveNullValue return null; - // 尝试将表达式求值为常量(仅支持 Constant 和 简单 Member 访问) - static T ExtractInstanceValue(Expression expression) - { - if (expression is ConstantExpression ce && ce.Value is T index) - { - return index; - } - var members = new Stack(); - Expression? current = expression; - - // 向下遍历,收集 MemberInfo - while (current is MemberExpression memberExpr) - { - members.Push(memberExpr.Member); - current = memberExpr.Expression; - } - object? value; - - if (current is ConstantExpression constExpr) - { - value = constExpr.Value; - } - else if (current is null) - { - value = null; - } - else - { - throw new LightOrmException($"数组索引表达式必须以常量或者Null结尾,但得到: {current?.GetType().Name}: {current}"); - } - value = GetValue(members, value); - - if (value is T t) - { - return t; - } - throw new LightOrmException($"尝试获取类型{typeof(T)}的值,实际类型: {value?.GetType()}"); - } } Expression? VisitConditional(ConditionalExpression exp) @@ -307,7 +314,7 @@ static T ExtractInstanceValue(Expression expression) { Debug.WriteLineIf(ShowExpressionResolveDebugInfo, $"{Options.SqlAction} {Options.SqlType}: UnaryExpression: {exp}"); IsNot = exp.NodeType == ExpressionType.Not; - isVisitConvert = exp.NodeType == ExpressionType.Convert; + IsVisitConvert = exp.NodeType == ExpressionType.Convert; Visit(exp.Operand); return null; } @@ -336,9 +343,9 @@ static T ExtractInstanceValue(Expression expression) } else { - if (isVisitConvert && Members.Count > 0) + if (IsVisitConvert && Members.Count > 0) { - isVisitConvert = false; + IsVisitConvert = false; var member = Members.Pop(); var table = Context.GetTable(exp); var col = table.GetColumn(member.Name)!; @@ -467,10 +474,11 @@ static T ExtractInstanceValue(Expression expression) { //value = GetValue(Members, value, out var name); //VariableValue(value, name); - var v = GetValueByExpression(Members, value, out var propNames); - var pn = FormatDbParameterName(Context, Options, propNames, ref parameterPositionIndex); + var v = ResolveHelper.GetValueByExpression(Members, value, out var propNames); + var pn = ResolveHelper.FormatDbParameterName(Context, Options, propNames, ref parameterPositionIndex); Sql.Append(pn); VariableValue(v, pn); + ContainVariable = true; } else { @@ -548,81 +556,61 @@ void ConstraintValue(object? v) } } - public static string FormatDbParameterName(ResolveContext? context, SqlResolveOptions? option, string name, ref int index) - { - var p = $"{context?.ParameterPrefix}{name}_{option?.ParameterPartialIndex}_{index}"; - index += 1; - return p; - } - - /// - /// 获取值 - /// - /// 成员信息 - /// 编译器变量值 - /// 成员名称 - /// - [Obsolete] - public static object? GetValue(Stack memberInfos, object? compilerVar, out string memberName) - { - var names = new List(); - while (memberInfos.Count > 0) - { - var item = memberInfos.Pop(); - if (!item.Name.StartsWith("CS$<>8__locals")) - { - names.Add(item.Name); - } - - compilerVar = GetValue(item, compilerVar); - } - memberName = string.Join("_", names); - return compilerVar; - } - public static object? GetValue(Stack memberInfos, object? compilerVar) => GetValueByExpression(memberInfos, compilerVar, out _); - - /// - /// 获取值 - /// - /// 成员信息 - /// 对象 - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [Obsolete] - public static object? GetValue(MemberInfo memberInfo, object? obj) - { - return memberInfo switch - { - PropertyInfo prop => prop.GetValue(obj), - FieldInfo field => field.GetValue(obj), - _ => throw new NotSupportedException($"不支持获取 {memberInfo.MemberType} 类型值.") - }; - } - - private static readonly ConcurrentDictionary> getterCache = []; - public static object? GetValueByExpression(Stack memberInfos, object? value, out string name) - { - name = string.Join("_", memberInfos.Where(m => !m.Name.StartsWith("CS$<>8__locals")).Select(m => m.Name)); - if (value is null) return null; - var type = value.GetType(); - var memberKey = $"{type.FullName}_{name}"; - var func = getterCache.GetOrAdd(memberKey, _ => - { - return CreateGetter(type, memberInfos); - }); - return func.Invoke(value); - } - - private static Func CreateGetter(Type type, Stack memberInfos) - { - var param = Expression.Parameter(typeof(object), "obj"); - Expression body = Expression.Convert(param, type); - while (memberInfos.Count > 0) - { - body = Expression.MakeMemberAccess(body, memberInfos.Pop()); - } - body = Expression.Convert(body, typeof(object)); - var lambda = Expression.Lambda>(body, param); - return lambda.Compile(); - } + //public static void FormatDbParameterName(StringBuilder sql, ResolveContext? context, SqlResolveOptions? option, string name, ref int index) + //{ + // //var p = $"{context?.ParameterPrefix}{name}_{option?.ParameterPartialIndex}_{index}"; + // //index += 1; + // //return p; + // sql.Append(context?.ParameterPrefix); + // sql.Append(name); + // sql.Append('_'); + // sql.Append(option?.ParameterPartialIndex); + // sql.Append('_'); + // index += 1; + // sql.Append(index); + //} + + ///// + ///// 获取值 + ///// + ///// 成员信息 + ///// 编译器变量值 + ///// 成员名称 + ///// + //[Obsolete] + //public static object? GetValue(Stack memberInfos, object? compilerVar, out string memberName) + //{ + // var names = new List(); + // while (memberInfos.Count > 0) + // { + // var item = memberInfos.Pop(); + // if (!item.Name.StartsWith("CS$<>8__locals")) + // { + // names.Add(item.Name); + // } + + // compilerVar = GetValue(item, compilerVar); + // } + // memberName = string.Join("_", names); + // return compilerVar; + //} + + + ///// + ///// 获取值 + ///// + ///// 成员信息 + ///// 对象 + ///// + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + //[Obsolete] + //public static object? GetValue(MemberInfo memberInfo, object? obj) + //{ + // return memberInfo switch + // { + // PropertyInfo prop => prop.GetValue(obj), + // FieldInfo field => field.GetValue(obj), + // _ => throw new NotSupportedException($"不支持获取 {memberInfo.MemberType} 类型值.") + // }; + //} } diff --git a/src/LightORM/Utils/ExpressionValueExtract.cs b/src/LightORM/Utils/ExpressionValueExtract.cs index 8dd9280d..61573fbb 100644 --- a/src/LightORM/Utils/ExpressionValueExtract.cs +++ b/src/LightORM/Utils/ExpressionValueExtract.cs @@ -1,46 +1,91 @@ -using System.Collections; +using LightORM.Extension; +using LightORM.Implements; +using LightORM.Interfaces.ExpSql; +using LightORM.Performances; +using Microsoft.Extensions.Options; +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.ObjectModel; +using System.Diagnostics; using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; namespace LightORM.Utils; -internal class ExpressionValueExtract : ExpressionVisitor +internal class ExpressionValueExtract : ExpressionVisitor, IResetable { private readonly List parameters = []; - private int index = 0; + private int parameterIndex = 0; private readonly Stack members = []; - public static ExpressionValueExtract Default => new(); - private bool isVisitArray = false; - private int visitArrayIndex = -1; + public static ExpressionValueExtract Default => ExpressionVisitorPool.Rent(); private ResolveContext? context; private SqlResolveOptions? option; + public void Reset() + { + parameters.Clear(); + parameterIndex = 0; + members.Clear(); + context = null; + option = null; + } public List Extract(Expression? exp) { - if (exp is not null) + try + { + if (exp is not null) + { + Visit(exp); + } + return [..parameters]; + } + finally { - Visit(exp); + ExpressionVisitorPool.Return(this); } - return parameters; } public List Extract(Expression? exp, SqlResolveOptions option, ResolveContext context) { - if (exp is not null) + try + { + if (exp is not null) + { + this.context = context; + this.option = option; + Visit(exp); + } + return [..parameters]; + } + finally { - this.context = context; - this.option = option; - Visit(exp); + ExpressionVisitorPool.Return(this); } - return parameters; } protected override Expression VisitBinary(BinaryExpression node) { if (node.NodeType == ExpressionType.ArrayIndex) { - var index = Convert.ToInt32(Expression.Lambda(node.Right).Compile().DynamicInvoke()); - isVisitArray = true; - visitArrayIndex = index; + //var index = Convert.ToInt32(Expression.Lambda(node.Right).Compile().DynamicInvoke()); + var index = ResolveHelper.ExtractInstanceValue(node.Right); + var array = ResolveHelper.ExtractInstanceValue(node.Left); + var arrayValue = array!.GetValue(index); + var pname = ResolveHelper.FormatDbParameterName(context, option, $"Arr{index}", ref parameterIndex); + parameters.Add(new(pname, arrayValue, ExpValueType.Other)); + } + else + { + Visit(node.Left); + Visit(node.Right); } - return base.VisitBinary(node); + return node; + } + + protected override Expression VisitUnary(UnaryExpression node) + { + Visit(node.Operand); + return node; } protected override Expression VisitMember(MemberExpression node) @@ -53,7 +98,8 @@ protected override Expression VisitMember(MemberExpression node) { members.Push(node.Member); } - return base.VisitMember(node); + Visit(node.Expression); + return node; } @@ -62,8 +108,8 @@ protected override Expression VisitConstant(ConstantExpression node) var value = node.Value; if (members.Count > 0 && value != null) { - value = ExpressionResolver.GetValueByExpression(members, value, out var name); - var bn = ExpressionResolver.FormatDbParameterName(context, option, name, ref index); + value = ResolveHelper.GetValueByExpression(members, value, out var name); + var bn = ResolveHelper.FormatDbParameterName(context, option, name, ref parameterIndex); VariableValue(value, bn); } @@ -77,13 +123,7 @@ void VariableValue(object? v, string bn) return; } - if (isVisitArray && v is Array arr) - { - var value = arr.GetValue(visitArrayIndex); - var pn = ExpressionResolver.FormatDbParameterName(context, option, $"Arr{visitArrayIndex}", ref index); - parameters.Add(new(pn, value, ExpValueType.Other)); - } - else if (v is IEnumerable && v is not string) + if (v is IEnumerable && v is not string) { parameters.Add(new(bn, v, ExpValueType.Collection)); } @@ -98,3 +138,504 @@ void VariableValue(object? v, string bn) } } } + + +//internal class ExpressionValueResolver(SqlResolveOptions options, ResolveContext context) +//{ +// public SqlResolveOptions Options { get; set; } = options; +// public ResolveContext Context { get; set; } = context; +// public List DbParameters { get; set; } = []; +// public Stack Members { get; set; } = []; +// public List ResolvedMembers { get; set; } = []; +// public List? WindowFnPartials { get; set; } +// public bool IsNot { get; set; } +// public bool UseNavigate { get; set; } +// public int NavigateDeep { get; set; } +// public int Level => Context.Level; +// internal List NavigateMembers { get; set; } = []; +// public Dictionary? ExpStores { get; set; } +// public Expression? NavigateWhereExpression { get; set; } +// public string? MemberOfNavigateMember { get; set; } +// public Expression? Body => bodyExpression; +// public bool ContainVariable { get; set; } +// public ReadOnlyCollection? Parameters +// { +// get => parametersExpression; +// set => parametersExpression = value; +// } + +// public Expression? Visit(Expression? expression) +// { +// //Debug.Write($""); +// return expression switch +// { +// LambdaExpression => Visit(VisitLambda((LambdaExpression)expression)), +// BinaryExpression => Visit(VisitBinary((BinaryExpression)expression)), +// ConditionalExpression => Visit(VisitConditional((ConditionalExpression)expression)), +// MethodCallExpression => Visit(VisitMethodCall((MethodCallExpression)expression)), +// NewArrayExpression => Visit(VisitNewArray((NewArrayExpression)expression)), +// NewExpression => Visit(VisitNew((NewExpression)expression)), +// UnaryExpression => Visit(VisitUnary((UnaryExpression)expression)), +// ParameterExpression => Visit(VisitParameter((ParameterExpression)expression)), +// MemberInitExpression => Visit(VisitMemberInit((MemberInitExpression)expression)), +// MemberExpression => Visit(VisitMember((MemberExpression)expression)), +// ConstantExpression => Visit(VisitConstant((ConstantExpression)expression)), +// _ => null +// }; +// } + +// private const string AS_LITERAL = " AS "; +// Expression? bodyExpression; +// ReadOnlyCollection? parametersExpression; +// //string? lastResolvedColumnName; +// bool useAs = true; +// public bool UseAs +// { +// get +// { +// if (!useAs) +// { +// useAs = true; +// return false; +// } +// return useAs && Options.UseColumnAlias; +// } +// set => useAs = value; +// } + +// bool resolveNullValue; +// public bool ResolveNullValue +// { +// get +// { +// if (resolveNullValue) +// { +// resolveNullValue = false; +// return true; +// } +// return resolveNullValue; +// } +// set => resolveNullValue = value; +// } + +// bool isVisitConvert; +// public bool IsVisitConvert +// { +// get => isVisitConvert; +// set => isVisitConvert = value; +// } + +// int parameterPositionIndex = 0; +// public int ParameterPositionIndex { get => parameterPositionIndex; set => parameterPositionIndex = value; } + +// Expression? VisitLambda(LambdaExpression exp) +// { +// Debug.WriteLineIf(ShowExpressionResolveDebugInfo, $"{Options.SqlAction} {Options.SqlType}: LambdaExpression: {exp}"); +// bodyExpression = exp.Body; +// parametersExpression = exp.Parameters; +// //Context.Tables.Clear(); +// for (int i = 0; i < exp.Parameters.Count; i++) +// { +// ParameterExpression? item = exp.Parameters[i]; +// Context.HandleParameterExpression(item, i); +// } +// return bodyExpression; +// } + +// Expression? VisitBinary(BinaryExpression exp) +// { +// Debug.WriteLineIf(ShowExpressionResolveDebugInfo, $"{Options.SqlAction} {Options.SqlType}: BinaryExpression: {exp}"); +// // 数组访问 +// if (exp.NodeType == ExpressionType.ArrayIndex) +// { +// var index = ExtractInstanceValue(exp.Right); +// var array = ExtractInstanceValue(exp.Left); +// var arrayValue = array!.GetValue(index); +// var pname = FormatDbParameterName(Context, Options, $"Arr{index}", ref parameterPositionIndex); +// DbParameters.Add(new(pname, arrayValue, ExpValueType.Other)); +// return null; +// } + +// Visit(exp.Left); +// Visit(exp.Right); + +// return null; + +// // 尝试将表达式求值为常量(仅支持 Constant 和 简单 Member 访问) +// static T ExtractInstanceValue(Expression expression) +// { +// if (expression is ConstantExpression ce && ce.Value is T index) +// { +// return index; +// } +// var members = new Stack(); +// Expression? current = expression; + +// // 向下遍历,收集 MemberInfo +// while (current is MemberExpression memberExpr) +// { +// members.Push(memberExpr.Member); +// current = memberExpr.Expression; +// } +// object? value; + +// if (current is ConstantExpression constExpr) +// { +// value = constExpr.Value; +// } +// else if (current is null) +// { +// value = null; +// } +// else +// { +// throw new LightOrmException($"数组索引表达式必须以常量或者Null结尾,但得到: {current?.GetType().Name}: {current}"); +// } + +// value = GetValue(members, value); + +// if (value is T t) +// { +// return t; +// } +// throw new LightOrmException($"尝试获取类型{typeof(T)}的值,实际类型: {value?.GetType()}"); +// } +// } + +// Expression? VisitConditional(ConditionalExpression exp) +// { +// Debug.WriteLineIf(ShowExpressionResolveDebugInfo, $"{Options.SqlAction} {Options.SqlType}: ConditionalExpression: {exp}"); +// Visit(exp.Test); +// Visit(exp.IfTrue); +// Visit(exp.IfFalse); +// return null; +// } + +// Expression? VisitMethodCall(MethodCallExpression exp) +// { +// Debug.WriteLineIf(ShowExpressionResolveDebugInfo, $"{Options.SqlAction} {Options.SqlType}: MethodCallExpression: {exp}"); +// Members.Clear(); +// if (exp.Method.Name.Equals("get_Item") && (exp.Method.DeclaringType?.FullName?.StartsWith("System.Collections.Generic") ?? false)) +// { + +// } +// else +// { +// if (exp.Method.Name == "op_Implicit" && exp.Method.IsSpecialName) +// { +// return exp.Arguments[0]; +// } +// MethodResolver.Resolve(this, exp); +// } +// return null; +// } + +// Expression? VisitNewArray(NewArrayExpression exp) +// { +// Debug.WriteLineIf(ShowExpressionResolveDebugInfo, $"{Options.SqlAction} {Options.SqlType}: NewArrayExpression: {exp}"); +// for (int i = 0; i < exp.Expressions.Count; i++) +// { +// Visit(exp.Expressions[i]); +// } +// return null; +// } + +// Expression? VisitNew(NewExpression exp) +// { +// Debug.WriteLineIf(ShowExpressionResolveDebugInfo, $"{Options.SqlAction} {Options.SqlType}: NewExpression: {exp}"); +// for (int i = 0; i < exp.Arguments.Count; i++) +// { +// var arg = exp.Arguments[i]; +// Visit(arg); +// } +// return null; +// } + +// Expression? VisitUnary(UnaryExpression exp) +// { +// Debug.WriteLineIf(ShowExpressionResolveDebugInfo, $"{Options.SqlAction} {Options.SqlType}: UnaryExpression: {exp}"); +// IsNot = exp.NodeType == ExpressionType.Not; +// IsVisitConvert = exp.NodeType == ExpressionType.Convert; +// Visit(exp.Operand); +// return null; +// } + +// Expression? VisitParameter(ParameterExpression exp) +// { +// Debug.WriteLineIf(ShowExpressionResolveDebugInfo, $"{Options.SqlAction} {Options.SqlType}: ParameterExpression: {exp}"); +// if (Options.SqlType == SqlPartial.Select) +// { +// } +// else if (Options.SqlAction == SqlAction.Insert) +// { +// var table = Context.GetTable(exp); +// foreach (var item in table.TableEntityInfo.Columns) +// { +// var prop = Expression.Property(exp, item.PropertyName); +// Visit(prop); +// } +// } +// else +// { +// if (IsVisitConvert && Members.Count > 0) +// { +// IsVisitConvert = false; +// _ = Members.Pop(); +// } +// } + +// return null; +// } + +// Expression? VisitMemberInit(MemberInitExpression exp) +// { +// Debug.WriteLineIf(ShowExpressionResolveDebugInfo, $"{Options.SqlAction} {Options.SqlType}: MemberInitExpression: {exp}"); +// for (int i = 0; i < exp.Bindings.Count; i++) +// { +// if (exp.Bindings[i].BindingType != MemberBindingType.Assignment) +// { +// continue; +// } +// if (exp.Bindings[i] is not MemberAssignment memberAssign) +// { +// continue; +// } +// if (Options.SqlType == SqlPartial.Select) +// { +// Visit(memberAssign.Expression); +// } +// } +// return null; +// } + +// Expression? VisitMember(MemberExpression exp) +// { +// Debug.WriteLineIf(ShowExpressionResolveDebugInfo, $"{Options.SqlAction} {Options.SqlType}: MemberExpression: {exp}"); +// if (bodyExpression?.NodeType == ExpressionType.MemberAccess) +// { +// ResolvedMembers.Add(exp.Member.Name); +// } +// if (exp.Expression?.NodeType == ExpressionType.Parameter) +// { + +// var paramExp = exp.Expression as ParameterExpression; +// //var pType = paramExp?.Type; + +// var name = exp.Member.Name; +// var table = Context.GetTable(paramExp!); +// var col = table.GetColumn(name)!; +// if (col.IsNavigate == true) +// { +// UseNavigate = true; +// NavigateMembers.Add(col.PropertyName); +// if (NavigateDeep == 0) +// { +// if (Members.Count > 0) +// { +// //ResolvedMembers.Add(Members.Pop().Name); +// MemberOfNavigateMember = Members.Pop().Name; +// } +// Members.Clear(); +// } +// //else +// //{ +// // return null; +// //} +// return null; +// } +// // TODO 表达式扁平化处理后,只有聚合属性才有 p.XXX.XXX ? +// if (Members.Count > 0) +// { +// if (col.IsAggregated) +// { +// name = Members.Pop().Name; +// col = table.GetColumnInfo(name); +// } +// } + +// if (Options.SqlType == SqlPartial.Where) +// { +// ResolvedMembers.Add(col.PropertyName); +// } +// Members.Clear(); +// return null; +// } +// Members.Push(exp.Member); +// return exp.Expression ?? Expression.Constant(exp.Type.TypeDefaultValue(), exp.Type); +// } + +// Expression? VisitConstant(ConstantExpression exp) +// { +// Debug.WriteLineIf(ShowExpressionResolveDebugInfo, $"{Options.SqlAction} {Options.SqlType}: ConstantExpression: {exp}"); +// var value = exp.Value; +// if (Members.Count > 0) +// { +// //value = GetValue(Members, value, out var name); +// //VariableValue(value, name); +// var v = GetValueByExpression(Members, value, out var propNames); +// var pn = FormatDbParameterName(Context, Options, propNames, ref parameterPositionIndex); +// VariableValue(v, pn); +// } +// else +// { +// ConstraintValue(value); +// } +// return null; + +// void VariableValue(object? v, string bn) +// { +// if (v == null) +// { +// DbParameters.Add(new(bn, null, ExpValueType.Null)); +// } +// else if (v is IEnumerable && v is not string) +// { +// DbParameters.Add(new(bn, v, ExpValueType.Collection)); +// } +// else if (v is bool) +// { +// if (IsNot) +// { +// DbParameters.Add(new(bn, v, ExpValueType.BooleanReverse)); +// IsNot = false; +// } +// else +// { +// DbParameters.Add(new(bn, v, ExpValueType.Boolean)); +// } +// } +// else +// { +// DbParameters.Add(new(bn, v, ExpValueType.Other)); +// } +// } + +// void ConstraintValue(object? v) +// { +// if (v == null) +// { +// ResolveNullValue = true; +// return; +// } +// if (bodyExpression?.NodeType == ExpressionType.Constant && exp.Type == typeof(bool)) +// { +// } +// else +// { +// if (exp.Type.IsNumber()) +// { +// } +// else if (exp.Type.IsBoolean() && v is bool _) +// { +// if (IsNot) +// { +// IsNot = false; +// } +// else +// { +// } +// } +// else +// { +// } +// } +// } +// } + +// public static string FormatDbParameterName(ResolveContext? context, SqlResolveOptions? option, string name, ref int index) +// { +// var p = $"{context?.ParameterPrefix}{name}_{option?.ParameterPartialIndex}_{index}"; +// index += 1; +// return p; +// } + +// //public static void FormatDbParameterName(StringBuilder sql, ResolveContext? context, SqlResolveOptions? option, string name, ref int index) +// //{ +// // //var p = $"{context?.ParameterPrefix}{name}_{option?.ParameterPartialIndex}_{index}"; +// // //index += 1; +// // //return p; +// // sql.Append(context?.ParameterPrefix); +// // sql.Append(name); +// // sql.Append('_'); +// // sql.Append(option?.ParameterPartialIndex); +// // sql.Append('_'); +// // index += 1; +// // sql.Append(index); +// //} + +// /// +// /// 获取值 +// /// +// /// 成员信息 +// /// 编译器变量值 +// /// 成员名称 +// /// +// [Obsolete] +// public static object? GetValue(Stack memberInfos, object? compilerVar, out string memberName) +// { +// var names = new List(); +// while (memberInfos.Count > 0) +// { +// var item = memberInfos.Pop(); +// if (!item.Name.StartsWith("CS$<>8__locals")) +// { +// names.Add(item.Name); +// } + +// compilerVar = GetValue(item, compilerVar); +// } +// memberName = string.Join("_", names); +// return compilerVar; +// } +// public static object? GetValue(Stack memberInfos, object? compilerVar) => GetValueByExpression(memberInfos, compilerVar, out _); + +// /// +// /// 获取值 +// /// +// /// 成员信息 +// /// 对象 +// /// +// [MethodImpl(MethodImplOptions.AggressiveInlining)] +// [Obsolete] +// public static object? GetValue(MemberInfo memberInfo, object? obj) +// { +// return memberInfo switch +// { +// PropertyInfo prop => prop.GetValue(obj), +// FieldInfo field => field.GetValue(obj), +// _ => throw new NotSupportedException($"不支持获取 {memberInfo.MemberType} 类型值.") +// }; +// } + +// private static readonly ConcurrentDictionary> getterCache = []; +// public static object? GetValueByExpression(Stack memberInfos, object? value, out string name) +// { +// name = string.Join("_", memberInfos.Where(m => !m.Name.StartsWith("CS$<>8__locals")).Select(m => m.Name)); +// if (value is null) return null; +// var type = value.GetType(); +// var memberKey = $"{type.FullName}_{name}"; +// var func = getterCache.GetOrAdd(memberKey, _ => +// { +// return CreateGetter(type, memberInfos); +// }); +// return func.Invoke(value); +// } + +// private static Func CreateGetter(Type type, Stack memberInfos) +// { +// var param = Expression.Parameter(typeof(object), "obj"); +// Expression body = Expression.Convert(param, type); +// while (memberInfos.Count > 0) +// { +// body = Expression.MakeMemberAccess(body, memberInfos.Pop()); +// } +// body = Expression.Convert(body, typeof(object)); +// var lambda = Expression.Lambda>(body, param); +// return lambda.Compile(); +// } +//} + +//internal class MethodValueResolver : BaseSqlMethodResolver +//{ + +//} \ No newline at end of file diff --git a/src/LightORM/Utils/ResolveHelper.cs b/src/LightORM/Utils/ResolveHelper.cs new file mode 100644 index 00000000..be3854fa --- /dev/null +++ b/src/LightORM/Utils/ResolveHelper.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace LightORM.Utils; + +internal class ResolveHelper +{ + public static string FormatDbParameterName(ResolveContext? context, SqlResolveOptions? option, string name, ref int index) + { + var p = $"{context?.ParameterPrefix}{name}_{option?.ParameterPartialIndex}_{index}"; + index += 1; + return p; + } + // 尝试将表达式求值为常量(仅支持 Constant 和 简单 Member 访问) + public static T ExtractInstanceValue(Expression expression) + { + if (expression is ConstantExpression ce && ce.Value is T index) + { + return index; + } + var members = new Stack(); + Expression? current = expression; + + // 向下遍历,收集 MemberInfo + while (current is MemberExpression memberExpr) + { + members.Push(memberExpr.Member); + current = memberExpr.Expression; + } + object? value; + + if (current is ConstantExpression constExpr) + { + value = constExpr.Value; + } + else if (current is null) + { + value = null; + } + else + { + throw new LightOrmException($"数组索引表达式必须以常量或者Null结尾,但得到: {current?.GetType().Name}: {current}"); + } + + value = GetValue(members, value); + + if (value is T t) + { + return t; + } + throw new LightOrmException($"尝试获取类型{typeof(T)}的值,实际类型: {value?.GetType()}"); + } + + public static object? GetValue(Stack memberInfos, object? compilerVar) => GetValueByExpression(memberInfos, compilerVar, out _); + + private static readonly ConcurrentDictionary> getterCache = []; + public static object? GetValueByExpression(Stack memberInfos, object? value, out string name) + { + name = string.Join("_", memberInfos.Where(m => !m.Name.StartsWith("CS$<>8__locals")).Select(m => m.Name)); + if (value is null) return null; + var type = value.GetType(); + var memberKey = $"{type.FullName}_{name}"; + var func = getterCache.GetOrAdd(memberKey, _ => + { + return CreateGetter(type, memberInfos); + }); + memberInfos.Clear(); + return func.Invoke(value); + } + + private static Func CreateGetter(Type type, Stack memberInfos) + { + var param = Expression.Parameter(typeof(object), "obj"); + Expression body = Expression.Convert(param, type); + while (memberInfos.Count > 0) + { + body = Expression.MakeMemberAccess(body, memberInfos.Pop()); + } + body = Expression.Convert(body, typeof(object)); + var lambda = Expression.Lambda>(body, param); + return lambda.Compile(); + } +} diff --git a/src/LightORM/Utils/Vistors/ExpressionHasher.cs b/src/LightORM/Utils/Vistors/ExpressionHasher.cs new file mode 100644 index 00000000..dfbd8a2b --- /dev/null +++ b/src/LightORM/Utils/Vistors/ExpressionHasher.cs @@ -0,0 +1,598 @@ +using LightORM.Performances; +using System.Collections.Concurrent; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; + +namespace LightORM.Utils.Vistors; + +//internal class ExpressionHashCreator : ExpressionVisitor, IResetable +//{ +// private readonly StringBuilder sb = new(128); +// public void Reset() +// { +// sb.Clear(); +// sb.EnsureCapacity(128); +// } + +// public static ExpressionHashCreator Default => ExpressionVisitorPool.Rent(); + +// public string Scan(Expression? node) +// { +// try +// { +// if (node is null) return string.Empty; +// _ = Visit(node); +// return sb.ToString(); +// } +// finally +// { +// ExpressionVisitorPool.Return(this); +// } +// } + +// protected override Expression VisitBinary(BinaryExpression node) +// { +// sb.Append("B:"); +// sb.Append(node.NodeType); +// sb.Append('('); +// Visit(node.Left); +// sb.Append(','); +// Visit(node.Right); +// sb.Append(')'); +// return node; +// } + +// protected override Expression VisitUnary(UnaryExpression node) +// { +// sb.Append("U:"); +// sb.Append(node.NodeType); +// sb.Append('('); +// Visit(node.Operand); +// sb.Append(')'); +// return node; +// } + +// protected override Expression VisitConstant(ConstantExpression node) +// { +// sb.Append("CT"); +// return node; +// } + +// protected override Expression VisitParameter(ParameterExpression node) +// { +// sb.Append("P:"); +// // 使用类型名 + 名称(若无名称则用类型) +// //sb.Append(node.Name ?? node.Type.Name); +// sb.Append('('); +// AppendTypeName(sb, node.Type.Name); +// sb.Append(':'); +// sb.Append(node.Name); +// sb.Append(')'); +// return node; +// } + +// protected override Expression VisitMember(MemberExpression node) +// { +// sb.Append("M:"); +// sb.Append(node.Member.Name); +// sb.Append('@'); +// //sb.Append(node.Member.DeclaringType?.Name ?? "null"); +// AppendTypeName(sb, node.Member.DeclaringType?.Name ?? "NULL"); +// sb.Append('('); +// Visit(node.Expression); // 递归访问对象表达式(可能是参数或另一个成员) +// sb.Append(')'); +// return node; +// } + +// protected override Expression VisitMethodCall(MethodCallExpression node) +// { +// sb.Append("C:"); +// //sb.Append(node.Method.DeclaringType?.Name ?? "null"); +// AppendTypeName(sb, node.Method.DeclaringType?.Name ?? "NULL"); +// sb.Append('.'); +// sb.Append(node.Method.Name); +// sb.Append('('); +// for (int i = 0; i < node.Arguments.Count; i++) +// { +// if (i > 0) sb.Append(','); +// Visit(node.Arguments[i]); +// } +// sb.Append(')'); +// return node; +// } + +// protected override Expression VisitMemberInit(MemberInitExpression node) +// { +// sb.Append("I:"); +// AppendTypeName(sb, node.Type.Name); +// sb.Append('('); +// for (int i = 0; i < node.Bindings.Count; i++) +// { +// if (i > 0) sb.Append(','); +// var b = node.Bindings[i]; +// sb.Append(b.Member.Name); +// } +// sb.Append(')'); +// return node; +// } + +// protected override Expression VisitNew(NewExpression node) +// { +// sb.Append("N:"); +// //sb.Append(node.Constructor?.DeclaringType?.Name ?? "Anonymous"); +// AppendTypeName(sb, node.Constructor?.DeclaringType?.Name ?? "Amo"); +// sb.Append('('); +// for (int i = 0; i < node.Arguments.Count; i++) +// { +// if (i > 0) sb.Append(','); +// Visit(node.Arguments[i]); +// } +// sb.Append(')'); +// return node; +// } + +// protected override Expression VisitConditional(ConditionalExpression node) +// { +// sb.Append("CD("); +// Visit(node.Test); +// sb.Append('?'); +// Visit(node.IfTrue); +// sb.Append(':'); +// Visit(node.IfFalse); +// sb.Append(')'); +// return node; +// } + +// private static void AppendTypeName(StringBuilder sb, string name) +// { +// const string ANONYMOUS_PREFIX = "<>f__AnonymousType"; +// var i = name.IndexOf(ANONYMOUS_PREFIX); +// if (i > -1) +// { +// sb.Append('A'); +// var ccc = name.AsSpan().Slice(i + ANONYMOUS_PREFIX.Length); +// for (int j = 0; j < ccc.Length; j++) +// { +// sb.Append(ccc[j]); +// } +// } +// else +// { +// sb.Append(name); +// } + +// } +//} + + +internal class ExpressionHasher : ExpressionVisitor, IResetable +{ + + private const ulong FNVOffsetBasis = 0xCBF29CE484222325; + private const ulong FNVPrime = 0x100000001B3; + private ulong _hashCode = FNVOffsetBasis; + private int _parameterIndex = 0; + private readonly Dictionary _parameterMap = new(); + private readonly Stack _scopes = new(); + + // 缓存常用类型的元数据令牌 + private static readonly ConcurrentDictionary _typeTokenCache = new(); + private static readonly ConcurrentDictionary _memberTokenCache = new(); + + public void Reset() + { + _hashCode = FNVOffsetBasis; + _parameterIndex = 0; + _parameterMap.Clear(); + _scopes.Clear(); + } + + public static ExpressionHasher Default => ExpressionVisitorPool.Rent(); + + /// + /// 计算表达式的64位哈希值(低碰撞率) + /// + public ulong ComputeHash64(Expression? node, bool includeParameterNames = false) + { + try + { + if (node is null) return 0; + + if (includeParameterNames) + { + _scopes.Push(new HashScope(ref _parameterIndex, _parameterMap)); + } + + Visit(node); + + if (includeParameterNames) + { + _scopes.Pop(); + } + + return _hashCode; + } + finally + { + ExpressionVisitorPool.Return(this); + } + } + + /// + /// 计算32位哈希(兼容旧代码) + /// + public int ComputeHash32(Expression? node, bool includeParameterNames = false) + { + var hash64 = ComputeHash64(node, includeParameterNames); + return (int)(hash64 ^ (hash64 >> 32)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void AddToHash(ulong value) + { + unchecked + { + _hashCode ^= value; + _hashCode *= FNVPrime; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void AddToHash(int value) => AddToHash((ulong)value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void AddToHash(long value) => AddToHash((ulong)value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void AddToHash(bool value) => AddToHash(value ? 1UL : 0UL); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void AddToHash(string value) + { + if (string.IsNullOrEmpty(value)) + { + AddToHash(0); + return; + } + + // 使用快速的字符串哈希 + unchecked + { + ulong hash = 5381; + foreach (char c in value) + { + hash = ((hash << 5) + hash) ^ c; + } + AddToHash(hash); + } + } + + private void AddTypeInfo(Type type) + { + if (type == null) + { + AddToHash(0); + return; + } + + // 使用元数据令牌作为类型标识 + if (!_typeTokenCache.TryGetValue(type, out var token)) + { + try + { + // 尝试获取元数据令牌 + token = type.MetadataToken; + } + catch + { + // 回退到类型名称哈希 + token = type.GetHashCode(); + } + _typeTokenCache.TryAdd(type, token); + } + + AddToHash(token); + } + + private void AddMemberInfo(MemberInfo member) + { + if (member == null) + { + AddToHash(0); + return; + } + + if (!_memberTokenCache.TryGetValue(member, out var token)) + { + try + { + token = member.MetadataToken; + } + catch + { + token = member.GetHashCode(); + } + _memberTokenCache.TryAdd(member, token); + } + + AddToHash(token); + } + + public override Expression? Visit(Expression? node) + { + if (node == null) return null; + + // 添加节点类型 + AddToHash((int)node.NodeType); + + // 添加类型信息 + AddTypeInfo(node.Type); + + return base.Visit(node); + } + + protected override Expression VisitBinary(BinaryExpression node) + { + AddToHash(node.IsLifted); + AddToHash(node.IsLiftedToNull); + + if (node.Method != null) + { + AddMemberInfo(node.Method); + } + + base.VisitBinary(node); + return node; + } + + protected override Expression VisitUnary(UnaryExpression node) + { + if (node.Method != null) + { + AddMemberInfo(node.Method); + } + + base.VisitUnary(node); + return node; + } + + protected override Expression VisitConstant(ConstantExpression node) + { + if (node.Value == null) + { + AddToHash(0xBADC0DE5); + } + else + { + var type = node.Value.GetType(); + AddTypeInfo(type); + + // 为常见值类型提供优化处理 + switch (node.Value) + { + case string str: + AddToHash(str); + break; + case int i: + AddToHash((ulong)i); + break; + case long l: + AddToHash((ulong)l); + break; + case bool b: + AddToHash(b); + break; + case decimal d: + var bits = decimal.GetBits(d); + foreach (var bit in bits) + { + AddToHash((ulong)bit); + } + break; + case double dbl: + AddToHash(DoubleToUInt64Bits(dbl)); + break; + case float f: + AddToHash(SingleToInt64Bits(f)); + break; + case IQueryable: + // 忽略IQueryable的具体实例 + AddToHash(0x5155455259); // "QUERY"的ASCII码 + break; + case Enum e: + AddToHash(Convert.ToUInt64(e)); + break; + case Guid guid: + var guidBytes = guid.ToByteArray(); + AddToHash(BitConverter.ToUInt64(guidBytes, 0)); + AddToHash(BitConverter.ToUInt64(guidBytes, 8)); + break; + case DateTime dateTime: + AddToHash(dateTime.Ticks); + break; + case DateTimeOffset dateTimeOffset: + AddToHash(dateTimeOffset.Ticks); + AddToHash(dateTimeOffset.Offset.Ticks); + break; + case TimeSpan timeSpan: + AddToHash(timeSpan.Ticks); + break; + case byte b: + AddToHash(b); + break; + case sbyte sb: + AddToHash((ulong)sb); + break; + case short s: + AddToHash((ulong)s); + break; + case ushort us: + AddToHash(us); + break; + case uint ui: + AddToHash(ui); + break; + case char c: + AddToHash(c); + break; + default: + // 使用值的哈希码 + AddToHash((ulong)node.Value.GetHashCode()); + break; + } + } + + return node; + + static ulong DoubleToUInt64Bits(double value) + { +#if NET462 + return (ulong)BitConverter.DoubleToInt64Bits(value); +#else + return BitConverter.DoubleToUInt64Bits(value); +#endif + } + + static long SingleToInt64Bits(float value) + { +#if NET462 + return BitConverter.DoubleToInt64Bits(value); +#else + return (long)BitConverter.SingleToUInt32Bits(value); +#endif + } + } + + protected override Expression VisitParameter(ParameterExpression node) + { + if (_scopes.Count > 0) // 包含参数名模式 + { + if (!_parameterMap.TryGetValue(node, out var index)) + { + index = ++_parameterIndex; + _parameterMap[node] = index; + } + + AddToHash(index); + AddTypeInfo(node.Type); + + if (!string.IsNullOrEmpty(node.Name)) + { + AddToHash(node.Name); + } + } + else // 忽略参数名模式 + { + AddTypeInfo(node.Type); + } + + return node; + } + + protected override Expression VisitMember(MemberExpression node) + { + AddMemberInfo(node.Member); + base.VisitMember(node); + return node; + } + + protected override Expression VisitMethodCall(MethodCallExpression node) + { + AddMemberInfo(node.Method); + + if (node.Method.IsGenericMethod) + { + foreach (var arg in node.Method.GetGenericArguments()) + { + AddTypeInfo(arg); + } + } + + base.VisitMethodCall(node); + return node; + } + + protected override Expression VisitMemberInit(MemberInitExpression node) + { + AddTypeInfo(node.Type); + + if (node.NewExpression.Constructor != null) + { + AddMemberInfo(node.NewExpression.Constructor); + } + + foreach (var binding in node.Bindings) + { + AddMemberInfo(binding.Member); + AddToHash((int)binding.BindingType); + } + + base.VisitMemberInit(node); + return node; + } + + protected override Expression VisitNew(NewExpression node) + { + AddTypeInfo(node.Type); + + if (node.Constructor != null) + { + AddMemberInfo(node.Constructor); + } + + if (node.Members != null) + { + foreach (var member in node.Members) + { + AddMemberInfo(member); + } + } + + base.VisitNew(node); + return node; + } + + protected override Expression VisitLambda(Expression node) + { + // 开始新的参数作用域 + var scope = new HashScope(ref _parameterIndex, _parameterMap); + _scopes.Push(scope); + + // 处理参数 + foreach (var param in node.Parameters) + { + VisitParameter(param); + } + + // 处理主体 + Visit(node.Body); + + _scopes.Pop(); + return node; + } + + protected override Expression VisitTypeBinary(TypeBinaryExpression node) + { + AddTypeInfo(node.TypeOperand); + base.VisitTypeBinary(node); + return node; + } + + // 辅助结构体,用于管理参数作用域 + private struct HashScope + { + public int SavedIndex; + public Dictionary SavedMap; + + public HashScope(ref int currentIndex, Dictionary currentMap) + { + SavedIndex = currentIndex; + SavedMap = new Dictionary(currentMap); + currentIndex = 0; + currentMap.Clear(); + } + } + +} \ No newline at end of file diff --git a/src/LightORM/Utils/FlatGrouping.cs b/src/LightORM/Utils/Vistors/FlatGrouping.cs similarity index 68% rename from src/LightORM/Utils/FlatGrouping.cs rename to src/LightORM/Utils/Vistors/FlatGrouping.cs index bc1f0ee3..28904276 100644 --- a/src/LightORM/Utils/FlatGrouping.cs +++ b/src/LightORM/Utils/Vistors/FlatGrouping.cs @@ -1,47 +1,65 @@ -namespace LightORM.Utils; +using LightORM; +using LightORM.Performances; -internal class FlatGrouping : ExpressionVisitor +namespace LightORM.Utils.Vistors; + +internal class FlatGrouping : ExpressionVisitor, IResetable { private List parameters = []; private ParameterExpression? lambdaTypeSet; private LambdaExpression? keySelector; private int groupTypeIndex = 0; - public static FlatGrouping Default => new(); + public static FlatGrouping Default => ExpressionVisitorPool.Rent(); + + public void Reset() + { + parameters.Clear(); + lambdaTypeSet = null; + keySelector = null; + groupTypeIndex = 0; + } public LambdaExpression? Flat(LambdaExpression exp, LambdaExpression keySelector) { - var p = exp.Parameters; - // IExpSelectGrouping - if (p.Count == 1 && p[0].Type.Name.StartsWith("IExpSelectGrouping")) + try { - var gType = p[0].Type.GenericTypeArguments[0]; - // 并且 TTables 是 TypeSet - if (p.Count == 1 && p[0].Type.GenericTypeArguments[1].Name.StartsWith("TypeSet`")) + var p = exp.Parameters; + // IExpSelectGrouping + if (p.Count == 1 && p[0].Type.Name.StartsWith("IExpSelectGrouping")) { - var type = p[0].Type.GenericTypeArguments[1]; - var tp = type.GetProperties().Where(p => p.Name.StartsWith("Tb")).Select((p, i) => Expression.Parameter(p.PropertyType, $"p{i}")).ToArray(); - groupTypeIndex = tp.Length; - parameters = [.. tp, p[0]]; + var gType = p[0].Type.GenericTypeArguments[0]; + // 并且 TTables 是 TypeSet + if (p.Count == 1 && p[0].Type.GenericTypeArguments[1].Name.StartsWith("TypeSet`")) + { + var type = p[0].Type.GenericTypeArguments[1]; + var tp = type.GetProperties().Where(p => p.Name.StartsWith("Tb")).Select((p, i) => Expression.Parameter(p.PropertyType, $"p{i}")).ToArray(); + groupTypeIndex = tp.Length; + parameters = [.. tp, p[0]]; + } + else + { + groupTypeIndex = 1; + var type = p[0].Type.GenericTypeArguments[1]; + parameters = [Expression.Parameter(type, "p0"), p[0]]; + } + } + else if (p.Count == 1 && p[0].Type.Name.StartsWith("IGrouping")) + { + parameters = [.. p]; } else { - groupTypeIndex = 1; - var type = p[0].Type.GenericTypeArguments[1]; - parameters = [Expression.Parameter(type, "p0"), p[0]]; + return exp; } + this.keySelector = keySelector; + TryAddSelectorParameters(); + var body = Visit(exp.Body); + return Expression.Lambda(body, parameters); } - else if (p.Count == 1 && p[0].Type.Name.StartsWith("IGrouping")) - { - parameters = [.. p]; - } - else + finally { - return exp; + ExpressionVisitorPool.Return(this); } - this.keySelector = keySelector; - TryAddSelectorParameters(); - var body = Visit(exp.Body); - return Expression.Lambda(body, parameters); } private void TryAddSelectorParameters() diff --git a/src/LightORM/Utils/FlatTypeSet.cs b/src/LightORM/Utils/Vistors/FlatTypeSet.cs similarity index 54% rename from src/LightORM/Utils/FlatTypeSet.cs rename to src/LightORM/Utils/Vistors/FlatTypeSet.cs index d4d887cc..fab4cf60 100644 --- a/src/LightORM/Utils/FlatTypeSet.cs +++ b/src/LightORM/Utils/Vistors/FlatTypeSet.cs @@ -1,46 +1,60 @@ -namespace LightORM.Utils; +using LightORM; +using LightORM.Performances; -internal class FlatTypeSet : ExpressionVisitor +namespace LightORM.Utils.Vistors; + +internal class FlatTypeSet : ExpressionVisitor, IResetable { private ParameterExpression[] parameters = []; private ParameterExpression? lambdaTypeSet; - public static FlatTypeSet Default => new(); - + public static FlatTypeSet Default => ExpressionVisitorPool.Rent(); + public void Reset() + { + parameters = []; + lambdaTypeSet = null; + } public Expression? Flat(LambdaExpression exp) { - var p = exp.Parameters; - // TypeSet - if (p.Count == 1 && p[0].Type.Name.StartsWith("TypeSet`")) + try { - var set = p[0]; - parameters = [.. set.Type.GetProperties().Where(p => p.Name.StartsWith("Tb")).Select((p, i) => Expression.Parameter(p.PropertyType, $"p{i}"))]; - lambdaTypeSet = p[0]; + var p = exp.Parameters; + // TypeSet + if (p.Count == 1 && p[0].Type.Name.StartsWith("TypeSet`")) + { + var set = p[0]; + parameters = [.. set.Type.GetProperties().Where(p => p.Name.StartsWith("Tb")).Select((p, i) => Expression.Parameter(p.PropertyType, $"p{i}"))]; + lambdaTypeSet = p[0]; + } + //// IExpSelectGrouping + //else if (p.Count == 1 && p[0].Type.Name.StartsWith("IExpSelectGrouping")) + //{ + // var gType = p[0].Type.GenericTypeArguments[0]; + // // 并且 TTables 是 TypeSet + // if (p.Count == 1 && p[0].Type.GenericTypeArguments[1].Name.StartsWith("TypeSet`")) + // { + // var type = p[0].Type.GenericTypeArguments[1]; + // var tp = type.GetProperties().Where(p => p.Name.StartsWith("Tb")).Select((p, i) => Expression.Parameter(p.PropertyType, $"p{i}")).ToArray(); + // groupTypeIndex = tp.Length; + // parameters = [.. tp, Expression.Parameter(gType, "group")]; + // } + // else + // { + // groupTypeIndex = 1; + // var type = p[0].Type.GenericTypeArguments[1]; + // parameters = [Expression.Parameter(type, "p0"), Expression.Parameter(gType, "group")]; + // } + //} + else + { + return exp; + } + var body = Visit(exp.Body); + return Expression.Lambda(body, parameters); } - //// IExpSelectGrouping - //else if (p.Count == 1 && p[0].Type.Name.StartsWith("IExpSelectGrouping")) - //{ - // var gType = p[0].Type.GenericTypeArguments[0]; - // // 并且 TTables 是 TypeSet - // if (p.Count == 1 && p[0].Type.GenericTypeArguments[1].Name.StartsWith("TypeSet`")) - // { - // var type = p[0].Type.GenericTypeArguments[1]; - // var tp = type.GetProperties().Where(p => p.Name.StartsWith("Tb")).Select((p, i) => Expression.Parameter(p.PropertyType, $"p{i}")).ToArray(); - // groupTypeIndex = tp.Length; - // parameters = [.. tp, Expression.Parameter(gType, "group")]; - // } - // else - // { - // groupTypeIndex = 1; - // var type = p[0].Type.GenericTypeArguments[1]; - // parameters = [Expression.Parameter(type, "p0"), Expression.Parameter(gType, "group")]; - // } - //} - else + finally { - return exp; + ExpressionVisitorPool.Return(this); } - var body = Visit(exp.Body); - return Expression.Lambda(body, parameters); } /// /// 将属性访问的节点替换掉 diff --git a/src/LightORM/Utils/LinqExpressionFlattener.cs b/src/LightORM/Utils/Vistors/LinqExpressionFlattener.cs similarity index 52% rename from src/LightORM/Utils/LinqExpressionFlattener.cs rename to src/LightORM/Utils/Vistors/LinqExpressionFlattener.cs index 2d1d0049..d75251d2 100644 --- a/src/LightORM/Utils/LinqExpressionFlattener.cs +++ b/src/LightORM/Utils/Vistors/LinqExpressionFlattener.cs @@ -1,39 +1,53 @@ -using System.Reflection; +using LightORM; +using LightORM.Performances; +using System.Reflection; -namespace LightORM.Utils; +namespace LightORM.Utils.Vistors; -internal class LinqExpressionFlattener : ExpressionVisitor +internal class LinqExpressionFlattener : ExpressionVisitor, IResetable { private List newParameters = []; private ParameterExpression? _transparentParameter; - public static LinqExpressionFlattener Default => new(); + public static LinqExpressionFlattener Default => ExpressionVisitorPool.Rent(); + public void Reset() + { + newParameters.Clear(); + _transparentParameter = null; + } public LambdaExpression Flatten(LambdaExpression lambda) { - var p = lambda.Parameters; - // 检查是否是透明标识符模式 - if (p.Count == 1 && - p[0].Name?.Contains("TransparentIdentifier") == true) + try { - _transparentParameter = lambda.Parameters[0]; + var p = lambda.Parameters; + // 检查是否是透明标识符模式 + if (p.Count == 1 && + p[0].Name?.Contains("TransparentIdentifier") == true) + { + _transparentParameter = lambda.Parameters[0]; + + //// 从透明标识符中提取原始参数 + //if (lambda.Body is MemberExpression memberExpr && memberExpr.Expression is not null) + //{ + // // 创建原始参数表达式 + // _originalParameter = Expression.Parameter(memberExpr.Expression.Type, "p"); + // // 访问并转换表达式体 + // // 返回新的Lambda表达式 + //} + var newBody = Visit(lambda.Body); + return Expression.Lambda(newBody, newParameters); + } + // IGrouping + else if (p.Count == 1 && p[0].Type.Name.StartsWith("IGrouping")) + { - //// 从透明标识符中提取原始参数 - //if (lambda.Body is MemberExpression memberExpr && memberExpr.Expression is not null) - //{ - // // 创建原始参数表达式 - // _originalParameter = Expression.Parameter(memberExpr.Expression.Type, "p"); - // // 访问并转换表达式体 - // // 返回新的Lambda表达式 - //} - var newBody = Visit(lambda.Body); - return Expression.Lambda(newBody, newParameters); + } + // 不是透明标识符模式,直接返回原表达式 + return lambda; } - // IGrouping - else if (p.Count == 1 && p[0].Type.Name.StartsWith("IGrouping")) + finally { - + ExpressionVisitorPool.Return(this); } - // 不是透明标识符模式,直接返回原表达式 - return lambda; } protected override Expression VisitMember(MemberExpression node) diff --git a/src/LightORM/Versions.props b/src/LightORM/Versions.props index 34124478..a8de349f 100644 --- a/src/LightORM/Versions.props +++ b/src/LightORM/Versions.props @@ -1,6 +1,6 @@  - 3.2.1 + 2026.02.11.1 $(BuildVersion) $(BuildVersion) diff --git a/src/LightORM/usings.cs b/src/LightORM/usings.cs index d242392e..30110e7f 100644 --- a/src/LightORM/usings.cs +++ b/src/LightORM/usings.cs @@ -7,13 +7,13 @@ global using LightORM.Providers.Select; global using LightORM.SqlExecutor; global using LightORM.Utils; +global using LightORM.Utils.Vistors; global using System; global using System.Collections.Generic; global using System.Data; global using System.Linq; global using System.Linq.Expressions; global using System.Threading.Tasks; - namespace System.Runtime.CompilerServices { internal static class IsExternalInit { } diff --git a/src/LightOrmExtensionGenerator/SelectProvidersGenerator.cs b/src/LightOrmExtensionGenerator/SelectProvidersGenerator.cs index ff61bbe6..08dd9e71 100644 --- a/src/LightOrmExtensionGenerator/SelectProvidersGenerator.cs +++ b/src/LightOrmExtensionGenerator/SelectProvidersGenerator.cs @@ -206,7 +206,7 @@ internal sealed class SelectProvider{{count}}<{{argsStr}}> : SelectProvider0 + + + Exe + net10.0 + enable + enable + + + + + + + + + + + + + diff --git a/test/BenchmarkTest/Models/User.cs b/test/BenchmarkTest/Models/User.cs new file mode 100644 index 00000000..31de8145 --- /dev/null +++ b/test/BenchmarkTest/Models/User.cs @@ -0,0 +1,48 @@ +using LightORM; + +namespace BenchmarkTest.Models; + +public enum SignType +{ + None = 0, + Vip = 1, + Svip = 2 +} + +[LightTable(Name = "USER")] +public class User +{ + /// + /// 自增ID + /// + [LightColumn(Name = "ID", PrimaryKey = true, Comment = "自增ID", AutoIncrement = true)] + public int Id { get; set; } + [LightColumn(Name = "USER_ID", PrimaryKey = true, Comment = "用户ID")] + public string UserId { get; set; } + [LightColumn(Name = "USER_NAME", Comment = "名称")] + public string UserName { get; set; } + [LightColumn(Name = "PASSWORD", Comment = "密码")] + public string Password { get; set; } + [LightColumn(Name = "AGE", Comment = "年龄")] + public int? Age { get; set; } + [LightColumn(Name = "SIGN", Comment = "签名")] + public SignType Sign { get; set; } + [LightColumn(Name = "LAST_LOGIN", Comment = "最后登录时间")] + public DateTime? LastLogin { get; set; } + [LightColumn(Name = "MODIFY_DATE", Comment = "修改时间")] + public DateTime? ModifyTime { get; set; } + + [LightColumn(Name = "IS_LOCK", Comment = "是否锁定")] + public bool? IsLock { get; set; } + + + [LightNavigate(nameof(Id), nameof(City.Id))] + public City City { get; set; } + +} + +public class City +{ + public string Id { get; set; } + public string Name { get; set; } +} \ No newline at end of file diff --git a/test/BenchmarkTest/Program.cs b/test/BenchmarkTest/Program.cs new file mode 100644 index 00000000..2ca1d7ad --- /dev/null +++ b/test/BenchmarkTest/Program.cs @@ -0,0 +1,7 @@ +// See https://aka.ms/new-console-template for more information +using BenchmarkTest; +using LightORM.Providers.Sqlite.Extensions; + +Console.WriteLine("Hello, World!"); +BenchmarkDotNet.Running.BenchmarkRunner.Run(); + diff --git a/test/BenchmarkTest/SqlBuild.cs b/test/BenchmarkTest/SqlBuild.cs new file mode 100644 index 00000000..4f3e4e25 --- /dev/null +++ b/test/BenchmarkTest/SqlBuild.cs @@ -0,0 +1,190 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Jobs; +using BenchmarkTest.Models; +using LightORM; +using LightORM.Providers.Sqlite.Extensions; +using System; +using System.Collections.Generic; +using System.Text; + +namespace BenchmarkTest; + +[MemoryDiagnoser] +public class SqlBuild +{ + class Jobs + { + public string? Plate { get; set; } + public string? StnId { get; set; } + } + [GlobalSetup] + public void Setup() + { + ExpSqlFactory.Configuration(config => + { + config.UseSqlite("Data Source=:memory:;Version=3;New=True;"); + config.SetEnableExpressionCache(false); + }); + } + [Benchmark] + public string SelectCTE() + { + var Db = ExpSqlFactory.GetContext(); + // 从Jobs表中,选择Plate的第一个字符作为Fzjg字段,选择Fzjg和StnId,作为temp表,并命名为info + // With info as (...) + var info = Db.Select().AsTemp("info", j => new + { + Fzjg = j.Plate!.Substring(1, 2), + j.StnId + }); + // 从info表中,按StnId和Fzjg分组并且按Count(*)排序后,选择StnId,Fzjg,Count(*),RowNumer,作为temp表,并命名为stn_fzjg,表数据为每个StnId中,按Fzjg数据量进行排序并标记为Index + var stnFzjg = Db.FromTemp(info) + .GroupBy(a => new { a.StnId, a.Fzjg }) + .OrderByDesc(a => new { a.Group.StnId, i = a.Count() }) + .AsTemp("stn_fzjg", g => new + { + g.Group.StnId, + g.Group.Fzjg, + Count = g.Count(), + Index = WinFn.RowNumber().PartitionBy(g.Tables.StnId).OrderByDesc(g.Count()).Value() + }); + // 从info表中,按Fzjg分组并且按Count(*)排序后,选择StnId,Fzjg,Count(*),RowNumer,作为temp表,并命名为all_fzjg,表数据为所有Fzjg中,按每个Fzjg的数据量进行排序并标记为Index + var allFzjg = Db.FromTemp(info).GroupBy(a => new { a.Fzjg }) + .OrderByDesc(a => a.Count()) + .AsTemp("all_fzjg", g => new + { + StnId = "合计", + Fzjg = g.Group.Fzjg, + Count = g.Count(), + Index = WinFn.RowNumber().OrderByDesc(g.Count()).Value() + }); + // 从info表中,按StnId进行Group By Rollup ,选择StnId和分组数据量,作为temp表,并命名为all_station + var allStation = Db.FromTemp(info).GroupBy(t => new { t.StnId }) + .Rollup() + .AsTemp("all_station", g => new + { + StnId = SqlFn.NullThen(g.Group.StnId, "合计"), + Total = SqlFn.Count() + }); + /* + * 1. 从stn_fzjg中,筛选出所有前3的Fzjg数量,然后按StnId分组,选择StnId,组内第一Fzjg作为FirstFzjg,组内第一的Count作为FirstCount + * 2. 从all_fzjg中,筛选出所有前3的Fzjg数量,选择'合计'作为StnId,第一Fzjg作为FirstFzjg,第一的Count作为FirstCount + * 3. 将1和2的结果Union ALl + * 4. 转为子查询,inner join all_station + * 5. select结果列 + */ + var sql = Db.FromTemp(stnFzjg).Where(t => t.Index < 4) + .GroupBy(t => new { t.StnId }) + .AsTable(g => new + { + StnId = g.Group.StnId!, + FirstFzjg = g.Join(g.Tables.Index == 1 ? g.Tables.Fzjg.ToString() : "").Separator("").OrderBy(g.Tables.StnId).Value(), + FirstCount = g.Join(g.Tables.Index == 1 ? g.Tables.Count.ToString() : "").Separator("").OrderBy(g.Tables.StnId).Value() + }).UnionAll(Db.FromTemp(allFzjg).Where(t => t.Index < 4).AsTable(g => new + { + StnId = "合计", + FirstFzjg = SqlFn.Join(g.Index == 1 ? g.Fzjg.ToString() : "").Separator("").OrderBy(g.StnId).Value(), + FirstCount = SqlFn.Join(g.Index == 1 ? g.Count.ToString() : "").Separator("").OrderBy(g.StnId).Value() + })).AsSubQuery() + .InnerJoin(allStation, (t, a) => t.StnId == a.StnId) + .ToSql((t, a) => new + { + Jczmc = SqlFn.NullThen(t.StnId, "TT"), + a.Total, + t + }); + return sql; + } + + [Benchmark] + public string SelectCTEOptimized() + { + var Db = ExpSqlFactory.GetContext(); + // 从Jobs表中,选择Plate的第一个字符作为Fzjg字段,选择Fzjg和StnId,作为temp表,并命名为info + // With info as (...) + var info = Db.Select().AsTemp("info", j => new + { + Fzjg = j.Plate!.Substring(1, 2), + j.StnId + }); + // 从info表中,按StnId和Fzjg分组并且按Count(*)排序后,选择StnId,Fzjg,Count(*),RowNumer,作为temp表,并命名为stn_fzjg,表数据为每个StnId中,按Fzjg数据量进行排序并标记为Index + var stnFzjg = Db.FromTemp(info) + .GroupBy(a => new { a.StnId, a.Fzjg }) + .OrderByDesc(a => new { a.Group.StnId, i = a.Count() }) + .AsTemp("stn_fzjg", g => new + { + g.Group.StnId, + g.Group.Fzjg, + Count = g.Count(), + Index = WinFn.RowNumber().PartitionBy(g.Tables.StnId).OrderByDesc(g.Count()).Value() + }); + // 从info表中,按Fzjg分组并且按Count(*)排序后,选择StnId,Fzjg,Count(*),RowNumer,作为temp表,并命名为all_fzjg,表数据为所有Fzjg中,按每个Fzjg的数据量进行排序并标记为Index + var allFzjg = Db.FromTemp(info).GroupBy(a => a.Fzjg) + .OrderByDesc(a => a.Count()) + .AsTemp("all_fzjg", g => new + { + StnId = "合计", + Fzjg = g.Group, + Count = g.Count(), + Index = WinFn.RowNumber().OrderByDesc(g.Count()).Value() + }); + // 从info表中,按StnId进行Group By Rollup ,选择StnId和分组数据量,作为temp表,并命名为all_station + var allStation = Db.FromTemp(info).GroupBy(t => t.StnId) + .Rollup() + .AsTemp("all_station", g => new + { + StnId = SqlFn.NullThen(g.Group, "合计"), + Total = SqlFn.Count() + }); + /* + * 1. 从stn_fzjg中,筛选出所有前3的Fzjg数量,然后按StnId分组,选择StnId,组内第一Fzjg作为FirstFzjg,组内第一的Count作为FirstCount + * 2. 从all_fzjg中,筛选出所有前3的Fzjg数量,选择'合计'作为StnId,第一Fzjg作为FirstFzjg,第一的Count作为FirstCount + * 3. 将1和2的结果Union ALl + * 4. 转为子查询,inner join all_station + * 5. select结果列 + */ + var sql = Db.FromTemp(stnFzjg).Where(t => t.Index < 4) + .GroupBy(t => t.StnId) + .AsTable(g => new + { + StnId = g.Group!, + FirstFzjg = g.Join(g.Tables.Index == 1 ? g.Tables.Fzjg.ToString() : "").Separator("").OrderBy(g.Tables.StnId).Value(), + FirstCount = g.Join(g.Tables.Index == 1 ? g.Tables.Count.ToString() : "").Separator("").OrderBy(g.Tables.StnId).Value() + }).UnionAll(Db.FromTemp(allFzjg).Where(t => t.Index < 4).AsTable(g => new + { + StnId = "合计", + FirstFzjg = SqlFn.Join(g.Index == 1 ? g.Fzjg.ToString() : "").Separator("").OrderBy(g.StnId).Value(), + FirstCount = SqlFn.Join(g.Index == 1 ? g.Count.ToString() : "").Separator("").OrderBy(g.StnId).Value() + })).AsSubQuery() + .InnerJoin(allStation, (t, a) => t.StnId == a.StnId) + .ToSql((t, a) => new + { + Jczmc = SqlFn.NullThen(t.StnId, "TT"), + a.Total, + t + }); + return sql; + } + + [Benchmark] + public string SelectWithArrayAccess() + { + var Db = ExpSqlFactory.GetContext(); + int[] arr = [10, 9, 11, 2]; + int? i = 2; + var sql = Db.Select() + .Where(u => u.Age == arr[i.Value]) + .ToSql(); + return sql; + } + + [Benchmark] + public string SelectWithContainArray() + { + int?[] arr = [10, 11]; + var Db = ExpSqlFactory.GetContext(); + var select = Db.Select() + .Where(u => arr.Contains(u.Age)); + return select.ToSql(); + } +} diff --git a/test/LightORMTest/DbMethodTest.cs b/test/LightORMTest/DbMethodTest.cs index 70ad3dcd..55453e48 100644 --- a/test/LightORMTest/DbMethodTest.cs +++ b/test/LightORMTest/DbMethodTest.cs @@ -12,8 +12,10 @@ public class DbMethodTest : TestBase [TestMethod] public void TestToString() { - Expression> exp = u => u.LastLogin.Value.ToString("yyyy-MM-dd"); - var sql = exp.Resolve(SqlResolveOptions.Where, ResolveCtx); - Console.WriteLine(sql.SqlString); + var today = DateTime.Now; + Expression> exp = u => u.LastLogin!.Value.ToString("yyyy-MM-dd") == today.ToString("yyyy-MM-dd"); + var r1 = exp.Resolve(SqlResolveOptions.Where, ResolveCtx); + var r2 = exp.Resolve(SqlResolveOptions.Where, ResolveCtx); + Console.WriteLine(r1.SqlString); } } diff --git a/test/LightORMTest/ResolverTest/ValueResolveTest.cs b/test/LightORMTest/ResolverTest/ValueResolveTest.cs index 888e8cbe..eee98a22 100644 --- a/test/LightORMTest/ResolverTest/ValueResolveTest.cs +++ b/test/LightORMTest/ResolverTest/ValueResolveTest.cs @@ -1,6 +1,7 @@ using LightORM.Providers.Sqlite; using System; using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; using System.Text; @@ -53,4 +54,58 @@ static int GetIndex() return 3; } } + + [TestMethod] + public void ResolveCacheTest() + { + int[] arr = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]; + int? i = GetIndex(); + string s = "ad"; + var ii = new { index = 5 }; + + var context = new ResolveContext(CustomSqlite.Instance); + + Expression> withVariable = u => u.Age > arr[i.Value] && u.Age < arr[ii.index] && u.UserName.Contains(s); + var result1 = withVariable.Resolve(SqlResolveOptions.Where, context); + var result2 = withVariable.Resolve(SqlResolveOptions.Where, context); + Assert.IsNotNull(result1.DbParameters); + Assert.IsNotNull(result2.DbParameters); + Assert.AreEqual(7, result1.DbParameters[0].Value); + Assert.AreEqual(7, result2.DbParameters[0].Value); + Assert.HasCount(3, result1.DbParameters); + Assert.HasCount(3, result2.DbParameters); + + for (int j = 0; j < result1.DbParameters.Count; j++) + { + Assert.AreEqual(result1.DbParameters[j].Value, result2.DbParameters[j].Value); + Assert.AreEqual(result1.DbParameters[j].Name, result2.DbParameters[j].Name); + } + + Expression> noVariable = u => u.Age > 7 && u.Age < 18 && u.UserName.Contains("123"); + var result3 = noVariable.Resolve(SqlResolveOptions.Where, context); + var result4 = noVariable.Resolve(SqlResolveOptions.Where, context); + Assert.IsNull(result3.DbParameters); + Assert.IsNull(result4.DbParameters); + static int GetIndex() + { + return 3; + } + } + + [TestMethod] + public void ResolveCacheArrayContain() + { + int[] arr = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]; + var context = new ResolveContext(CustomSqlite.Instance); + Expression> inArray = u => arr.Contains(u.Age!.Value); + var result1 = inArray.Resolve(SqlResolveOptions.Where, context); + var result2 = inArray.Resolve(SqlResolveOptions.Where, context); + Assert.IsNotNull(result1.DbParameters); + Assert.IsNotNull(result2.DbParameters); + for (int j = 0; j < result1.DbParameters.Count; j++) + { + Assert.AreEqual(result1.DbParameters[j].Value, result2.DbParameters[j].Value); + Assert.AreEqual(result1.DbParameters[j].Name, result2.DbParameters[j].Name); + } + } } diff --git a/test/LightORMTest/ResultTest/Select.cs b/test/LightORMTest/ResultTest/Select.cs index 4d578686..d2240ea9 100644 --- a/test/LightORMTest/ResultTest/Select.cs +++ b/test/LightORMTest/ResultTest/Select.cs @@ -225,7 +225,7 @@ await DoSomethingWithTempUserDataAsync(Db, async () => { var dto = await Db.Select() .Where(u => u.UserId == "test04") - .ToListAsync(u => new { u.UserId, u.Sign }); + .ToListAsync(u => new() { UserId = u.UserId, Sign = u.Sign }); Assert.HasCount(1, dto); Assert.AreEqual(SignType.Svip, dto[0].Sign); }); diff --git a/test/LightORMTest/SqlGenerate/SelectSql.cs b/test/LightORMTest/SqlGenerate/SelectSql.cs index e30abd26..f167fe5e 100644 --- a/test/LightORMTest/SqlGenerate/SelectSql.cs +++ b/test/LightORMTest/SqlGenerate/SelectSql.cs @@ -406,21 +406,21 @@ public void Select_Temp_ThenGroup_And_Union() Index = WinFn.RowNumber().PartitionBy(g.Tables.StnId).OrderByDesc(g.Count()).Value() }); // 从info表中,按Fzjg分组并且按Count(*)排序后,选择StnId,Fzjg,Count(*),RowNumer,作为temp表,并命名为all_fzjg,表数据为所有Fzjg中,按每个Fzjg的数据量进行排序并标记为Index - var allFzjg = Db.FromTemp(info).GroupBy(a => new { a.Fzjg }) + var allFzjg = Db.FromTemp(info).GroupBy(a => a.Fzjg) .OrderByDesc(a => a.Count()) .AsTemp("all_fzjg", g => new { StnId = "合计", - g.Group.Fzjg, + Fzjg = g.Group, Count = g.Count(), Index = WinFn.RowNumber().OrderByDesc(g.Count()).Value() }); // 从info表中,按StnId进行Group By Rollup ,选择StnId和分组数据量,作为temp表,并命名为all_station - var allStation = Db.FromTemp(info).GroupBy(t => new { t.StnId }) + var allStation = Db.FromTemp(info).GroupBy(t => t.StnId) .Rollup() .AsTemp("all_station", g => new { - StnId = SqlFn.NullThen(g.Group.StnId, "合计"), + StnId = SqlFn.NullThen(g.Group, "合计"), Total = SqlFn.Count() }); /* @@ -431,10 +431,10 @@ public void Select_Temp_ThenGroup_And_Union() * 5. select结果列 */ var sql = Db.FromTemp(stnFzjg).Where(t => t.Index < 4) - .GroupBy(t => new { t.StnId }) + .GroupBy(t => t.StnId) .AsTable(g => new { - StnId = g.Group.StnId!, + StnId = g.Group!, FirstFzjg = g.Join(g.Tables.Index == 1 ? g.Tables.Fzjg.ToString() : "").Separator("").OrderBy(g.Tables.StnId).Value(), FirstCount = g.Join(g.Tables.Index == 1 ? g.Tables.Count.ToString() : "").Separator("").OrderBy(g.Tables.StnId).Value() }).UnionAll(Db.FromTemp(allFzjg).Where(t => t.Index < 4).AsTable(g => new @@ -537,11 +537,20 @@ int GetIndex() public void TestArrayContain() { int?[] arr = [10, 11]; - var select = Db.Select() + var r1 = Run(); + arr = [9, 1, 3]; + var r2 = Run(); + Console.WriteLine(r1.Item1); + Console.WriteLine(r2.Item1); + return; + (string, Dictionary?) Run() + { + var select = Db.Select() .Where(u => arr.Contains(u.Age)); - var sql = select.ToSql(); - var ps = select.SqlBuilder.DbParameters; - Console.WriteLine(sql); + var sql = select.ToSql(); + var ps = select.SqlBuilder.DbParameters; + return (sql, ps); + } } [TestMethod]