From 98b6fb130dc1e9933a1bf965e93e06f119ca974b Mon Sep 17 00:00:00 2001 From: MarvelTiter_yaoqinglin Date: Fri, 30 Jan 2026 16:56:22 +0800 Subject: [PATCH 1/2] =?UTF-8?q?BUG=E4=BF=AE=E5=A4=8D=E4=B8=8E=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...10\346\234\254\346\227\245\345\277\227.md" | 3 +- src/LightORM/Builder/UpdateBuilder.cs | 4 +- .../Extension/CustomDatabaseExtensions.cs | 23 ++- src/LightORM/Interfaces/ExpSql/IExpSelect.cs | 12 +- src/LightORM/Interfaces/ExpSql/IExpUpdate.cs | 4 +- .../Providers/Select/SelectProvider1.cs | 4 +- .../Providers/Select/SelectProvider2.cs | 8 +- src/LightORM/Providers/UpdateProvider.cs | 24 +-- src/LightORM/Utils/ExpressionResolver.cs | 138 +++++++++++----- .../SelectInterfacesGenerator.cs | 9 +- .../SelectProvidersGenerator.cs | 8 +- test/LightORMTest.SqlServer/DbMethodTest.cs | 155 ------------------ test/LightORMTest/SqlGenerate/SelectSql.cs | 10 +- test/LightORMTest/SqlGenerate/UpdateSql.cs | 10 ++ test/TestWeb/Components/Pages/Home.razor | 11 +- test/TestWeb/Program.cs | 3 +- test/TestWeb/SqlTrace.cs | 17 ++ test/test.db | 0 18 files changed, 198 insertions(+), 245 deletions(-) create mode 100644 test/TestWeb/SqlTrace.cs create mode 100644 test/test.db 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 bdf7889a..6eeb524e 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" @@ -3,7 +3,8 @@ ## v3.1.7.9 - 🐞当条件直接写 xxx == null 时,生成的SQL错误的BUG - 🐞修复批量更新时,更新指定列丢失主键列的BUG,同时更新指定列时,如果有提供指定值,将使用指定值而不是对象的值 -- 🐞修改Select时的别名使用,总是使用AS +- 🐞修改Select时的别名使用,总是使用AS(`select *`除外) +- ⚡️更新操作时,SetNull支持多字段设置(使用`new {...}`) - ⚡️修改接口的签名,将Func<.., object>修改成泛型形式,使用与原来一样,但是在解析表达式时可以带来一些好处 ```csharp IExpUpdate UpdateColumns(Expression> columns); diff --git a/src/LightORM/Builder/UpdateBuilder.cs b/src/LightORM/Builder/UpdateBuilder.cs index 32935736..78bfc16c 100644 --- a/src/LightORM/Builder/UpdateBuilder.cs +++ b/src/LightORM/Builder/UpdateBuilder.cs @@ -34,13 +34,13 @@ protected override void HandleResult(ICustomDatabase database, ExpressionInfo ex } else if (expInfo.AdditionalParameter is UpdateValue v) { - var member = result.Members!.First(); if (v.Value is null) { - SetNullMembers.Add(member); + SetNullMembers.AddRange(result.Members!); } else { + var member = result.Members!.First(); Members.Add(member); DbParameters.Add(member, v.Value); } diff --git a/src/LightORM/Extension/CustomDatabaseExtensions.cs b/src/LightORM/Extension/CustomDatabaseExtensions.cs index 3690e0b7..b525ff00 100644 --- a/src/LightORM/Extension/CustomDatabaseExtensions.cs +++ b/src/LightORM/Extension/CustomDatabaseExtensions.cs @@ -1,4 +1,6 @@ -namespace LightORM.Extension; +using System.Text; + +namespace LightORM.Extension; internal static class CustomDatabaseExtensions { @@ -13,6 +15,25 @@ public static string AttachEmphasis(this ICustomDatabase database, string name) return name; } + public static StringBuilder AppendEmphasis(this StringBuilder sql, string name, ICustomDatabase database) + { + if (database.Emphasis.Length != 2) + { + throw new LightOrmException("Emphasis must be exactly 2 characters, e.g., \"[]\" or \"``\"."); + } + if (database.UseIdentifierQuote || database.IsKeyWord(name)) + { + sql.Append(database.Emphasis[0]); + sql.Append(name); + sql.Append(database.Emphasis[1]); + } + else + { + sql.Append(name); + } + return sql; + } + public static ICustomDatabase GetDbCustom(this DbBaseType type) { if (!ExpressionSqlOptions.Instance.Value.CustomDatabases.TryGetValue(type.Name, out var custom)) diff --git a/src/LightORM/Interfaces/ExpSql/IExpSelect.cs b/src/LightORM/Interfaces/ExpSql/IExpSelect.cs index 6a6a0964..2380f61b 100644 --- a/src/LightORM/Interfaces/ExpSql/IExpSelect.cs +++ b/src/LightORM/Interfaces/ExpSql/IExpSelect.cs @@ -68,8 +68,8 @@ public interface IExpSelect0 : IExpSelect where TSelect : IExpS public interface IExpSelect : IExpSelect0, T1> { - IExpSelect OrderBy(Expression> exp); - IExpSelect OrderByDesc(Expression> exp); + IExpSelect OrderBy(Expression> exp); + IExpSelect OrderByDesc(Expression> exp); IExpSelectGroup GroupBy(Expression> exp); #region join methods @@ -207,8 +207,8 @@ public interface IExpTemp : IExpTemp public interface IExpSelect : IExpSelect0, T1> { - IExpSelect OrderBy(Expression> exp); - IExpSelect OrderByDesc(Expression> exp); + IExpSelect OrderBy(Expression> exp); + IExpSelect OrderByDesc(Expression> exp); IExpSelect Where(Expression> exp); @@ -272,8 +272,8 @@ public interface IExpSelect : IExpSelect0, T1> #region TypeSet - IExpSelect OrderBy(Expression, object>> exp); - IExpSelect OrderByDesc(Expression, object>> exp); + IExpSelect OrderBy(Expression, TOrder>> exp); + IExpSelect OrderByDesc(Expression, TOrder>> exp); IExpSelect Where(Expression, bool>> exp); diff --git a/src/LightORM/Interfaces/ExpSql/IExpUpdate.cs b/src/LightORM/Interfaces/ExpSql/IExpUpdate.cs index 0f711dea..3db50911 100644 --- a/src/LightORM/Interfaces/ExpSql/IExpUpdate.cs +++ b/src/LightORM/Interfaces/ExpSql/IExpUpdate.cs @@ -6,7 +6,7 @@ public interface IExpUpdate : ISql, T> IExpUpdate UpdateColumns(Expression> columns); IExpUpdate IgnoreColumns(Expression> columns); IExpUpdate Set(Expression> exp, TField value); - IExpUpdate SetNull(Expression> exp); + IExpUpdate SetNull(Expression> exp); IExpUpdate SetIf(bool condition, Expression> exp, TField value); - IExpUpdate SetNullIf(bool condition, Expression> exp); + IExpUpdate SetNullIf(bool condition, Expression> exp); } diff --git a/src/LightORM/Providers/Select/SelectProvider1.cs b/src/LightORM/Providers/Select/SelectProvider1.cs index d61dbdb4..edcfe162 100644 --- a/src/LightORM/Providers/Select/SelectProvider1.cs +++ b/src/LightORM/Providers/Select/SelectProvider1.cs @@ -53,13 +53,13 @@ public IExpSelectGroup GroupBy(Expression> return this.GroupByHandle(exp); } - public IExpSelect OrderBy(Expression> exp) + public IExpSelect OrderBy(Expression> exp) { this.OrderByHandle(exp, true); return this; } - public IExpSelect OrderByDesc(Expression> exp) + public IExpSelect OrderByDesc(Expression> exp) { this.OrderByHandle(exp, false); return this; diff --git a/src/LightORM/Providers/Select/SelectProvider2.cs b/src/LightORM/Providers/Select/SelectProvider2.cs index d6ac28eb..040f473d 100644 --- a/src/LightORM/Providers/Select/SelectProvider2.cs +++ b/src/LightORM/Providers/Select/SelectProvider2.cs @@ -20,13 +20,13 @@ public IExpSelectGroup> GroupBy(Expression>(exp); } - public IExpSelect OrderBy(Expression> exp) + public IExpSelect OrderBy(Expression> exp) { this.OrderByHandle(exp, true); return this; } - public IExpSelect OrderByDesc(Expression> exp) + public IExpSelect OrderByDesc(Expression> exp) { this.OrderByHandle(exp, false); return this; @@ -220,14 +220,14 @@ public IExpSelectGroup> GroupBy(Expression>(flatExp); } - public IExpSelect OrderBy(Expression, object>> exp) + public IExpSelect OrderBy(Expression, TOrder>> exp) { var flatExp = FlatTypeSet.Default.Flat(exp)!; this.OrderByHandle(flatExp, true); return this; } - public IExpSelect OrderByDesc(Expression, object>> exp) + public IExpSelect OrderByDesc(Expression, TOrder>> exp) { var flatExp = FlatTypeSet.Default.Flat(exp)!; this.OrderByHandle(flatExp, false); diff --git a/src/LightORM/Providers/UpdateProvider.cs b/src/LightORM/Providers/UpdateProvider.cs index 2a40b19e..5361329e 100644 --- a/src/LightORM/Providers/UpdateProvider.cs +++ b/src/LightORM/Providers/UpdateProvider.cs @@ -106,11 +106,8 @@ public async Task ExecuteAsync(CancellationToken cancellationToken = defaul } } - public IExpUpdate SetNull(Expression> exp) + public IExpUpdate SetNull(Expression> exp) { - //var result = exp.Resolve(SqlResolveOptions.Update, SqlBuilder.MainTable); - //var member = result.Members!.First(); - //SqlBuilder.SetNullMembers.Add(member); sqlBuilder.Expressions.Add(new ExpressionInfo() { Expression = exp, @@ -120,7 +117,7 @@ public IExpUpdate SetNull(Expression> exp) return this; } - public IExpUpdate SetNullIf(bool condition, Expression> exp) + public IExpUpdate SetNullIf(bool condition, Expression> exp) { if (condition) { @@ -131,19 +128,10 @@ public IExpUpdate SetNullIf(bool condition, Expression Set(Expression> exp, TField value) { - - //var result = exp.Resolve(SqlResolveOptions.Update, SqlBuilder.MainTable); - //var member = result.Members!.First(); - //if (value is null) - //{ - // SqlBuilder.SetNullMembers.Add(member); - //} - //else - //{ - // SqlBuilder.Members.Add(member); - // SqlBuilder.DbParameters.Add(member, value!); - //} - + if (exp.Body.NodeType == ExpressionType.New || exp.Body.NodeType == ExpressionType.MemberInit) + { + throw new LightOrmException("不支持多字段设置"); + } sqlBuilder.Expressions.Add(new ExpressionInfo() { Expression = exp, diff --git a/src/LightORM/Utils/ExpressionResolver.cs b/src/LightORM/Utils/ExpressionResolver.cs index 95831075..134c6fd9 100644 --- a/src/LightORM/Utils/ExpressionResolver.cs +++ b/src/LightORM/Utils/ExpressionResolver.cs @@ -1,9 +1,10 @@ using LightORM.Extension; -using System.Reflection; -using System.Text; using System.Collections; -using System.Diagnostics; using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; namespace LightORM; internal static class ExpressionExtensions @@ -89,25 +90,27 @@ internal class ExpressionResolver(SqlResolveOptions options, ResolveContext cont _ => null }; } + + private const string AS_LITERAL = " AS "; Expression? bodyExpression; ReadOnlyCollection? parametersExpression; int parameterPositionIndex = 0; bool resolveNullValue; //string? lastResolvedColumnName; - //bool useAs; - //bool UseAs - //{ - // get - // { - // if (useAs) - // { - // useAs = false; - // return true && Options.UseColumnAlias; - // } - // return useAs && Options.UseColumnAlias; - // } - // set => useAs = value; - //} + bool useAs = true; + bool UseAs + { + get + { + if (!useAs) + { + useAs = true; + return false; + } + return useAs && Options.UseColumnAlias; + } + set => useAs = value; + } bool ResolveNullValue { @@ -155,7 +158,7 @@ bool ResolveNullValue // 数组访问 if (exp.NodeType == ExpressionType.ArrayIndex) { - var index = Convert.ToInt32(Expression.Lambda(exp.Right).Compile().DynamicInvoke()); + var index = ExtractConstantInt(exp.Right); var array = Expression.Lambda(exp.Left).Compile().DynamicInvoke() as Array; var arrayValue = array!.GetValue(index); //var parameterName = AddDbParameter("Const", ); @@ -182,6 +185,50 @@ bool ResolveNullValue } return null; + + // 尝试将表达式求值为常量(仅支持 Constant 和 简单 Member 访问) + static int ExtractConstantInt(Expression expression) + { + if (expression is ConstantExpression ce && ce.Value is int 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); + + // 4. 转为 int + return value switch + { + int i => i, + long l when l >= int.MinValue && l <= int.MaxValue => (int)l, + short s => s, + byte b => b, + _ => throw new LightOrmException($"索引值必须是整数类型,实际类型: {value?.GetType()}") + }; + } } Expression? VisitConditional(ConditionalExpression exp) @@ -241,10 +288,8 @@ bool ResolveNullValue if (Options.SqlType == SqlPartial.Select) { Visit(arg); - //if (member.Name != lastResolvedColumnName || UseAs) - //{ - //} - Sql.Append($" AS {Database.AttachEmphasis(member.Name)}"); + if (UseAs) + Sql.Append($" AS {Database.AttachEmphasis(member.Name)}"); } else if (Options.SqlType == SqlPartial.Insert) { @@ -282,7 +327,7 @@ bool ResolveNullValue { var alias = Context.GetTable(exp).Alias; Sql.Append($"{alias}.*"); - //UseAs = false; + UseAs = false; //foreach (var item in alias.Columns) //{ // var prop = Expression.Property(exp, item.PropertyName); @@ -342,10 +387,13 @@ bool ResolveNullValue if (Options.SqlType == SqlPartial.Select) { Visit(memberAssign.Expression); - //if (lastResolvedColumnName != memberAssign.Member.Name || UseAs) - //{ - //} - Sql.Append($" AS {Database.AttachEmphasis(memberAssign.Member.Name)}"); + + if (UseAs) + { + Sql.Append(AS_LITERAL); + } + Sql.AppendEmphasis(memberAssign.Member.Name, Database); + if (i + 1 < exp.Bindings.Count) { Sql.Append(", "); @@ -565,6 +613,16 @@ public static string FormatDbParameterName(ResolveContext? context, SqlResolveOp return compilerVar; } + public static object? GetValue(Stack memberInfos, object? compilerVar) + { + while (memberInfos.Count > 0) + { + var item = memberInfos.Pop(); + compilerVar = GetValue(item, compilerVar); + } + return compilerVar; + } + /// /// 获取值 /// @@ -593,20 +651,18 @@ public static string GetValueName(Stack memberInfos) /// public static object? GetValue(MemberInfo memberInfo, object? obj) { - if (obj == null) - { - return null; - } - if (memberInfo.MemberType == MemberTypes.Property) - { - var propertyInfo = memberInfo as PropertyInfo; - return propertyInfo!.GetValue(obj); - } - else if (memberInfo.MemberType == MemberTypes.Field) + return memberInfo switch { - var fieldInfo = memberInfo as FieldInfo; - return fieldInfo!.GetValue(obj); - } - return new NotSupportedException($"不支持获取 {memberInfo.MemberType} 类型值."); + PropertyInfo prop => prop.GetValue(obj), + FieldInfo field => field.GetValue(obj), + //PropertyInfo prop when obj is not null => prop.GetValue(obj), + //PropertyInfo staticProp when obj is null => staticProp.GetValue(null), + //FieldInfo field when obj is not null => field.GetValue(obj), + //FieldInfo staticField when obj is null => staticField.GetValue(null), + _ => throw new NotSupportedException($"不支持获取 {memberInfo.MemberType} 类型值.") + }; } + + + } diff --git a/src/LightOrmExtensionGenerator/SelectInterfacesGenerator.cs b/src/LightOrmExtensionGenerator/SelectInterfacesGenerator.cs index d5859c94..46f6a1cb 100644 --- a/src/LightOrmExtensionGenerator/SelectInterfacesGenerator.cs +++ b/src/LightOrmExtensionGenerator/SelectInterfacesGenerator.cs @@ -77,10 +77,8 @@ public override string Handler(AttributeData data) public interface IExpSelect<{{argsStr}}> : IExpSelect0, T1> { - IExpSelect<{{argsStr}}> OrderBy(Expression> exp); - IExpSelect<{{argsStr}}> OrderBy(Expression, object>> exp); - - IExpSelect<{{argsStr}}> OrderByDesc(Expression> exp); + IExpSelect<{{argsStr}}> OrderBy(Expression> exp); + IExpSelect<{{argsStr}}> OrderByDesc(Expression> exp); IExpSelectGroup> GroupBy(Expression> exp); @@ -123,7 +121,8 @@ public interface IExpSelect<{{argsStr}}> : IExpSelect0, #region TypeSet {{typeSetJoin}} - IExpSelect<{{argsStr}}> OrderByDesc(Expression, object>> exp); + IExpSelect<{{argsStr}}> OrderBy(Expression, TOrder>> exp); + IExpSelect<{{argsStr}}> OrderByDesc(Expression, TOrder>> exp); IExpSelectGroup> GroupBy(Expression, TGroup>> exp); IEnumerable ToList(Expression, TReturn>> exp); IEnumerable ToList(Expression, object>> exp); diff --git a/src/LightOrmExtensionGenerator/SelectProvidersGenerator.cs b/src/LightOrmExtensionGenerator/SelectProvidersGenerator.cs index 7bee7f43..ff61bbe6 100644 --- a/src/LightOrmExtensionGenerator/SelectProvidersGenerator.cs +++ b/src/LightOrmExtensionGenerator/SelectProvidersGenerator.cs @@ -216,13 +216,13 @@ internal sealed class SelectProvider{{count}}<{{argsStr}}> : SelectProvider0>(exp); } - public IExpSelect<{{argsStr}}> OrderBy(Expression> exp) + public IExpSelect<{{argsStr}}> OrderBy(Expression> exp) { this.OrderByHandle(exp, true); return this; } - public IExpSelect<{{argsStr}}> OrderByDesc(Expression> exp) + public IExpSelect<{{argsStr}}> OrderByDesc(Expression> exp) { this.OrderByHandle(exp, false); return this; @@ -302,14 +302,14 @@ public string ToSql(Expression> exp) return this.GroupByHandle>(flatExp); } - public IExpSelect<{{argsStr}}> OrderBy(Expression, object>> exp) + public IExpSelect<{{argsStr}}> OrderBy(Expression, TOrder>> exp) { var flatExp = FlatTypeSet.Default.Flat(exp)!; this.OrderByHandle(flatExp, true); return this; } - public IExpSelect<{{argsStr}}> OrderByDesc(Expression, object>> exp) + public IExpSelect<{{argsStr}}> OrderByDesc(Expression, TOrder>> exp) { var flatExp = FlatTypeSet.Default.Flat(exp)!; this.OrderByHandle(flatExp, false); diff --git a/test/LightORMTest.SqlServer/DbMethodTest.cs b/test/LightORMTest.SqlServer/DbMethodTest.cs index 953308af..73bbf307 100644 --- a/test/LightORMTest.SqlServer/DbMethodTest.cs +++ b/test/LightORMTest.SqlServer/DbMethodTest.cs @@ -13,159 +13,4 @@ protected override void Configura(IExpressionContextSetup option) option.UseSqlServer(LightORM.Providers.SqlServer.SqlServerVersion.V1, ConnectString.Value); option.UseInterceptor(); } - - [TestMethod] - public void CCC() - { - var sql = Db.Select() - .InnerJoin((v, i) => v.InspectorID == i.InspectorID) - .Where(i => i.Season == 3) - .GroupBy(g => new { g.Tb2.StationID, g.Tb2.StationName }) - .OrderByDesc(g => g.Average(g.Tables.Tb1.Scores)) - //.AsTable(g => new {Name = g.Group.StationName , Scores = g.Average(g.Tables.Tb1.Scores)}) - .ToSql(g => new RRR() - { - Name = g.Group.StationName, - Scores = g.Average(g.Tables.Tb1.Scores) - }); - Console.WriteLine(sql); - } - - [TestMethod] - public void CCCC() - { - var sql = Db.Update([.. GetList()]) - .UpdateColumns(i => i.Line) - .Set(i => i.Status, 1) - .ToSql(); - Console.WriteLine(sql); - - List GetList() - { - return new List - { - new() , - new() , - new() , - new() , - new() , - new() , - }; - } - } -} -public class RRR -{ - public string? Name { get; set; } - public double Scores { get; set; } -} -[LightTable(Name = "VmasRatingData")] -public class VmasRatingData -{ - - /// - /// - /// - [LightColumn(Name = "DetectID")] - public int DetectID { get; set; } - - - /// - /// - /// - [LightColumn(Name = "InspectorID")] - public int InspectorID { get; set; } - - - /// - /// - /// - [LightColumn(Name = "BeginTime")] - public DateTime BeginTime { get; set; } - - - /// - /// - /// - [LightColumn(Name = "EndTime")] - public DateTime EndTime { get; set; } - - - /// - /// - /// - [LightColumn(Name = "Status")] - public int Status { get; set; } - - [LightColumn(Name = "Scores")] - public double Scores { get; set; } - - /// - /// 比赛编号 - /// - [LightColumn(Name = "Season")] - public int Season { get; set; } - - [LightColumn(Name = "StationId")] - public string? StationId { get; set; } - - [LightColumn(Name = "StationName")] - public string? StationName { get; set; } - - [LightColumn(Name = "District")] - public string? District { get; set; } -} - -[LightTable(Name = "Inspectotrs")] -public class Inspectotrs -{ - - /// - /// - /// - [LightColumn(Name = "InspectorID", PrimaryKey = true, AutoIncrement = true)] - public int InspectorID { get; set; } - - - /// - /// - /// - [LightColumn(Name = "InspectorName")] - public string InspectorName { get; set; } - - - /// - /// - /// - [LightColumn(Name = "StationID")] - public string StationID { get; set; } - - - /// - /// - /// - [LightColumn(Name = "StationName")] - public string StationName { get; set; } - - - /// - /// 0 - 未开始(初始状态) - /// 1 - 待开始(分配了检测线) - /// 2 - 已开始(上线检测中) - /// 3 - 已完成(完成所有检测) - /// 4 - 归档 - /// - [LightColumn(Name = "Status")] - public int Status { get; set; } - - - /// - /// - /// - [LightColumn(Name = "Line")] - public int? Line { get; set; } - - [LightColumn(Name = "District")] - public string District { get; set; } - } \ No newline at end of file diff --git a/test/LightORMTest/SqlGenerate/SelectSql.cs b/test/LightORMTest/SqlGenerate/SelectSql.cs index bca57091..ad412708 100644 --- a/test/LightORMTest/SqlGenerate/SelectSql.cs +++ b/test/LightORMTest/SqlGenerate/SelectSql.cs @@ -522,15 +522,21 @@ public void TestBooleanValue() .ToSql(); Console.WriteLine(sql); } - + static int ArrIndex = 10; [TestMethod] public void TestArrayAccess() { int[] arr = [10]; + int? i = GetIndex(); + var ii = new { index = 5 }; var sql = Db.Select() - .Where(u => u.Age == arr[0]) + .Where(u => u.Age == arr[i.Value]) .ToSql(); Console.WriteLine(sql); + int GetIndex() + { + return 3; + } } [TestMethod] diff --git a/test/LightORMTest/SqlGenerate/UpdateSql.cs b/test/LightORMTest/SqlGenerate/UpdateSql.cs index 2b4797e7..6e5d0108 100644 --- a/test/LightORMTest/SqlGenerate/UpdateSql.cs +++ b/test/LightORMTest/SqlGenerate/UpdateSql.cs @@ -161,4 +161,14 @@ List GetList() }; } } + + [TestMethod] + public void Update_SetNull() + { + var sql = Db.Update() + .SetNull(t => new { t.Sign, t.Age }) + .Where(u => u.Id == 10) + .ToSql(); + Console.WriteLine(sql); + } } diff --git a/test/TestWeb/Components/Pages/Home.razor b/test/TestWeb/Components/Pages/Home.razor index 302012ba..1cc20764 100644 --- a/test/TestWeb/Components/Pages/Home.razor +++ b/test/TestWeb/Components/Pages/Home.razor @@ -1,7 +1,16 @@ @page "/" @inject Services1 S1 +@inject Ctx ctx Home - +

Hello, world!

Welcome to your new app. + +@code { + private async Task OnClick() + { + ctx.Id = 10; + await S1.Query(); + } +} \ No newline at end of file diff --git a/test/TestWeb/Program.cs b/test/TestWeb/Program.cs index 1d5fd27b..bc0003bc 100644 --- a/test/TestWeb/Program.cs +++ b/test/TestWeb/Program.cs @@ -9,12 +9,13 @@ // Add services to the container. builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); - +builder.Services.AddScoped(); builder.Services.AddLightOrm(option => { var path = Path.GetFullPath("../test.db"); //option.SetDatabase(DbBaseType.Sqlite, "DataSource=" + path, SQLiteFactory.Instance); option.UseSqlite("DataSource=" + path); + option.UseInterceptor(); option.UseOracle(option => { option.DbKey = "Oracle"; diff --git a/test/TestWeb/SqlTrace.cs b/test/TestWeb/SqlTrace.cs new file mode 100644 index 00000000..3d44e23d --- /dev/null +++ b/test/TestWeb/SqlTrace.cs @@ -0,0 +1,17 @@ +using LightORM.Implements; +using LightORM.Models; + +namespace TestWeb +{ + public class Ctx + { + public int Id { get; set; } + } + public class SqlTrace(Ctx ctx, ILogger logger) : AdoInterceptorBase + { + public override void BeforeExecute(SqlExecuteContext context) + { + logger.LogInformation("ctx => {id} : {Sql}", ctx.Id, context.Sql); + } + } +} diff --git a/test/test.db b/test/test.db new file mode 100644 index 00000000..e69de29b From 5e87f9365a8d871fb43365d0a3b5db3c208b3441 Mon Sep 17 00:00:00 2001 From: MarvelTiter_yaoqinglin Date: Sun, 1 Feb 2026 10:28:52 +0800 Subject: [PATCH 2/2] =?UTF-8?q?BUG=E4=BF=AE=E5=A4=8D=E5=92=8C=E6=80=A7?= =?UTF-8?q?=E8=83=BD=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...10\346\234\254\346\227\245\345\277\227.md" | 15 +- src/LightORM/Builder/DeleteBuilder.cs | 146 +++- src/LightORM/Builder/InsertBuilder.cs | 82 ++- src/LightORM/Builder/SelectBuilder.cs | 626 ++++++++++-------- src/LightORM/Builder/SqlBuilder.cs | 2 + src/LightORM/Builder/UpdateBuilder.cs | 109 ++- src/LightORM/Cache/DbParameterReader.cs | 51 +- src/LightORM/Cache/TableContext.cs | 1 + .../DbEntity/Attributes/TableNameAttribute.cs | 1 + .../ExpressionSql/ExpressionCoreSqlBase.cs | 18 +- src/LightORM/Extension/BatchActionHelper.cs | 6 +- .../Extension/BatchSqlInfoExtensions.cs | 1 + .../Extension/CustomDatabaseExtensions.cs | 44 ++ src/LightORM/Extension/HashSetExtensions.cs | 13 +- .../Implements/BaseSqlMethodResolver.cs | 34 +- src/LightORM/Interfaces/ITableEntityInfo.cs | 1 + src/LightORM/Models/BatchSqlInfo.cs | 4 +- src/LightORM/Models/TableEntity.cs | 1 + src/LightORM/Models/TableInfo.cs | 1 + src/LightORM/Providers/DeleteProvider.cs | 78 ++- src/LightORM/Providers/InsertProvider.cs | 30 +- src/LightORM/Providers/UpdateProvider.cs | 30 +- src/LightORM/Utils/DictionaryHelper.cs | 1 + src/LightORM/Utils/ExpressionResolver.cs | 102 +-- src/LightORM/Versions.props | 2 +- .../TableContextGenerator.cs | 3 +- .../DamengMethodResolver.cs | 38 +- .../LightORM.Providers.Dameng/Versions.props | 2 +- .../MySqlMethodResolver.cs | 39 +- .../LightORM.Providers.MySql/Versions.props | 2 +- .../OracleMethodResolver.cs | 37 +- .../LightORM.Providers.Oracle/Versions.props | 2 +- .../PostgreSQLMethodResolver.cs | 37 +- .../Versions.props | 2 +- .../SqlServerMethodResolver.cs | 37 +- .../Versions.props | 2 +- .../SqliteMethodResolver.cs | 37 +- .../LightORM.Providers.Sqlite/Versions.props | 2 +- test/LightORMTest.PostgreSQL/SelectResult.cs | 17 + test/LightORMTest/Models/User.cs | 2 +- test/LightORMTest/Models/UserFlat.cs | 3 + test/LightORMTest/ResultTest/Select.cs | 114 +++- test/LightORMTest/SqlGenerate/InsertSql.cs | 7 + test/LightORMTest/SqlGenerate/SelectSql.cs | 10 +- test/LightORMTest/SqlGenerate/UpdateSql.cs | 5 +- test/LightORMTest/TestBase.cs | 26 +- 46 files changed, 1171 insertions(+), 652 deletions(-) create mode 100644 test/LightORMTest.PostgreSQL/SelectResult.cs 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 6eeb524e..bdd18321 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,6 +1,7 @@ # 版本功能更新记录 -## v3.1.7.9 +## v3.2.0 +- 🛠SqlBuilder大量字符串拼接的优化 - 🐞当条件直接写 xxx == null 时,生成的SQL错误的BUG - 🐞修复批量更新时,更新指定列丢失主键列的BUG,同时更新指定列时,如果有提供指定值,将使用指定值而不是对象的值 - 🐞修改Select时的别名使用,总是使用AS(`select *`除外) @@ -10,7 +11,19 @@ IExpUpdate UpdateColumns(Expression> columns); => IExpUpdate UpdateColumns(Expression> columns); + ``` +- 🐞修复方法解析时遇到的隐式转换`op_Implicit` +- 🐞修复处理Array/Collection的Contains解析 + + | Provider | 版本要求 | + |---------------------------------|-------| + | `LightORM.Providers.Dameng` | >=0.0.6 | + | `LightORM.Providers.MySql` | >=0.1.4 | + | `LightORM.Providers.Oracle` | >=0.1.5 | + | `LightORM.Providers.PostgreSQL` | >=0.0.9 | + | `LightORM.Providers.Sqlite` | >=0.1.4 | + | `LightORM.Providers.SqlServer` | >=0.1.5 | ## v3.1.7.8 - ⚡️升级`.NET 10` diff --git a/src/LightORM/Builder/DeleteBuilder.cs b/src/LightORM/Builder/DeleteBuilder.cs index 81823b9e..2bd4a2df 100644 --- a/src/LightORM/Builder/DeleteBuilder.cs +++ b/src/LightORM/Builder/DeleteBuilder.cs @@ -2,14 +2,16 @@ using System.Text; namespace LightORM.Builder; + internal record DeleteBuilder : SqlBuilder { public new T? TargetObject { get; set; } - public IEnumerable TargetObjects { get; set; } = []; + public T[] TargetObjects { get; set; } = []; private bool batchDone = false; public bool IsBatchDelete { get; set; } - public bool ForceDelete { get; set; } + public bool FullDelete { get; set; } public bool Truncate { get; set; } + HashSet Members { get; set; } = []; public List? BatchInfos { get; set; } protected override void HandleResult(ICustomDatabase database, ExpressionInfo expInfo, ExpressionResolvedResult result) { @@ -112,6 +114,7 @@ protected override void HandleResult(ICustomDatabase database, ExpressionInfo ex else { Where.Add(result.SqlString!); + Members.AddRange(result.Members); } } } @@ -124,22 +127,69 @@ private void CreateBatchDeleteSql(ICustomDatabase database) ResolveExpressions(database); var columns = MainTable.TableEntityInfo.Columns .Where(c => c.IsPrimaryKey || c.IsVersionColumn).ToArray(); - - BatchInfos = columns.GenBatchInfos(TargetObjects.ToList(), 2000 - DbParameters.Count); - var delete = $"DELETE FROM {GetTableName(database, MainTable, false)}"; + if (columns.Length == 0 && Where.Count == 0) + { + throw new LightOrmException("没有主键并且未设置Where条件"); + } + BatchInfos = columns.GenBatchInfos(TargetObjects, 2000 - DbParameters.Count); + //var delete = $"DELETE FROM {GetTableName(database, MainTable, false)}"; foreach (var batch in BatchInfos) { - StringBuilder sb = new(); - sb.AppendLine(delete); - List autoWhereList = []; - foreach (var p in batch.Parameters) + StringBuilder sb = new("DELETE FROM "); + //sb.AppendLine(GetTableName(database, MainTable, false)); + sb.AppendTableName(database, MainTable, false).AppendLine(); + sb.Append("WHERE "); + if (TargetObjects.Length == 0) + { + sb.Append("1=0"); + batch.Sql = sb.ToString(); + break; + } + sb.Append('('); + for (int rowIndex = 0; rowIndex < batch.Parameters.Count; rowIndex++) { - var where = string.Join(" AND ", p.Select(c => $"{c.ColumnName} = {c.ParameterName}")); - autoWhereList.Add($"({where})"); + List? row = batch.Parameters[rowIndex]; + if (columns.Length > 1) + { + sb.Append('('); + for (var i = 0; i < row.Count; i++) + { + if (i > 0) + { + sb.Append(" AND "); + } + sb.AppendEmphasis(row[i].ColumnName, database); + sb.Append(" = "); + sb.WithPrefix(row[i].ParameterName, database); + } + sb.Append(')'); + if (rowIndex < batch.Parameters.Count - 1) + sb.Append(" OR "); + } + else + { + if (rowIndex == 0) + { + // 这里直接访问索引0是安全的,因为进入到else分支的话,说明row.Count == 1, 而row.Count是跟前面的columns的数量是一致的 + sb.AppendEmphasis(row[0].ColumnName, database); + sb.Append(" IN ("); + + } + sb.WithPrefix(row[0].ParameterName, database); + if (rowIndex < batch.Parameters.Count - 1) + sb.Append(','); + else + sb.Append(')'); + } + } + sb.Append(')'); + + foreach (var w in Where) + { + sb.AppendLine(); + sb.Append("AND "); + sb.Append(w); } - var autoWhere = string.Join(" OR ", autoWhereList); - Where.Add(autoWhere); - sb.AppendLine($"WHERE {string.Join($"{N}AND ", Where)}"); HandleSqlParameters(sb, database); batch.Sql = sb.ToString(); } @@ -150,10 +200,12 @@ public override string ToSqlString(ICustomDatabase database) if (IsBatchDelete) { CreateBatchDeleteSql(database); - return string.Join(",", BatchInfos?.Select(b => b.Sql) ?? []); + // ToSqlString由内部或者测试项目调用,批量情况下查看SQL使用BatchInfos属性 + return string.Empty; + //return string.Join(",", BatchInfos?.Select(b => b.Sql) ?? []); } ResolveExpressions(database); - if (ForceDelete) + if (FullDelete) { if (Truncate) { @@ -166,30 +218,62 @@ public override string ToSqlString(ICustomDatabase database) } else { - // 没有设置Where条件, 且提供实体值, 则使用主键作为Where条件 - if (Where.Count == 0) + if (Where.Count == 0 && TargetObject is null) { - if (TargetObject is null) LightOrmException.Throw("Where Condition is null and not provider a entity value"); - var primary = MainTable.TableEntityInfo.Columns.Where(f => f.IsPrimaryKey).ToArray(); - if (primary.Length == 0) LightOrmException.Throw($"Where Condition is null and Model of [{MainTable.Type}] do not has a PrimaryKey"); - var wheres = primary.Select(c => - { - DbParameters.Add(c.ColumnName, c.GetValue(TargetObject!)!); - return $"{database.AttachEmphasis(c.ColumnName)} = {database.AttachPrefix(c.ColumnName)}"; - }); - Where.AddRange(wheres); + throw new LightOrmException("Where Condition is null and not provider a entity value"); } StringBuilder sql; sql = new("DELETE FROM "); - sql.AppendLine(GetTableName(database, MainTable, false)); + //sql.AppendLine(GetTableName(database, MainTable, false)); + sql.AppendTableName(database, MainTable, false).AppendLine(); + // 没有设置Where条件, 且提供实体值, 则使用主键作为Where条件 + bool first = true; + if (TargetObject is not null) + { + var keyedColumns = MainTable.TableEntityInfo.Columns.Where(f => f.IsPrimaryKey || f.IsVersionColumn).ToArray(); + if (keyedColumns.Length == 0) LightOrmException.Throw($"Where Condition is null and Model of [{MainTable.Type}] do not has a PrimaryKey"); + //var wheres = keyedColumns.Select(c => + //{ + // DbParameters.Add(c.ColumnName, c.GetValue(TargetObject!)!); + // return $"{database.AttachEmphasis(c.ColumnName)} = {database.AttachPrefix(c.ColumnName)}"; + //}); + //Where.AddRange(wheres); + sql.Append("WHERE "); + foreach (var col in keyedColumns) + { + if (Members.Contains(col.PropertyName)) + { + continue; + } + DbParameters.Add(col.PropertyName, col.GetValue(TargetObject)!); + if (!first) + { + sql.Append(" AND "); + } + first = false; + sql.Append('('); + sql.AppendEmphasis(col.ColumnName, database); + sql.Append(" = "); + sql.WithPrefix(col.PropertyName, database); + sql.Append(')'); + } + } + if (Where.Count > 0) { - sql.AppendLine($"WHERE {string.Join(" AND ", Where)}"); + if (first) + { + sql.Append("WHERE "); + } + else + { + sql.Append(" AND "); + } + //sql.AppendLine($"WHERE {string.Join(" AND ", Where)}"); + sql.AppendJoined(Where, " AND "); } HandleSqlParameters(sql, database); return sql.Trim(); } } - - } diff --git a/src/LightORM/Builder/InsertBuilder.cs b/src/LightORM/Builder/InsertBuilder.cs index 08295fa3..c28b48dc 100644 --- a/src/LightORM/Builder/InsertBuilder.cs +++ b/src/LightORM/Builder/InsertBuilder.cs @@ -6,7 +6,7 @@ namespace LightORM.Builder; internal record InsertBuilder : SqlBuilder { public new T? TargetObject { get; set; } - public IEnumerable TargetObjects { get; set; } = []; + public T[] TargetObjects { get; set; } = []; public List? BatchInfos { get; set; } HashSet IgnoreMembers { get; set; } = []; HashSet Members { get; set; } = []; @@ -41,35 +41,58 @@ public void CreateInsertBatchSql(ICustomDatabase database) .Where(c => !IgnoreMembers.Contains(c.PropertyName)) .Where(c => Members.Contains(c.PropertyName) && !c.IsNotMapped && !c.IsNavigate).ToArray(); - BatchInfos = insertColumns.GenBatchInfos(TargetObjects.ToList(), 2000 - DbParameters.Count); - var insert = $"INSERT INTO {GetTableName(database, MainTable, false)} {N}({string.Join(", ", insertColumns.Select(c => database.AttachEmphasis(c.ColumnName)))}) {N}VALUES {N}"; + BatchInfos = insertColumns.GenBatchInfos(TargetObjects, 2000 - DbParameters.Count); + //var insert = $"INSERT INTO {GetTableName(database, MainTable, false)} {N}({string.Join(", ", insertColumns.Select(c => database.AttachEmphasis(c.ColumnName)))}) {N}VALUES {N}"; foreach (var item in BatchInfos) { - StringBuilder sb = new(insert); - List values = []; + StringBuilder sb = CreateInsertBuilder();//new(insert);// + bool firstRow = true; foreach (var dic in item.Parameters) { - var rowValues = dic.Select(c => - { - var val = c.Value; - if (val is null) - { - return "NULL"; - } - //if (c.UnderlyingType.IsNumber() || c.UnderlyingType == typeof(string)) - //{ - // return $"'{val}'"; - //} - return database.AttachPrefix(c.ParameterName); - }); - values.Add($"({string.Join(", ", rowValues)})"); + if (!firstRow) + { + sb.Append(','); + sb.AppendLine(); + } + firstRow = false; + sb.Append('('); + foreach (var c in dic) + { + if (c.Value is null) + { + sb.Append("NULL"); + } + else + { + sb.WithPrefix(c.ParameterName, database); + } + sb.Append(','); + } + sb.RemoveLast(1); + sb.Append(')'); } - sb.AppendLine($"{string.Join($", {N}", values)}"); HandleSqlParameters(sb, database); item.Sql = sb.ToString(); } batchDone = true; + StringBuilder CreateInsertBuilder() + { + var sb = new StringBuilder("INSERT INTO "); + //sb.Append(GetTableName(database, MainTable, false)); + sb.AppendTableName(database, MainTable, false).AppendLine(); + sb.Append('('); + foreach (var item in insertColumns) + { + sb.AppendEmphasis(item.ColumnName, database); + sb.Append(','); + } + sb.RemoveLast(1); + sb.Append(')'); + sb.AppendLine(); + sb.AppendLine("VALUES"); + return sb; + } } @@ -78,7 +101,9 @@ public override string ToSqlString(ICustomDatabase database) if (IsBatchInsert) { CreateInsertBatchSql(database); - return string.Join(",", BatchInfos?.Select(b => b.Sql) ?? []); + //return string.Join(",", BatchInfos?.Select(b => b.Sql) ?? []); + // ToSqlString由内部或者测试项目调用,批量情况下查看SQL使用BatchInfos属性 + return string.Empty; } if (TargetObject == null) LightOrmException.Throw("insert null entity"); @@ -116,8 +141,8 @@ public override string ToSqlString(ICustomDatabase database) } foreach (var item in insertColumns) { - columns.Append(database.AttachEmphasis(item.ColumnName)); - columns.Append(", "); + columns.AppendEmphasis(item.ColumnName, database); + columns.Append(','); var val = item.GetValue(TargetObject!); if (val is bool b) { @@ -127,14 +152,15 @@ public override string ToSqlString(ICustomDatabase database) else { DbParameters.Add(item.PropertyName, val!); - values.Append(database.AttachPrefix(item.PropertyName)); + values.WithPrefix(item.PropertyName, database); } - values.Append(", "); + values.Append(','); } - columns.RemoveLast(2); - values.RemoveLast(2); + columns.RemoveLast(1); + values.RemoveLast(1); StringBuilder sb = new("INSERT INTO"); - sb.AppendLine($" {GetTableName(database, MainTable, false)} "); + //sb.AppendLine($" {GetTableName(database, MainTable, false)} "); + sb.AppendTableName(database, MainTable, false).AppendLine(); sb.Append('('); sb.Append(columns); sb.AppendLine(")"); diff --git a/src/LightORM/Builder/SelectBuilder.cs b/src/LightORM/Builder/SelectBuilder.cs index 4c5f59f8..1ea3a4d7 100644 --- a/src/LightORM/Builder/SelectBuilder.cs +++ b/src/LightORM/Builder/SelectBuilder.cs @@ -1,362 +1,420 @@ using LightORM.Extension; using System.Text; -namespace LightORM.Builder +namespace LightORM.Builder; + +internal struct UnionItem(SelectBuilder select, bool all) { - internal struct UnionItem(SelectBuilder select, bool all) - { - public SelectBuilder SqlBuilder { get; set; } = select; - public bool IsAll { get; set; } = all; - } + public SelectBuilder SqlBuilder { get; set; } = select; + public bool IsAll { get; set; } = all; +} + +internal struct SelectInsert(string tableName, string columns) +{ + public string TableName { get; set; } = tableName; + public string InsertColumns { get; set; } = columns; +} - internal struct SelectInsert(string tableName, string columns) +internal record SelectBuilder : SqlBuilder, ISelectSqlBuilder +{ + public SelectBuilder() { - public string TableName { get; set; } = tableName; - public string InsertColumns { get; set; } = columns; + //DbType = dbType; + IncludeContext = new IncludeContext(); + //Indent = new Lazy(() => new string(' ', 4 * Level)); } + public string Id { get; } = $"{Guid.NewGuid():N}"; + public int PageIndex { get; set; } + public int PageSize { get; set; } + public int Skip { get; set; } + public int Take { get; set; } + //private Lazy Indent { get; } + public List TempViews { get; } = []; + public SelectBuilder? SubQuery { get; set; } + public List Unions { get; } = []; + public bool IsSubQuery { get; set; } + public bool IsTemp { get; set; } + public bool IsUnion { get; set; } + public SelectInsert? InsertInfo { get; set; } + public int UnionIndex { get; set; } + //public bool UseTemp { get; set; } + public string? TempName { get; set; } + public bool IsDistinct { get; set; } + public bool IsRollup { get; set; } + public string SelectValue { get; set; } = "*"; + public int Level { get; set; } + public List Joins { get; set; } = []; + public List Having { get; set; } = []; + public List Includes { get; set; } = []; + public IncludeContext IncludeContext { get; set; } = default!; - internal record SelectBuilder : SqlBuilder, ISelectSqlBuilder + public List GroupBy { get; set; } = []; + public List OrderBy { get; set; } = []; + public int TableIndexFix { get; set; } + public object? AdditionalValue { get; set; } + public int NextTableIndex => SelectedTables.Count + Joins.Count + TableIndexFix; + //protected override Lazy GetAllTables() + //{ + // return new(() => [.. SelectedTables, .. Joins.Select(j => j.EntityInfo)]); + //} + + public override IEnumerable AllTables() { - public SelectBuilder() + foreach (var item in SelectedTables) { - //DbType = dbType; - IncludeContext = new IncludeContext(); - Indent = new Lazy(() => new string(' ', 4 * Level)); + yield return item; } - public string Id { get; } = $"{Guid.NewGuid():N}"; - public int PageIndex { get; set; } - public int PageSize { get; set; } - public int Skip { get; set; } - public int Take { get; set; } - private Lazy Indent { get; } - public List TempViews { get; } = []; - public SelectBuilder? SubQuery { get; set; } - public List Unions { get; } = []; - public bool IsSubQuery { get; set; } - public bool IsTemp { get; set; } - public bool IsUnion { get; set; } - public SelectInsert? InsertInfo { get; set; } - public int UnionIndex { get; set; } - //public bool UseTemp { get; set; } - public string? TempName { get; set; } - public bool IsDistinct { get; set; } - public bool IsRollup { get; set; } - public string SelectValue { get; set; } = "*"; - public int Level { get; set; } - public List Joins { get; set; } = []; - public List Having { get; set; } = []; - public List Includes { get; set; } = []; - public IncludeContext IncludeContext { get; set; } = default!; + foreach (var item in Joins) + { + yield return item.EntityInfo!; + } + //return [.. SelectedTables, .. Joins.Select(j => j.EntityInfo!)]; + } - public List GroupBy { get; set; } = []; - public List OrderBy { get; set; } = []; - public int TableIndexFix { get; set; } - public object? AdditionalValue { get; set; } - public int NextTableIndex => SelectedTables.Count + Joins.Count + TableIndexFix; - //protected override Lazy GetAllTables() - //{ - // return new(() => [.. SelectedTables, .. Joins.Select(j => j.EntityInfo)]); - //} - public override IEnumerable AllTables() + protected override void BeforeResolveExpressions(ResolveContext context) + { + context.Level = Level; + if (IsTemp) { - foreach (var item in SelectedTables) + context.SetParamPrefix(TempName); + } + else if (IsSubQuery) + { + //context.ModifyAlias(t => t.Alias = t.Alias?.Replace("a", $"s{Level}_")); + context.SetParamPrefix("s"); + } + } + protected override void HandleResult(ICustomDatabase database, ExpressionInfo expInfo, ExpressionResolvedResult result) + { + if (expInfo.ResolveOptions?.SqlType == SqlPartial.Where) + { + if (result.UseNavigate) { - yield return item; + if (result.NavigateDeep == 0) result.NavigateDeep = 1; + ScanNavigate(database, result, MainTable); + IsDistinct = true; + if (result.NavigateWhereExpression.TryGetLambdaExpression(out var l) + && l!.Parameters[0].Type == Joins.LastOrDefault()?.EntityInfo?.Type) + { + List ps = [.. AllTables().Select(t => Expression.Parameter(t.TableEntityInfo.Type!))]; + ps.RemoveAt(ps.Count - 1); + var newWhereExpression = Expression.Lambda(l.Body, [.. ps, l.Parameters[0]]); + var ee = new ExpressionInfo() + { + ResolveOptions = SqlResolveOptions.Where, + Expression = newWhereExpression, + }; + var eeResult = ee.Expression.Resolve(ee.ResolveOptions, ResolveCtx!); + Where.Add(eeResult.SqlString!); + if (eeResult.DbParameters?.Count > 0) + DbParameterInfos.AddRange(eeResult.DbParameters); + } } - foreach (var item in Joins) + else { - yield return item.EntityInfo!; + Where.Add(result.SqlString!); } - //return [.. SelectedTables, .. Joins.Select(j => j.EntityInfo!)]; } - - - protected override void BeforeResolveExpressions(ResolveContext context) + else if (expInfo.ResolveOptions?.SqlType == SqlPartial.Join) { - context.Level = Level; - if (IsTemp) - { - context.SetParamPrefix(TempName); - } - else if (IsSubQuery) + var joinInfo = Joins.FirstOrDefault(j => j.ExpressionId == expInfo.Id); + if (joinInfo != null) { - //context.ModifyAlias(t => t.Alias = t.Alias?.Replace("a", $"s{Level}_")); - context.SetParamPrefix("s"); + joinInfo.Where = result.SqlString!; } } - protected override void HandleResult(ICustomDatabase database, ExpressionInfo expInfo, ExpressionResolvedResult result) + else if (expInfo.ResolveOptions?.SqlType == SqlPartial.Select) { - if (expInfo.ResolveOptions?.SqlType == SqlPartial.Where) + if (result.UseNavigate) { - if (result.UseNavigate) + ScanNavigate(database, result, MainTable); + foreach (var item in result.WindowFnPartials ?? []) { - if (result.NavigateDeep == 0) result.NavigateDeep = 1; - ScanNavigate(database, result, MainTable); - IsDistinct = true; - if (result.NavigateWhereExpression.TryGetLambdaExpression(out var l) - && l!.Parameters[0].Type == Joins.LastOrDefault()?.EntityInfo?.Type) + if (item.Expression is MemberExpression m) { List ps = [.. AllTables().Select(t => Expression.Parameter(t.TableEntityInfo.Type!))]; - ps.RemoveAt(ps.Count - 1); - var newWhereExpression = Expression.Lambda(l.Body, [.. ps, l.Parameters[0]]); - var ee = new ExpressionInfo() - { - ResolveOptions = SqlResolveOptions.Where, - Expression = newWhereExpression, - }; - var eeResult = ee.Expression.Resolve(ee.ResolveOptions, ResolveCtx!); - Where.Add(eeResult.SqlString!); - if (eeResult.DbParameters?.Count > 0) - DbParameterInfos.AddRange(eeResult.DbParameters); + var p = ps.First(p => p.Type == m.Expression?.Type); + var lambda = Expression.Lambda(Expression.Property(p, m.Member.Name), ps); + var rr = lambda.Resolve(expInfo.ResolveOptions, ResolveCtx!); + result.SqlString = result.SqlString?.Replace(item.Idenfity, rr.SqlString); } } - else - { - Where.Add(result.SqlString!); - } } - else if (expInfo.ResolveOptions?.SqlType == SqlPartial.Join) + if (!string.IsNullOrWhiteSpace(result.SqlString)) + { + SelectValue = result.SqlString!; + } + } + else if (expInfo.ResolveOptions?.SqlType == SqlPartial.GroupBy) + { + GroupBy.Add(result.SqlString!); + } + else if (expInfo.ResolveOptions?.SqlType == SqlPartial.OrderBy) + { + OrderBy.Add(result.SqlString!); + AdditionalValue = expInfo.AdditionalParameter; + } + else if (expInfo.ResolveOptions?.SqlType == SqlPartial.Having) + { + Having.Add(result.SqlString!); + } + } + + private void ScanNavigate(ICustomDatabase database, ExpressionResolvedResult result, TableInfo mainTableInfo) + { + foreach (var navColumn in mainTableInfo.GetNavigateColumns()) + { + if (!result.NavigateMembers!.Contains(navColumn.PropertyName)) + { + continue; + } + var navInfo = navColumn.NavigateInfo!; + var mainCol = mainTableInfo.GetColumnInfo(navInfo.MainName!); + var targetType = navInfo.NavigateType; + var targetTable = TableInfo.Create(targetType, NextTableIndex); + if (navInfo.MappingType != null) { - var joinInfo = Joins.FirstOrDefault(j => j.ExpressionId == expInfo.Id); - if (joinInfo != null) + var targetNav = targetTable.GetNavigateColumns(c => c.NavigateInfo?.MappingType == navInfo.MappingType).First().NavigateInfo!; + var targetCol = targetTable.GetColumnInfo(targetNav.MainName!); + var mapTable = TableInfo.Create(navInfo.MappingType, NextTableIndex); + var subCol = mapTable.GetColumnInfo(navInfo.SubName!); + //TryJoin(mapTable); + Joins.Add(new JoinInfo { - joinInfo.Where = result.SqlString!; - } + EntityInfo = mapTable, + JoinType = TableLinkType.InnerJoin, + Where = $"( {mainTableInfo.Alias}.{database.AttachEmphasis(mainCol.ColumnName)} = {mapTable.Alias}.{database.AttachEmphasis(subCol.ColumnName)} )" + }); + + subCol = mapTable.GetColumnInfo(targetNav.SubName!); + targetTable.Index += 1; + Joins.Add(new JoinInfo + { + EntityInfo = targetTable, + JoinType = TableLinkType.InnerJoin, + Where = $"( {targetTable.Alias}.{database.AttachEmphasis(targetCol.ColumnName)} = {mapTable.Alias}.{database.AttachEmphasis(subCol.ColumnName)} )" + }); } - else if (expInfo.ResolveOptions?.SqlType == SqlPartial.Select) + else { - if (result.UseNavigate) + var targetCol = targetTable.GetColumnInfo(navInfo.SubName!); + //TryJoin(targetTable); + Joins.Add(new JoinInfo { - ScanNavigate(database, result, MainTable); - foreach (var item in result.WindowFnPartials ?? []) + EntityInfo = targetTable, + JoinType = TableLinkType.InnerJoin, + Where = $"( {mainTableInfo.Alias}.{database.AttachEmphasis(mainCol.ColumnName)} = {targetTable.Alias}.{database.AttachEmphasis(targetCol.ColumnName)} )" + }); + var n = result.Members?.FirstOrDefault(m => m != navColumn.PropertyName); + if (n is not null) + { + var c = targetTable.GetColumn(n); + if (c is not null) { - if (item.Expression is MemberExpression m) - { - List ps = [.. AllTables().Select(t => Expression.Parameter(t.TableEntityInfo.Type!))]; - var p = ps.First(p => p.Type == m.Expression?.Type); - var lambda = Expression.Lambda(Expression.Property(p, m.Member.Name), ps); - var rr = lambda.Resolve(expInfo.ResolveOptions, ResolveCtx!); - result.SqlString = result.SqlString?.Replace(item.Idenfity, rr.SqlString); - } + var where = $"{targetTable.Alias}.{database.AttachEmphasis(c.ColumnName)}{result.SqlString}"; + Where.Add(where); } } - if (!string.IsNullOrWhiteSpace(result.SqlString)) - { - SelectValue = result.SqlString!; - } - } - else if (expInfo.ResolveOptions?.SqlType == SqlPartial.GroupBy) - { - GroupBy.Add(result.SqlString!); } - else if (expInfo.ResolveOptions?.SqlType == SqlPartial.OrderBy) - { - OrderBy.Add(result.SqlString!); - AdditionalValue = expInfo.AdditionalParameter; - } - else if (expInfo.ResolveOptions?.SqlType == SqlPartial.Having) + + if (result.NavigateDeep > 1) { - Having.Add(result.SqlString!); + ScanNavigate(database, result, targetTable); + result.NavigateDeep--; } } + } - private void ScanNavigate(ICustomDatabase database, ExpressionResolvedResult result, TableInfo mainTableInfo) + private void BuildFromString(StringBuilder sql, ICustomDatabase database) + { + if (SelectedTables.Count == 1) { - foreach (var navColumn in mainTableInfo.GetNavigateColumns()) + sql.AppendTableName(database, MainTable); + } + else + { + bool first = true; + foreach (var table in SelectedTables) { - if (!result.NavigateMembers!.Contains(navColumn.PropertyName)) - { - continue; - } - var navInfo = navColumn.NavigateInfo!; - var mainCol = mainTableInfo.GetColumnInfo(navInfo.MainName!); - var targetType = navInfo.NavigateType; - var targetTable = TableInfo.Create(targetType, NextTableIndex); - if (navInfo.MappingType != null) - { - var targetNav = targetTable.GetNavigateColumns(c => c.NavigateInfo?.MappingType == navInfo.MappingType).First().NavigateInfo!; - var targetCol = targetTable.GetColumnInfo(targetNav.MainName!); - var mapTable = TableInfo.Create(navInfo.MappingType, NextTableIndex); - var subCol = mapTable.GetColumnInfo(navInfo.SubName!); - //TryJoin(mapTable); - Joins.Add(new JoinInfo - { - EntityInfo = mapTable, - JoinType = TableLinkType.InnerJoin, - Where = $"( {mainTableInfo.Alias}.{database.AttachEmphasis(mainCol.ColumnName)} = {mapTable.Alias}.{database.AttachEmphasis(subCol.ColumnName)} )" - }); - - subCol = mapTable.GetColumnInfo(targetNav.SubName!); - targetTable.Index += 1; - Joins.Add(new JoinInfo - { - EntityInfo = targetTable, - JoinType = TableLinkType.InnerJoin, - Where = $"( {targetTable.Alias}.{database.AttachEmphasis(targetCol.ColumnName)} = {mapTable.Alias}.{database.AttachEmphasis(subCol.ColumnName)} )" - }); - } - else - { - var targetCol = targetTable.GetColumnInfo(navInfo.SubName!); - //TryJoin(targetTable); - Joins.Add(new JoinInfo - { - EntityInfo = targetTable, - JoinType = TableLinkType.InnerJoin, - Where = $"( {mainTableInfo.Alias}.{database.AttachEmphasis(mainCol.ColumnName)} = {targetTable.Alias}.{database.AttachEmphasis(targetCol.ColumnName)} )" - }); - var n = result.Members?.FirstOrDefault(m => m != navColumn.PropertyName); - if (n is not null) - { - var c = targetTable.GetColumn(n); - if (c is not null) - { - var where = $"{targetTable.Alias}.{database.AttachEmphasis(c.ColumnName)}{result.SqlString}"; - Where.Add(where); - } - } - } - - if (result.NavigateDeep > 1) + if (!first) { - ScanNavigate(database, result, targetTable); - result.NavigateDeep--; + sql.Append(", "); } + first = false; + sql.AppendTableName(database, table); } } + sql.AppendLine(); + } + + public override string ToSqlString(ICustomDatabase database) + { + //SubQuery?.ResolveExpressions(); + StringBuilder sql = new(); + Build(sql, database, Level); + HandleSqlParameters(sql, database); + sql.Trim(); + return sql.ToString(); + + } - private string BuildFromString(ICustomDatabase database) + public void Build(StringBuilder sql, ICustomDatabase database, int currentLevel) + { + ResolveExpressions(database); + var ident = new string(' ', 4 * currentLevel); + if (InsertInfo.HasValue) { - if (SelectedTables.Count == 1) - { - return GetTableName(database, MainTable); - } - return string.Join(", ", SelectedTables.Select(t => GetTableName(database, t))); + //sb.AppendLine($"INSERT INTO {database.AttachEmphasis(InsertInfo.Value.TableName)}"); + //sb.AppendLine($"({InsertInfo.Value.InsertColumns})"); + // INSERT INTO table + // (columns...) + sql.Append("INSERT INTO ") + .AppendEmphasis(InsertInfo.Value.TableName, database) + .AppendLine() + .Append('(').Append(InsertInfo.Value.InsertColumns).AppendLine(")"); } - public override string ToSqlString(ICustomDatabase database) + if (TempViews.Count > 0) { - //SubQuery?.ResolveExpressions(); - ResolveExpressions(database); - - StringBuilder sb = new(); - if (InsertInfo.HasValue) + sql.Append("WITH"); + foreach (var item in TempViews) { - sb.AppendLine($"INSERT INTO {database.AttachEmphasis(InsertInfo.Value.TableName)}"); - sb.AppendLine($"({InsertInfo.Value.InsertColumns})"); - } - - if (TempViews.Count > 0) - { - sb.Append("WITH"); - foreach (var item in TempViews) - { - sb.Append(item.ToSqlString(database)); - sb.Append(','); - DbParameters.TryAddDictionary(item.DbParameters); - } - sb.RemoveLast(1); - } - if (IsTemp) - { - Level += 1; - sb.AppendLine($" {TempName} AS ("); + //sql.Append(item.ToSqlString(database)); + //item.Level = Level + 1; + item.Build(sql, database, currentLevel + 1); + sql.Append(','); + DbParameters.TryAddDictionary(item.DbParameters); } + sql.RemoveLast(1); + } + if (IsTemp) + { + //Level += 1; + //sb.AppendLine($" {TempName} AS ("); + //$" {TempName} AS ("; + sql.Append(' ').Append(TempName).Append(' ').AppendLine(" AS ("); + } - if (GroupBy.Count > 0) - { - var groupby = string.Join(",", GroupBy); - if (SelectValue == "*") - { - SelectValue = groupby; - } - } - var dist = IsDistinct ? "DISTINCT " : ""; - sb.AppendLine($"{Indent.Value}SELECT {dist}{SelectValue}"); + //if (GroupBy.Count > 0) + //{ + // if (SelectValue == "*") + // { + // SelectValue = string.Join(",", GroupBy); + // } + //} + //var dist = IsDistinct ? "DISTINCT " : string.Empty; + // $"{ident}SELECT {dist}{SelectValue}" + sql.Append(ident).Append("SELECT ");//.AppendLine(SelectValue); + if (IsDistinct) + { + sql.Append("DISTINCT "); + } + if (GroupBy.Count > 0 && SelectValue == "*") + { + sql.AppendJoined(GroupBy, ","); + } + else + { + sql.AppendLine(SelectValue); + } + if (SubQuery == null) + { + // $"{ident}FROM {BuildFromString(database)}" + sql.Append(ident).Append("FROM "); + BuildFromString(sql, database); + } + else + { + //SubQuery.Level = Level + 1; + //sb.AppendLine($"{ident}FROM ("); + //sb.Append(SubQuery.ToSqlString(database)); + //sb.AppendLine($"{ident}) {MainTable.Alias}"); + sql.Append(ident).AppendLine("FROM ("); + //.Append(SubQuery.ToSqlString(database)) + SubQuery.Build(sql, database, currentLevel + 1); + sql.Append(ident).Append(") ").AppendLine(MainTable.Alias); + DbParameters.TryAddDictionary(SubQuery.DbParameters); + } - if (SubQuery == null) + foreach (var item in Joins) + { + if (item.IsSubQuery && item.SubQuery is not null) { - sb.AppendLine($"{Indent.Value}FROM {BuildFromString(database)}"); + //item.SubQuery!.Level = Level + 1; + //sb.AppendLine($"{ident}{item.JoinType.ToLabel()} ("); + //sb.Append(item.SubQuery.ToSqlString(database)); + //sb.AppendLine($"{ident}) {item.EntityInfo!.Alias!} ON {item.Where}"); + sql.Append(ident).Append(item.JoinType.ToLabel()).AppendLine(" ("); + //.Append(item.SubQuery.ToSqlString(database)) + item.SubQuery.Build(sql, database, currentLevel + 1); + sql.Append(ident).Append(") ").Append(item.EntityInfo!.Alias!).Append(" ON ").AppendLine(item.Where); + DbParameters.TryAddDictionary(item.SubQuery.DbParameters); } else { - SubQuery.Level = Level + 1; - sb.AppendLine($"{Indent.Value}FROM ("); - sb.Append(SubQuery.ToSqlString(database)); - sb.AppendLine($"{Indent.Value}) {MainTable.Alias}"); - DbParameters.TryAddDictionary(SubQuery.DbParameters); - } - - foreach (var item in Joins) - { - if (item.IsSubQuery) - { - item.SubQuery!.Level = Level + 1; - sb.AppendLine($"{Indent.Value}{item.JoinType.ToLabel()} ("); - sb.Append(item.SubQuery.ToSqlString(database)); - sb.AppendLine($"{Indent.Value}) {item.EntityInfo!.Alias!} ON {item.Where}"); - DbParameters.TryAddDictionary(item.SubQuery.DbParameters); - } - else - { - sb.AppendLine($"{Indent.Value}{item.JoinType.ToLabel()} {GetTableName(database, item.EntityInfo!)} ON {item.Where}"); - } + // $"{ident}{item.JoinType.ToLabel()} {GetTableName(database, item.EntityInfo!)} ON {item.Where}" + sql.Append(ident).Append(item.JoinType.ToLabel()).Append(' ').AppendTableName(database, item.EntityInfo!).Append(" ON ").AppendLine(item.Where); } + } - if (Where.Count > 0) - { - sb.AppendLine($"{Indent.Value}WHERE {string.Join($" AND ", Where)}"); - } - if (GroupBy.Count > 0) - { - if (IsRollup) - { - sb.AppendLine($"{Indent.Value}GROUP BY ROLLUP ({string.Join(", ", GroupBy)})"); - } - else - { - sb.AppendLine($"{Indent.Value}GROUP BY {string.Join(", ", GroupBy)}"); - } - } - if (Having.Count > 0) + if (Where.Count > 0) + { + // $"{ident}WHERE {string.Join($" AND ", Where)}" + sql.Append(ident).Append("WHERE ").AppendJoined(Where, " AND ").AppendLine(); + } + if (GroupBy.Count > 0) + { + if (IsRollup) { - sb.AppendLine($"{Indent.Value}HAVING {string.Join(", ", Having)}"); + // $"{ident}GROUP BY ROLLUP ({string.Join(", ", GroupBy)})" + sql.Append(ident).Append("GROUP BY ROLLUP (").AppendJoined(GroupBy, ",").AppendLine(")"); } - if (OrderBy.Count > 0) + else { - sb.AppendLine($"{Indent.Value}ORDER BY {string.Join(", ", OrderBy)} {AdditionalValue}"); + // $"{ident}GROUP BY {string.Join(", ", GroupBy)}" + sql.Append(ident).Append("GROUP BY ").AppendJoined(GroupBy, ",").AppendLine(); } - if (Take > 0) + } + if (Having.Count > 0) + { + // $"{ident}HAVING {string.Join(", ", Having)}" + sql.Append(ident).Append("HAVING ").AppendJoined(Having, ",").AppendLine(); + } + if (OrderBy.Count > 0) + { + // $"{ident}ORDER BY {string.Join(", ", OrderBy)} {AdditionalValue}" + sql.Append(ident).Append("ORDER BY ").AppendJoined(OrderBy, ",").Append(' '); + if (AdditionalValue is not null) { - if (Skip < 0) Skip = 0; - database.Paging(this, sb); + sql.Append(AdditionalValue.ToString()); } + sql.AppendLine(); + } + if (Take > 0) + { + if (Skip < 0) Skip = 0; + database.Paging(this, sql); + } - if (IsTemp) - { - sb.AppendLine(")"); - } + if (IsTemp) + { + sql.AppendLine(")"); + } - // union - if (Unions.Count > 0) - { - foreach (var item in Unions) - { - item.SqlBuilder.Level = Level; - var union = item.IsAll ? "UNION ALL" : "UNION"; - sb.AppendLine($"{Indent.Value}{union}"); - sb.Append(item.SqlBuilder.ToSqlString(database)); - DbParameters.TryAddDictionary(item.SqlBuilder.DbParameters); - } - } - HandleSqlParameters(sb, database); - if (Level == 0) + // union + if (Unions.Count > 0) + { + foreach (var item in Unions) { - return sb.Trim(); + //item.SqlBuilder.Level = Level; + var union = item.IsAll ? "UNION ALL" : "UNION"; + // $"{ident}{union}" + sql.Append(ident).AppendLine(union); + item.SqlBuilder.Build(sql, database, currentLevel); + //sql.Append(item.SqlBuilder.ToSqlString(database)); + DbParameters.TryAddDictionary(item.SqlBuilder.DbParameters); } - return sb.ToString(); - } } } diff --git a/src/LightORM/Builder/SqlBuilder.cs b/src/LightORM/Builder/SqlBuilder.cs index 3f936105..9965bfee 100644 --- a/src/LightORM/Builder/SqlBuilder.cs +++ b/src/LightORM/Builder/SqlBuilder.cs @@ -118,6 +118,8 @@ public static string GetTableName(ICustomDatabase database, TableInfo ti, bool u return $"{NpTableName(database, ti)}{((useAlias && !string.IsNullOrEmpty(ti.Alias)) ? $" {ti.Alias}" : "")}"; } + + //TODO Oracle? protected static string NpTableName(ICustomDatabase database, TableInfo table) { diff --git a/src/LightORM/Builder/UpdateBuilder.cs b/src/LightORM/Builder/UpdateBuilder.cs index 78bfc16c..70fd9109 100644 --- a/src/LightORM/Builder/UpdateBuilder.cs +++ b/src/LightORM/Builder/UpdateBuilder.cs @@ -11,7 +11,7 @@ internal struct UpdateValue internal record UpdateBuilder : SqlBuilder { public new T? TargetObject { get; set; } - public IEnumerable TargetObjects { get; set; } = []; + public T[] TargetObjects { get; set; } = []; public List? BatchInfos { get; set; } HashSet IgnoreMembers { get; set; } = []; HashSet Members { get; set; } = []; @@ -95,51 +95,96 @@ private void CreateUpdateBatchSql(ICustomDatabase database) }) .ToArray(); - BatchInfos = columns.GenBatchInfos(TargetObjects.ToList(), 2000 - DbParameters.Count, DbParameters); - var update = $"UPDATE {GetTableName(database, MainTable, false)} SET"; + BatchInfos = columns.GenBatchInfos(TargetObjects, 2000 - DbParameters.Count, DbParameters); + //var update = $"UPDATE {GetTableName(database, MainTable, false)} SET"; //var primaryWhen = $"WHEN {string.Join(" AND ", primaryCol.Select(p => $"{AttachPrefix(p.ColumnName)}_{{0}}"))}"; foreach (var batch in BatchInfos) { // 每一个BatchSqInfo就是每批次更新的数据量 - StringBuilder sb = new(update); - foreach (var col in columns) + StringBuilder sb = new("UPDATE "); + //sb.Append(GetTableName(database, MainTable, false)); + sb.AppendTableName(database, MainTable, false); + sb.Append(" SET "); + for (int i = 0; i < columns.Length; i++) { + ITableColumnInfo? col = columns[i]; if (col.IsPrimaryKey) continue; - sb.Append($"\n{database.AttachEmphasis(col.ColumnName)} = CASE "); - + //sb.Append($"\n{database.AttachEmphasis(col.ColumnName)} = CASE "); + sb.AppendEmphasis(col.ColumnName, database); + sb.AppendLine(" = CASE"); // 每一条记录的参数数量 - foreach (var rowDatas in batch.Parameters) + for (var rowIndex = 0; rowIndex < batch.Parameters.Count; rowIndex++) { + var rowDatas = batch.Parameters[rowIndex]; var currentCol = rowDatas.First(r => r.PropName == col.PropertyName); if (currentCol.IsVersion) { var newVersion = VersionPlus(currentCol.Value); - var newCol = currentCol with { ParameterName = $"{currentCol.ParameterName}_new", Value = newVersion }; - sb.Append($"WHEN {string.Join(" AND ", rowDatas.Where(r => r.IsPrimaryKey || r.IsVersion).Select(r => $"{database.AttachEmphasis(r.ColumnName)} = {database.AttachPrefix(r.ParameterName)}"))} THEN {GetValueExpression(newCol)} "); - //(newCol.Value == null ? "NULL" : database.AttachPrefix(newCol.ParameterName)) + var newCol = currentCol with { ParameterName = $"{currentCol.ParameterName}_n", Value = newVersion, IsVersion = false }; rowDatas.Add(newCol); + currentCol = newCol; } - else + bool first = true; + sb.Append(" WHEN "); + foreach (var item in rowDatas.Where(r => r.IsPrimaryKey || r.IsVersion)) { - sb.Append($"WHEN {string.Join(" AND ", rowDatas.Where(r => r.IsPrimaryKey || r.IsVersion).Select(r => $"{database.AttachEmphasis(r.ColumnName)} = {database.AttachPrefix(r.ParameterName)}"))} THEN {GetValueExpression(currentCol)} "); - // (currentCol.Value == null ? "NULL" : database.AttachPrefix(currentCol.ParameterName)) + if (!first) sb.Append(" AND "); + first = false; + sb.AppendEmphasis(item.ColumnName, database); + sb.Append(" = "); + sb.WithPrefix(item.ParameterName, database); } + sb.Append(" THEN "); + sb.AppendLine(GetValueExpression(currentCol)); + //if (currentCol.IsVersion) + //{ + // rowDatas.Add(currentCol); + //} } - sb.Append("END,"); - } - sb.RemoveLast(1); + sb.Append("END, "); + } - var pValues = batch.Parameters.SelectMany(rowDatas => rowDatas.Where(r => r.IsPrimaryKey)); + sb.RemoveLast(2); - foreach (var item in pValues.GroupBy(c => c.ColumnName)) + var pValues = batch.Parameters.SelectMany(rowDatas => rowDatas.Where(r => r.IsPrimaryKey | r.IsVersion)).GroupBy(c => c.ColumnName).ToList(); + if (pValues.Count == 0 && Where.Count == 0) { - Where.Add($"( {database.AttachEmphasis(item.Key)} IN ({string.Join(", ", item.Select(i => database.AttachPrefix(i.ParameterName)))}))"); + throw new LightOrmException($"类型{typeof(T)}, 没有主键并且缺失Where条件"); + } + sb.AppendLine(); + sb.Append("WHERE "); + for (int k = 0; k < pValues.Count; k++) + { + IGrouping? item = pValues[k]; + if (k > 0) + { + sb.AppendLine(); + sb.Append("AND "); + } + sb.Append('('); + sb.AppendEmphasis(item.Key, database); + sb.Append(" IN ("); + foreach (var i in item) + { + sb.WithPrefix(i.ParameterName, database); + sb.Append(','); + } + sb.RemoveLast(1); + sb.Append("))"); } if (Where.Count > 0) { - sb.AppendLine(); - sb.AppendLine($"WHERE {string.Join($"{N}AND ", Where)}"); + //if (pValues.Count == 0) + for (int i = 0; i < Where.Count; i++) + { + if (i > 0 || pValues.Count > 0) + { + sb.AppendLine(); + sb.Append("AND "); + } + sb.Append(Where[i]); + } } HandleSqlParameters(sb, database); batch.Sql = sb.ToString(); @@ -248,7 +293,7 @@ public override string ToSqlString(ICustomDatabase database) { var version = versionColumn.GetValue(TargetObject); var newVersion = VersionPlus(version); - DbParameters.Add($"{versionColumn.PropertyName}_new", newVersion); + DbParameters.Add($"{versionColumn.PropertyName}_n", newVersion); #if NET462 if (!DbParameters.ContainsKey(versionColumn.PropertyName)) { @@ -264,22 +309,32 @@ public override string ToSqlString(ICustomDatabase database) var setNullCol = MainTable.TableEntityInfo.Columns.Where(c => SetNullMembers.Count > 0 && SetNullMembers.Contains(c.PropertyName)); StringBuilder sb = new("UPDATE "); - sb.Append(GetTableName(database, MainTable, false)); + sb.AppendTableName(database, MainTable, false); sb.AppendLine(" SET "); foreach (var c in customCols) { // 处理一般列 - sb.AppendLine($"{database.AttachEmphasis(c.ColumnName)} = {database.AttachPrefix(c.PropertyName)},"); + sb.AppendEmphasis(c.ColumnName, database); + sb.Append(" = "); + sb.WithPrefix(c.PropertyName, database); + sb.AppendLine(","); + //sb.AppendLine($"{database.AttachEmphasis(c.ColumnName)} = {database.AttachPrefix(c.PropertyName)},"); } foreach (var c in setNullCol) { // 处理显式设置为Null值的列 - sb.AppendLine($"{database.AttachEmphasis(c.ColumnName)} = NULL,"); + //sb.AppendLine($"{database.AttachEmphasis(c.ColumnName)} = NULL,"); + sb.AppendEmphasis(c.ColumnName, database); + sb.AppendLine(" = NULL,"); } if (versionColumn is not null) { // 处理版本列 - sb.AppendLine($"{database.AttachEmphasis(versionColumn.ColumnName)} = {database.AttachPrefix($"{versionColumn.PropertyName}_new")},"); + sb.AppendEmphasis(versionColumn.ColumnName, database); + sb.Append(" = "); + sb.WithPrefix($"{versionColumn.PropertyName}_n", database); + sb.AppendLine(","); + //sb.AppendLine($"{database.AttachEmphasis(versionColumn.ColumnName)} = {database.AttachPrefix($"{versionColumn.PropertyName}_new")},"); if (!WhereMembers.Contains(versionColumn.PropertyName)) { var versonCondition = $"({database.AttachEmphasis(versionColumn.ColumnName)} = {database.AttachPrefix($"{versionColumn.PropertyName}")})"; diff --git a/src/LightORM/Cache/DbParameterReader.cs b/src/LightORM/Cache/DbParameterReader.cs index 9fbb13c4..1039f4af 100644 --- a/src/LightORM/Cache/DbParameterReader.cs +++ b/src/LightORM/Cache/DbParameterReader.cs @@ -19,12 +19,13 @@ public static Action GetDbParameterReader(string connectionSt Certificate cer = new(connectionString, commandText, paramaterType); return cacheReaders.GetOrAdd($"DbParameterReader_{cer}", _ => { - return (cmd, obj) => - { - var reader = CreateReader(cmd.CommandText, paramaterType); - reader.Invoke(cmd, obj); - SetDbType(cmd); - }; + //return (cmd, obj) => + //{ + // var reader = CreateReader(cmd.CommandText, paramaterType); + // reader.Invoke(cmd, obj); + // SetDbType(cmd); + //}; + return CreateReader(commandText, paramaterType); }); } @@ -47,15 +48,27 @@ public static Dictionary ReadToDictionary(string sql, object val return func.Invoke(value); } - private static void SetDbType(DbCommand cmd) - { - foreach (IDbDataParameter p in cmd.Parameters) - { - var dbType = GetDbType(p.Value!); - if (dbType.HasValue) - p.DbType = dbType.Value; - } - } + //private static void SetDbType(DbCommand cmd) + //{ + // foreach (IDbDataParameter p in cmd.Parameters) + // { + // var dbType = GetDbType(p.Value!); + // if (dbType.HasValue) + // p.DbType = dbType.Value; + // } + //} + + //private static object? ConvertEnumValue(object? value) + //{ + // if (value == null) return null; + // var t = value.GetType(); + // var ut = Nullable.GetUnderlyingType(t) ?? t; + // if (ut.IsEnum) + // { + // return Convert.ChangeType(value, Enum.GetUnderlyingType(ut)); + // } + // return value; + //} private static void ReadDictionary(DbCommand cmd, object obj) { @@ -63,10 +76,11 @@ private static void ReadDictionary(DbCommand cmd, object obj) foreach (var item in dic) { var p = cmd.CreateParameter(item.Key); - var dbType = GetDbType(item.Value); + var value = item.Value; + var dbType = GetDbType(ref value); if (dbType.HasValue) p.DbType = dbType.Value; - p.Value = item.Value; + p.Value = value; cmd.Parameters.Add(p); } } @@ -174,7 +188,7 @@ private static DbParameter CreateParameter(this DbCommand cmd, string parameterN } } - private static DbType? GetDbType(object value) + private static DbType? GetDbType(ref object? value) { if (value == null) { @@ -185,6 +199,7 @@ private static DbParameter CreateParameter(this DbCommand cmd, string parameterN if (t.IsEnum) { t = Enum.GetUnderlyingType(t); + value = Convert.ChangeType(value, t); } if (typeMapDbType.TryGetValue(t, out var v)) return v; diff --git a/src/LightORM/Cache/TableContext.cs b/src/LightORM/Cache/TableContext.cs index 0b37126b..0dd29b5d 100644 --- a/src/LightORM/Cache/TableContext.cs +++ b/src/LightORM/Cache/TableContext.cs @@ -140,6 +140,7 @@ public static ITableEntityInfo GetTableInfo(Type type) if (!entityInfo.IsAnonymousType) { entityInfo.TargetDatabase = lightTableAttribute?.DatabaseKey; + entityInfo.Schema = lightTableAttribute?.Schema; var descriptionAttribute = type.GetAttribute(); if (descriptionAttribute != null) { diff --git a/src/LightORM/DbEntity/Attributes/TableNameAttribute.cs b/src/LightORM/DbEntity/Attributes/TableNameAttribute.cs index 1b42f607..9618bc16 100644 --- a/src/LightORM/DbEntity/Attributes/TableNameAttribute.cs +++ b/src/LightORM/DbEntity/Attributes/TableNameAttribute.cs @@ -13,6 +13,7 @@ public class TableAttribute : Attribute public class LightTableAttribute : Attribute { public string? Name { get; set; } + public string? Schema { get; set; } public string? DatabaseKey { get; set; } } diff --git a/src/LightORM/ExpressionSql/ExpressionCoreSqlBase.cs b/src/LightORM/ExpressionSql/ExpressionCoreSqlBase.cs index fe89a275..88df1623 100644 --- a/src/LightORM/ExpressionSql/ExpressionCoreSqlBase.cs +++ b/src/LightORM/ExpressionSql/ExpressionCoreSqlBase.cs @@ -20,16 +20,16 @@ public IExpInsert Insert(params T[] entities) { if (entities.Length == 1) { - return CreateInsertProvider(entities[0]); + return CreateInsertProvider(entities[0]); } else { - return CreateInsertProvider(entities); + return CreateInsertProvider(entities); } } InsertProvider CreateInsertProvider(T? entity = default) => new(Ado, entity); - InsertProvider CreateInsertProvider(IEnumerable entities) => new(Ado, entities); + InsertProvider CreateInsertProvider(T[] entities) => new(Ado, entities); #endregion @@ -41,16 +41,16 @@ public IExpUpdate Update(params T[] entities) { if (entities.Length == 1) { - return CreateUpdateProvider(entities[0]); + return CreateUpdateProvider(entities[0]); } else { - return CreateUpdateProvider(entities); + return CreateUpdateProvider(entities); } } UpdateProvider CreateUpdateProvider(T? entity = default) => new(Ado, entity); - UpdateProvider CreateUpdateProvider(IEnumerable entities) => new(Ado, entities); + UpdateProvider CreateUpdateProvider(T[] entities) => new(Ado, entities); #endregion @@ -62,16 +62,16 @@ public IExpDelete Delete(params T[] entities) { if (entities.Length == 1) { - return CreateDeleteProvider(entities[0]); + return CreateDeleteProvider(entities[0]); } else { - return CreateDeleteProvider(entities); + return CreateDeleteProvider(entities); } } DeleteProvider CreateDeleteProvider(T? entity = default) => new(Ado, entity); - DeleteProvider CreateDeleteProvider(IEnumerable entities) => new(Ado, entities); + DeleteProvider CreateDeleteProvider(T[] entities) => new(Ado, entities); #endregion diff --git a/src/LightORM/Extension/BatchActionHelper.cs b/src/LightORM/Extension/BatchActionHelper.cs index 67ce1655..d87f69ff 100644 --- a/src/LightORM/Extension/BatchActionHelper.cs +++ b/src/LightORM/Extension/BatchActionHelper.cs @@ -24,13 +24,13 @@ private static int CalcBatchSize(int column, int limit, int dataCount, out int r return size; } public static List GenBatchInfos(this ITableColumnInfo[] columns - , List datas + , T[] datas , int limit = 2000 , Dictionary? additionalParameters = null) { var list = new List(); var verions = columns.Count(c => c.IsVersionColumn); - var size = CalcBatchSize(columns.Length + verions, limit, datas.Count, out var rows); + var size = CalcBatchSize(columns.Length + verions, limit, datas.Length, out var rows); for (var i = 0; i < size; i++) { var rowIndex = 0; @@ -49,7 +49,7 @@ public static List GenBatchInfos(this ITableColumnInfo[] column pList.Add(dbParameters); } - list.Add(new BatchSqlInfo(pList)); + list.Add(new BatchSqlInfo(pList, i + 1)); } return list; diff --git a/src/LightORM/Extension/BatchSqlInfoExtensions.cs b/src/LightORM/Extension/BatchSqlInfoExtensions.cs index cd81e8c9..1f950aec 100644 --- a/src/LightORM/Extension/BatchSqlInfoExtensions.cs +++ b/src/LightORM/Extension/BatchSqlInfoExtensions.cs @@ -10,6 +10,7 @@ public static Dictionary ToDictionaryParameters(this BatchSqlInf foreach (var col in row) { if (col.Value == null) continue; + if (col.isStaticValue) continue; values.Add(col.ParameterName, col.Value); } } diff --git a/src/LightORM/Extension/CustomDatabaseExtensions.cs b/src/LightORM/Extension/CustomDatabaseExtensions.cs index b525ff00..0642eb96 100644 --- a/src/LightORM/Extension/CustomDatabaseExtensions.cs +++ b/src/LightORM/Extension/CustomDatabaseExtensions.cs @@ -4,8 +4,16 @@ namespace LightORM.Extension; internal static class CustomDatabaseExtensions { + public static string AttachPrefix(this ICustomDatabase database, string name) => $"{database.Prefix}{name}"; + public static StringBuilder WithPrefix(this StringBuilder sql, string name, ICustomDatabase database) + { + sql.Append(database.Prefix); + sql.Append(name); + return sql; + } + public static string AttachEmphasis(this ICustomDatabase database, string name) { if (database.UseIdentifierQuote || database.IsKeyWord(name)) @@ -34,6 +42,42 @@ public static StringBuilder AppendEmphasis(this StringBuilder sql, string name, return sql; } + public static StringBuilder AppendJoined(this StringBuilder sql, List values, string separator) + { + bool first = true; + foreach (var item in values) + { + if (!first) + { + sql.Append(separator); + } + first = false; + sql.Append(item); + } + return sql; + } + + public static StringBuilder AppendTableName(this StringBuilder sql, ICustomDatabase database, TableInfo ti, bool useAlias = true, bool useEmphasis = true) + { + if (ti.TableEntityInfo.IsTempTable) + { + sql.Append(ti.TableEntityInfo.TableName); + } + else + { + if (ti.Schema is not null) + { + sql.AppendEmphasis(ti.Schema, database).Append('.'); + } + sql.AppendEmphasis(ti.TableName, database); + } + if (useAlias && !string.IsNullOrEmpty(ti.Alias)) + { + sql.Append(' ').Append(ti.Alias); + } + return sql; + } + public static ICustomDatabase GetDbCustom(this DbBaseType type) { if (!ExpressionSqlOptions.Instance.Value.CustomDatabases.TryGetValue(type.Name, out var custom)) diff --git a/src/LightORM/Extension/HashSetExtensions.cs b/src/LightORM/Extension/HashSetExtensions.cs index 5956fa1b..df806b7e 100644 --- a/src/LightORM/Extension/HashSetExtensions.cs +++ b/src/LightORM/Extension/HashSetExtensions.cs @@ -8,15 +8,12 @@ namespace LightORM.Extension; internal static class HashSetExtensions { - public static void AddRange(this HashSet set, IEnumerable values) + public static void AddRange(this HashSet set, IEnumerable? values) { -#if NET6_0_OR_GREATER - ArgumentNullException.ThrowIfNull(set); - ArgumentNullException.ThrowIfNull(values); -#else - if (set == null) throw new ArgumentNullException(nameof(set)); - if (values == null) throw new ArgumentNullException(nameof(values)); -#endif + if (values is null) + { + return; + } set.UnionWith(values); } } diff --git a/src/LightORM/Implements/BaseSqlMethodResolver.cs b/src/LightORM/Implements/BaseSqlMethodResolver.cs index 4bbd001d..39798def 100644 --- a/src/LightORM/Implements/BaseSqlMethodResolver.cs +++ b/src/LightORM/Implements/BaseSqlMethodResolver.cs @@ -50,11 +50,12 @@ public virtual void Result(IExpressionResolver resolver, MethodCallExpression me var sel = methodCall.GetExpSelectObject(); if (sel is not null) { - sel.SqlBuilder.Level = resolver.Level + 1; + //sel.SqlBuilder.Level = resolver.Level + 1; sel.SqlBuilder.IsSubQuery = true; resolver.Sql.AppendLine("("); - var sql = sel.SqlBuilder.ToSqlString(sel.Executor.Database.CustomDatabase); - resolver.Sql.Append(sql); + //var sql = sel.SqlBuilder.ToSqlString(sel.Executor.Database.CustomDatabase); + //resolver.Sql.Append(sql); + sel.SqlBuilder.Build(resolver.Sql, sel.Executor.Database.CustomDatabase, resolver.Level + 1); resolver.Sql.Append(')'); } } @@ -67,12 +68,13 @@ public virtual void Exits(IExpressionResolver resolver, MethodCallExpression met var sel = methodCall.GetExpSelectObject(); if (sel is not null) { - sel.SqlBuilder.Level = resolver.Level + 1; + //sel.SqlBuilder.Level = resolver.Level + 1; sel.SqlBuilder.IsSubQuery = true; sel.SqlBuilder.SelectValue = "1"; resolver.Sql.AppendLine(resolver.IsNot ? "NOT EXISTS (" : "EXISTS ("); - var sql = sel.SqlBuilder.ToSqlString(sel.Executor.Database.CustomDatabase); - resolver.Sql.Append(sql); + //var sql = sel.SqlBuilder.ToSqlString(sel.Executor.Database.CustomDatabase); + //resolver.Sql.Append(sql); + sel.SqlBuilder.Build(resolver.Sql, sel.Executor.Database.CustomDatabase, resolver.Level + 1); resolver.Sql.Append(')'); } } @@ -162,11 +164,12 @@ public virtual void CountDistinct(IExpressionResolver resolver, MethodCallExpres sel.HandleResult(methodCall.Arguments[0], "COUNT(DISTINCT {0})"); } - sel.SqlBuilder.Level = resolver.Level + 1; + //sel.SqlBuilder.Level = resolver.Level + 1; sel.SqlBuilder.IsSubQuery = true; resolver.Sql.AppendLine("("); - var sql = sel.SqlBuilder.ToSqlString(sel.Executor.Database.CustomDatabase); - resolver.Sql.Append(sql); + //var sql = sel.SqlBuilder.ToSqlString(sel.Executor.Database.CustomDatabase); + //resolver.Sql.Append(sql); + sel.SqlBuilder.Build(resolver.Sql, sel.Executor.Database.CustomDatabase, resolver.Level + 1); resolver.Sql.AppendLine(")"); return; } @@ -483,7 +486,13 @@ public virtual void WhereIf(IExpressionResolver resolver, MethodCallExpression m public virtual void Any(IExpressionResolver resolver, MethodCallExpression methodCall) { + if (resolver.NavigateDeep > 0) + { + resolver.Sql.Clear(); + } + resolver.NavigateDeep++; resolver.Visit(methodCall.Arguments[0]); + resolver.NavigateWhereExpression = methodCall.Arguments[1]; } #endregion @@ -616,11 +625,12 @@ private static bool HandleSubContext(IExpressionResolver resolver, MethodCallExp //sel.HandleResult(methodCall.Arguments[0], template); action(sel, methodCall.Arguments, template); - sel.SqlBuilder.Level = resolver.Level + 1; + //sel.SqlBuilder.Level = resolver.Level + 1; sel.SqlBuilder.IsSubQuery = true; resolver.Sql.AppendLine("("); - var sql = sel.SqlBuilder.ToSqlString(sel.Executor.Database.CustomDatabase); - resolver.Sql.Append(sql); + //var sql = sel.SqlBuilder.ToSqlString(sel.Executor.Database.CustomDatabase); + //resolver.Sql.Append(sql); + sel.SqlBuilder.Build(resolver.Sql, sel.Executor.Database.CustomDatabase, resolver.Level + 1); resolver.Sql.Append(')'); return true; } diff --git a/src/LightORM/Interfaces/ITableEntityInfo.cs b/src/LightORM/Interfaces/ITableEntityInfo.cs index 1e83dfd9..4c26d5de 100644 --- a/src/LightORM/Interfaces/ITableEntityInfo.cs +++ b/src/LightORM/Interfaces/ITableEntityInfo.cs @@ -4,6 +4,7 @@ public interface ITableEntityInfo { Type? Type { get; } string TableName { get; } + string? Schema { get; } //string? Alias { get; set; } bool IsAnonymousType { get; } bool IsTempTable { get; set; } diff --git a/src/LightORM/Models/BatchSqlInfo.cs b/src/LightORM/Models/BatchSqlInfo.cs index 9964f4a7..e68b1a25 100644 --- a/src/LightORM/Models/BatchSqlInfo.cs +++ b/src/LightORM/Models/BatchSqlInfo.cs @@ -21,10 +21,12 @@ internal record SimpleColumn(bool IsPrimaryKey, bool IsVersion, string ColumnNam internal record BatchParameters(ITableColumnInfo Column, List Parameters); internal class BatchSqlInfo { - public BatchSqlInfo(List> parameters) + public BatchSqlInfo(List> parameters, int index) { Parameters = parameters; + Index = index; } + public int Index { get; set; } public string? Sql { get; set; } public List> Parameters { get; set; } } diff --git a/src/LightORM/Models/TableEntity.cs b/src/LightORM/Models/TableEntity.cs index 24239f06..3ecb9ce6 100644 --- a/src/LightORM/Models/TableEntity.cs +++ b/src/LightORM/Models/TableEntity.cs @@ -14,6 +14,7 @@ public TableEntity() } public Type? Type { get; set; } public string TableName => CustomName ?? Type?.Name ?? throw new LightOrmException("获取表名异常"); + public string? Schema { get; set; } public string? Alias { get; set; } public bool IsAnonymousType { get; set; } public bool IsTempTable { get; set; } diff --git a/src/LightORM/Models/TableInfo.cs b/src/LightORM/Models/TableInfo.cs index a481ff27..33b12b22 100644 --- a/src/LightORM/Models/TableInfo.cs +++ b/src/LightORM/Models/TableInfo.cs @@ -24,6 +24,7 @@ private TableInfo(string? tableName, Type type, int index) : this(type, index) } private readonly string? overriddenTableName; public string TableName => overriddenTableName ?? TableEntityInfo.TableName; + public string? Schema => TableEntityInfo.Schema; /// /// 表类型 /// diff --git a/src/LightORM/Providers/DeleteProvider.cs b/src/LightORM/Providers/DeleteProvider.cs index 371af3fe..3ef767f5 100644 --- a/src/LightORM/Providers/DeleteProvider.cs +++ b/src/LightORM/Providers/DeleteProvider.cs @@ -1,4 +1,5 @@ -using System.Text; +using LightORM.Extension; +using System.Text; using System.Threading; namespace LightORM.Providers @@ -18,7 +19,7 @@ public DeleteProvider(ISqlExecutor executor, T? entity) sqlBuilder.TargetObject = entity; } - public DeleteProvider(ISqlExecutor executor, IEnumerable entities) + public DeleteProvider(ISqlExecutor executor, T[] entities) { this.executor = executor; sqlBuilder = new DeleteBuilder(); @@ -35,17 +36,62 @@ public int Execute() return executor.ExecuteNonQuery(sql, dbParameters); } - public Task ExecuteAsync(CancellationToken cancellationToken = default) + public async Task ExecuteAsync(CancellationToken cancellationToken = default) { + //var sql = sqlBuilder.ToSqlString(Database); + //var dbParameters = sqlBuilder.DbParameters; + //return executor.ExecuteNonQueryAsync(sql, dbParameters, cancellationToken: cancellationToken); var sql = sqlBuilder.ToSqlString(Database); - var dbParameters = sqlBuilder.DbParameters; - return executor.ExecuteNonQueryAsync(sql, dbParameters, cancellationToken: cancellationToken); + if (sqlBuilder.IsBatchDelete) + { + var usingTransaction = executor.DbTransaction == null; + try + { + var effectRows = 0; + if (usingTransaction) + { + executor.BeginTransaction(); + } + foreach (var item in sqlBuilder.BatchInfos!) + { + effectRows += await executor.ExecuteNonQueryAsync(item.Sql!, item.ToDictionaryParameters(), cancellationToken: cancellationToken).ConfigureAwait(false); + } + if (usingTransaction) + { + await executor.CommitTransactionAsync(cancellationToken).ConfigureAwait(false); + } + return effectRows; + } + catch + { + if (usingTransaction) + { + await executor.RollbackTransactionAsync(cancellationToken).ConfigureAwait(false); + } + throw; + } + + } + else + { + var dbParameters = sqlBuilder.DbParameters; + return await executor.ExecuteNonQueryAsync(sql, dbParameters, cancellationToken: cancellationToken); + } + } - public string ToSql() => sqlBuilder.ToSqlString(Database); - public string ToSqlWithParameters() + public string ToSql() { var sql = sqlBuilder.ToSqlString(Database); + if (sqlBuilder.IsBatchDelete) + { + return string.Join($";{Environment.NewLine}", sqlBuilder.BatchInfos?.Select(b => b.Sql) ?? []); + } + return sql; + } + public string ToSqlWithParameters() + { + var sql = ToSql(); StringBuilder sb = new(sql); sb.AppendLine(); sb.AppendLine("参数列表: "); @@ -53,6 +99,22 @@ public string ToSqlWithParameters() { sb.AppendLine($"{item.Key} - {item.Value}"); } + if (sqlBuilder.IsBatchDelete) + { + foreach (var batch in sqlBuilder.BatchInfos ?? []) + { + sb.AppendLine($"批量删除,批次:{batch.Index}"); + foreach (var item in batch.Parameters) + { + sb.AppendLine("----行数据"); + item.ForEach(row => + { + if (row.isStaticValue) return; + sb.AppendLine($"--------{row.ParameterName} - {row.Value}"); + }); + } + } + } return sb.ToString(); } @@ -77,7 +139,7 @@ public IExpDelete WhereIf(bool condition, Expression> exp) public IExpDelete FullDelete(bool truncate = false) { - sqlBuilder.ForceDelete = true; + sqlBuilder.FullDelete = true; sqlBuilder.Truncate = truncate; return this; } diff --git a/src/LightORM/Providers/InsertProvider.cs b/src/LightORM/Providers/InsertProvider.cs index a19bfeea..a40ef27b 100644 --- a/src/LightORM/Providers/InsertProvider.cs +++ b/src/LightORM/Providers/InsertProvider.cs @@ -17,7 +17,7 @@ public InsertProvider(ISqlExecutor executor, T? entity) sqlBuilder.TargetObject = entity; } - public InsertProvider(ISqlExecutor executor, IEnumerable entities) + public InsertProvider(ISqlExecutor executor, T[] entities) { this.executor = executor; sqlBuilder = new InsertBuilder(); @@ -131,11 +131,19 @@ public IExpInsert SetColumns(Expression> columns) return this; } - public string ToSql() => sqlBuilder.ToSqlString(Database); + public string ToSql() + { + var sql = sqlBuilder.ToSqlString(Database); + if (sqlBuilder.IsBatchInsert) + { + return string.Join($";{Environment.NewLine}", sqlBuilder.BatchInfos?.Select(b => b.Sql) ?? []); + } + return sql; + } public string ToSqlWithParameters() { - var sql = sqlBuilder.ToSqlString(Database); + var sql = ToSql(); StringBuilder sb = new(sql); sb.AppendLine(); sb.AppendLine("参数列表: "); @@ -143,6 +151,22 @@ public string ToSqlWithParameters() { sb.AppendLine($"{item.Key} - {item.Value}"); } + if (sqlBuilder.IsBatchInsert) + { + foreach (var batch in sqlBuilder.BatchInfos ?? []) + { + sb.AppendLine($"批量插入,批次:{batch.Index}"); + foreach (var item in batch.Parameters) + { + sb.AppendLine("----行数据"); + item.ForEach(row => + { + if (row.isStaticValue) return; + sb.AppendLine($"--------{row.ParameterName} - {row.Value}"); + }); + } + } + } return sb.ToString(); } } diff --git a/src/LightORM/Providers/UpdateProvider.cs b/src/LightORM/Providers/UpdateProvider.cs index 5361329e..a7738e33 100644 --- a/src/LightORM/Providers/UpdateProvider.cs +++ b/src/LightORM/Providers/UpdateProvider.cs @@ -17,7 +17,7 @@ public UpdateProvider(ISqlExecutor executor, T? entity) sqlBuilder.TargetObject = entity; } - public UpdateProvider(ISqlExecutor executor, IEnumerable entities) + public UpdateProvider(ISqlExecutor executor, T[] entities) { this.executor = executor; sqlBuilder = new UpdateBuilder(); @@ -198,11 +198,19 @@ public IExpUpdate WhereIf(bool condition, Expression> exp) } return this; } - public string ToSql() => sqlBuilder.ToSqlString(Database); + public string ToSql() + { + var sql = sqlBuilder.ToSqlString(Database); + if (sqlBuilder.IsBatchUpdate) + { + return string.Join($";{Environment.NewLine}", sqlBuilder.BatchInfos?.Select(b => b.Sql) ?? []); + } + return sql; + } public string ToSqlWithParameters() { - var sql = sqlBuilder.ToSqlString(Database); + var sql = ToSql(); StringBuilder sb = new(sql); sb.AppendLine(); sb.AppendLine("参数列表: "); @@ -210,6 +218,22 @@ public string ToSqlWithParameters() { sb.AppendLine($"{item.Key} - {item.Value}"); } + if (sqlBuilder.IsBatchUpdate) + { + foreach (var batch in sqlBuilder.BatchInfos ?? []) + { + sb.AppendLine($"批量更新,批次:{batch.Index}"); + foreach (var item in batch.Parameters) + { + sb.AppendLine("----行数据"); + item.ForEach(row => + { + if (row.isStaticValue) return; + sb.AppendLine($"--------{row.ParameterName} - {row.Value}"); + }); + } + } + } return sb.ToString(); } diff --git a/src/LightORM/Utils/DictionaryHelper.cs b/src/LightORM/Utils/DictionaryHelper.cs index 9988e280..16bad858 100644 --- a/src/LightORM/Utils/DictionaryHelper.cs +++ b/src/LightORM/Utils/DictionaryHelper.cs @@ -7,6 +7,7 @@ internal static class DictionaryHelper public static void TryAddDictionary(this IDictionary dic, IDictionary? other) { if (other == null) return; + if (other.Count == 0) return; foreach (var kv in other) { if (dic.ContainsKey(kv.Key)) diff --git a/src/LightORM/Utils/ExpressionResolver.cs b/src/LightORM/Utils/ExpressionResolver.cs index 134c6fd9..f274a020 100644 --- a/src/LightORM/Utils/ExpressionResolver.cs +++ b/src/LightORM/Utils/ExpressionResolver.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; +using System.Runtime.CompilerServices; using System.Text; namespace LightORM; @@ -54,7 +55,7 @@ internal class ExpressionResolver(SqlResolveOptions options, ResolveContext cont public SqlResolveOptions Options { get; } = options; public ResolveContext Context { get; } = context; public List DbParameters { get; set; } = []; - public StringBuilder Sql { get; set; } = new StringBuilder(); + public StringBuilder Sql { get; set; } = new StringBuilder(128); public Stack Members { get; set; } = []; public List ResolvedMembers { get; set; } = []; public List? WindowFnPartials { get; set; } @@ -128,15 +129,6 @@ bool ResolveNullValue bool isVisitConvert; - //bool ShouldApplyUseAs(Expression exp, string columnName) - //{ - // if (exp is MethodCallExpression) - // { - // return true; - // } - // return lastResolvedColumnName != columnName; - //} - Expression? VisitLambda(LambdaExpression exp) { Debug.WriteLineIf(ShowExpressionResolveDebugInfo, $"{Options.SqlAction} {Options.SqlType}: LambdaExpression: {exp}"); @@ -158,11 +150,9 @@ bool ResolveNullValue // 数组访问 if (exp.NodeType == ExpressionType.ArrayIndex) { - var index = ExtractConstantInt(exp.Right); - var array = Expression.Lambda(exp.Left).Compile().DynamicInvoke() as Array; + var index = ExtractInstanceValue(exp.Right); + var array = ExtractInstanceValue(exp.Left); var arrayValue = array!.GetValue(index); - //var parameterName = AddDbParameter("Const", ); - //Sql.Append(parameterName); var pname = FormatDbParameterName(Context, Options, $"Arr{index}", ref parameterPositionIndex); Sql.Append(pname); DbParameters.Add(new(pname, arrayValue, ExpValueType.Other)); @@ -187,9 +177,9 @@ bool ResolveNullValue return null; // 尝试将表达式求值为常量(仅支持 Constant 和 简单 Member 访问) - static int ExtractConstantInt(Expression expression) + static T ExtractInstanceValue(Expression expression) { - if (expression is ConstantExpression ce && ce.Value is int index) + if (expression is ConstantExpression ce && ce.Value is T index) { return index; } @@ -219,15 +209,11 @@ static int ExtractConstantInt(Expression expression) value = GetValue(members, value); - // 4. 转为 int - return value switch + if (value is T t) { - int i => i, - long l when l >= int.MinValue && l <= int.MaxValue => (int)l, - short s => s, - byte b => b, - _ => throw new LightOrmException($"索引值必须是整数类型,实际类型: {value?.GetType()}") - }; + return t; + } + throw new LightOrmException($"尝试获取类型{typeof(T)}的值,实际类型: {value?.GetType()}"); } } @@ -259,6 +245,10 @@ static int ExtractConstantInt(Expression expression) //{ // UseAs = true; //} + if (exp.Method.Name == "op_Implicit" && exp.Method.IsSpecialName) + { + return exp.Arguments[0]; + } MethodResolver.Resolve(this, exp); } return null; @@ -289,7 +279,11 @@ static int ExtractConstantInt(Expression expression) { Visit(arg); if (UseAs) - Sql.Append($" AS {Database.AttachEmphasis(member.Name)}"); + { + Sql.Append(AS_LITERAL); + } + //Sql.Append($" AS {Database.AttachEmphasis(member.Name)}"); + Sql.AppendEmphasis(member.Name, Database); } else if (Options.SqlType == SqlPartial.Insert) { @@ -326,17 +320,10 @@ static int ExtractConstantInt(Expression expression) if (Options.SqlType == SqlPartial.Select) { var alias = Context.GetTable(exp).Alias; - Sql.Append($"{alias}.*"); + //Sql.Append($"{alias}.*"); + Sql.Append(alias); + Sql.Append(".*"); UseAs = false; - //foreach (var item in alias.Columns) - //{ - // var prop = Expression.Property(exp, item.PropertyName); - // Visit(prop); - // if (UseAs) - // { - // Sql.Append() - // } - //} } else if (Options.SqlAction == SqlAction.Insert) { @@ -459,12 +446,11 @@ static int ExtractConstantInt(Expression expression) //} if (Options.RequiredTableAlias) { - Sql.Append($"{table.Alias}.{Database.AttachEmphasis(col.ColumnName)}"); - } - else - { - Sql.Append($"{Database.AttachEmphasis(col.ColumnName)}"); + Sql.Append(table.Alias); + Sql.Append('.'); + //Sql.Append($"{table.Alias}.{Database.AttachEmphasis(col.ColumnName)}"); } + Sql.AppendEmphasis(col.ColumnName, Database); //lastResolvedColumnName = col.ColumnName; Members.Clear(); return null; @@ -524,10 +510,6 @@ void ConstraintValue(object? v) { if (v == null) { - //var name = FormatDbParameterName(Context, Options, "CONST", ref parameterPositionIndex); - //Sql.Append(name); - //DbParameters.Add(new(name, null, ExpValueType.Null)); - //return; Sql.Append("NULL"); ResolveNullValue = true; return; @@ -542,7 +524,7 @@ void ConstraintValue(object? v) { if (exp.Type.IsNumber()) { - Sql.Append($"{v}"); + Sql.Append(v.ToString()); } else if (exp.Type.IsBoolean() && v is bool b) { @@ -558,30 +540,14 @@ void ConstraintValue(object? v) } else { - //var parameterName = AddDbParameter("Const", value); - Sql.Append($"'{v}'"); + Sql.Append('\''); + Sql.Append(v.ToString()); + Sql.Append('\''); } } - //if (Options.SqlAction == SqlAction.Select) - //{ - // UseAs = true; - //} } } - //private string AddDbParameter(string name, object v) - //{ - // if (v is string s && !Options.Parameterized) - // { - // return $"'{s}'"; - // } - // // TODO 非参数化模式 - // var parameterName = $"{Context.ParameterPrefix}{name}_{Options.ParameterPartialIndex}"; - // DbParameters.Add(parameterName, v); - // Options.ParameterPartialIndex++; - // return Database.AttachPrefix(parameterName); - //} - public static string FormatDbParameterName(ResolveContext? context, SqlResolveOptions? option, string name, ref int index) { var p = $"{context?.ParameterPrefix}{name}_{option?.ParameterPartialIndex}_{index}"; @@ -649,20 +615,14 @@ public static string GetValueName(Stack memberInfos) /// 成员信息 /// 对象 /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static object? GetValue(MemberInfo memberInfo, object? obj) { return memberInfo switch { PropertyInfo prop => prop.GetValue(obj), FieldInfo field => field.GetValue(obj), - //PropertyInfo prop when obj is not null => prop.GetValue(obj), - //PropertyInfo staticProp when obj is null => staticProp.GetValue(null), - //FieldInfo field when obj is not null => field.GetValue(obj), - //FieldInfo staticField when obj is null => staticField.GetValue(null), _ => throw new NotSupportedException($"不支持获取 {memberInfo.MemberType} 类型值.") }; } - - - } diff --git a/src/LightORM/Versions.props b/src/LightORM/Versions.props index 11fdd362..e9015487 100644 --- a/src/LightORM/Versions.props +++ b/src/LightORM/Versions.props @@ -1,6 +1,6 @@  - 3.1.7.9-pre4 + 3.2.0 $(BuildVersion) $(BuildVersion) diff --git a/src/LightOrmTableContextGenerator/TableContextGenerator.cs b/src/LightOrmTableContextGenerator/TableContextGenerator.cs index 15a333a5..f3827eac 100644 --- a/src/LightOrmTableContextGenerator/TableContextGenerator.cs +++ b/src/LightOrmTableContextGenerator/TableContextGenerator.cs @@ -86,8 +86,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context) // public string? TargetDatabase => null; lightTable.GetNamedValue("DatabaseKey", out var dbKey); + lightTable.GetNamedValue("Schema", out var schema); members.Add(PropertyBuilder.Default.MemberType("string?").PropertyName("TargetDatabase").Lambda($"{(dbKey == null ? "null" : $"\"{dbKey}\"")}")); - + members.Add(PropertyBuilder.Default.MemberType("string?").PropertyName("Schema").Lambda($"{(dbKey == null ? "null": $"\"{schema}\"")}")); // public string? Description => null; var desValue = "null"; if (des?.GetNamedValue("Description", out var description) == true) diff --git a/src/Providers/LightORM.Providers.Dameng/DamengMethodResolver.cs b/src/Providers/LightORM.Providers.Dameng/DamengMethodResolver.cs index 485d23d1..10a3c4dd 100644 --- a/src/Providers/LightORM.Providers.Dameng/DamengMethodResolver.cs +++ b/src/Providers/LightORM.Providers.Dameng/DamengMethodResolver.cs @@ -66,23 +66,8 @@ public override void StartsWith(IExpressionResolver resolver, MethodCallExpressi public override void Contains(IExpressionResolver resolver, MethodCallExpression methodCall) { - if (methodCall.Object != null && (methodCall.Object.Type.FullName?.StartsWith("System.Collections.Generic") ?? false)) - { - resolver.Visit(methodCall.Arguments[0]); - resolver.Sql.Append(resolver.IsNot ? " NOT IN " : " IN "); - resolver.Sql.Append('('); - resolver.Visit(methodCall.Object); - resolver.Sql.Append(')'); - } - else if (methodCall.Method.DeclaringType == typeof(Enumerable)) - { - resolver.Visit(methodCall.Arguments[1]); - resolver.Sql.Append(resolver.IsNot ? " NOT IN " : " IN "); - resolver.Sql.Append('('); - resolver.Visit(methodCall.Arguments[0]); - resolver.Sql.Append(')'); - } - else + + if (methodCall.Method.DeclaringType == typeof(string)) { // 字符串 resolver.Visit(methodCall.Object); @@ -91,6 +76,25 @@ public override void Contains(IExpressionResolver resolver, MethodCallExpression resolver.Visit(methodCall.Arguments[0]); resolver.Sql.Append(" || '%'"); } + else + { + if (methodCall.Method.IsStatic) + { + resolver.Visit(methodCall.Arguments[1]); + resolver.Sql.Append(resolver.IsNot ? " NOT IN " : " IN "); + resolver.Sql.Append('('); + resolver.Visit(methodCall.Arguments[0]); + resolver.Sql.Append(')'); + } + else + { + resolver.Visit(methodCall.Arguments[0]); + resolver.Sql.Append(resolver.IsNot ? " NOT IN " : " IN "); + resolver.Sql.Append('('); + resolver.Visit(methodCall.Object); + resolver.Sql.Append(')'); + } + } } public override void EndsWith(IExpressionResolver resolver, MethodCallExpression methodCall) diff --git a/src/Providers/LightORM.Providers.Dameng/Versions.props b/src/Providers/LightORM.Providers.Dameng/Versions.props index 1a0c604b..2cca2265 100644 --- a/src/Providers/LightORM.Providers.Dameng/Versions.props +++ b/src/Providers/LightORM.Providers.Dameng/Versions.props @@ -1,5 +1,5 @@ - 0.0.5 + 0.0.6 \ No newline at end of file diff --git a/src/Providers/LightORM.Providers.MySql/MySqlMethodResolver.cs b/src/Providers/LightORM.Providers.MySql/MySqlMethodResolver.cs index 7fc630f0..a7e8697c 100644 --- a/src/Providers/LightORM.Providers.MySql/MySqlMethodResolver.cs +++ b/src/Providers/LightORM.Providers.MySql/MySqlMethodResolver.cs @@ -67,23 +67,8 @@ public override void StartsWith(IExpressionResolver resolver, MethodCallExpressi public override void Contains(IExpressionResolver resolver, MethodCallExpression methodCall) { - if (methodCall.Object != null && (methodCall.Object.Type.FullName?.StartsWith("System.Collections.Generic") ?? false)) - { - resolver.Visit(methodCall.Arguments[0]); - resolver.Sql.Append(resolver.IsNot ? " NOT IN " : " IN "); - resolver.Sql.Append('('); - resolver.Visit(methodCall.Object); - resolver.Sql.Append(')'); - } - else if (methodCall.Method.DeclaringType == typeof(Enumerable)) - { - resolver.Visit(methodCall.Arguments[1]); - resolver.Sql.Append(resolver.IsNot ? " NOT IN " : " IN "); - resolver.Sql.Append('('); - resolver.Visit(methodCall.Arguments[0]); - resolver.Sql.Append(')'); - } - else + + if (methodCall.Method.DeclaringType == typeof(string)) { // 字符串 resolver.Visit(methodCall.Object); @@ -92,6 +77,26 @@ public override void Contains(IExpressionResolver resolver, MethodCallExpression resolver.Visit(methodCall.Arguments[0]); resolver.Sql.Append(", '%')"); } + else + { + if (methodCall.Method.IsStatic) + { + resolver.Visit(methodCall.Arguments[1]); + resolver.Sql.Append(resolver.IsNot ? " NOT IN " : " IN "); + resolver.Sql.Append('('); + resolver.Visit(methodCall.Arguments[0]); + resolver.Sql.Append(')'); + + } + else + { + resolver.Visit(methodCall.Arguments[0]); + resolver.Sql.Append(resolver.IsNot ? " NOT IN " : " IN "); + resolver.Sql.Append('('); + resolver.Visit(methodCall.Object); + resolver.Sql.Append(')'); + } + } } public override void EndsWith(IExpressionResolver resolver, MethodCallExpression methodCall) diff --git a/src/Providers/LightORM.Providers.MySql/Versions.props b/src/Providers/LightORM.Providers.MySql/Versions.props index fe71bb24..43ff3297 100644 --- a/src/Providers/LightORM.Providers.MySql/Versions.props +++ b/src/Providers/LightORM.Providers.MySql/Versions.props @@ -1,5 +1,5 @@ - 0.1.3 + 0.1.4 \ No newline at end of file diff --git a/src/Providers/LightORM.Providers.Oracle/OracleMethodResolver.cs b/src/Providers/LightORM.Providers.Oracle/OracleMethodResolver.cs index 2ac0d263..afd54a7e 100644 --- a/src/Providers/LightORM.Providers.Oracle/OracleMethodResolver.cs +++ b/src/Providers/LightORM.Providers.Oracle/OracleMethodResolver.cs @@ -63,23 +63,7 @@ public override void StartsWith(IExpressionResolver resolver, MethodCallExpressi public override void Contains(IExpressionResolver resolver, MethodCallExpression methodCall) { - if (methodCall.Object != null && (methodCall.Object.Type.FullName?.StartsWith("System.Collections.Generic") ?? false)) - { - resolver.Visit(methodCall.Arguments[0]); - resolver.Sql.Append(resolver.IsNot ? " NOT IN " : " IN "); - resolver.Sql.Append('('); - resolver.Visit(methodCall.Object); - resolver.Sql.Append(')'); - } - else if (methodCall.Method.DeclaringType == typeof(Enumerable)) - { - resolver.Visit(methodCall.Arguments[1]); - resolver.Sql.Append(resolver.IsNot ? " NOT IN " : " IN "); - resolver.Sql.Append('('); - resolver.Visit(methodCall.Arguments[0]); - resolver.Sql.Append(')'); - } - else + if (methodCall.Method.DeclaringType == typeof(string)) { // 字符串 resolver.Visit(methodCall.Object); @@ -88,6 +72,25 @@ public override void Contains(IExpressionResolver resolver, MethodCallExpression resolver.Visit(methodCall.Arguments[0]); resolver.Sql.Append("|| '%'"); } + else + { + if (methodCall.Method.IsStatic) + { + resolver.Visit(methodCall.Arguments[1]); + resolver.Sql.Append(resolver.IsNot ? " NOT IN " : " IN "); + resolver.Sql.Append('('); + resolver.Visit(methodCall.Arguments[0]); + resolver.Sql.Append(')'); + } + else + { + resolver.Visit(methodCall.Arguments[0]); + resolver.Sql.Append(resolver.IsNot ? " NOT IN " : " IN "); + resolver.Sql.Append('('); + resolver.Visit(methodCall.Object); + resolver.Sql.Append(')'); + } + } } diff --git a/src/Providers/LightORM.Providers.Oracle/Versions.props b/src/Providers/LightORM.Providers.Oracle/Versions.props index 43ff3297..85774828 100644 --- a/src/Providers/LightORM.Providers.Oracle/Versions.props +++ b/src/Providers/LightORM.Providers.Oracle/Versions.props @@ -1,5 +1,5 @@ - 0.1.4 + 0.1.5 \ No newline at end of file diff --git a/src/Providers/LightORM.Providers.PostgreSQL/PostgreSQLMethodResolver.cs b/src/Providers/LightORM.Providers.PostgreSQL/PostgreSQLMethodResolver.cs index a2e78245..8708ddd9 100644 --- a/src/Providers/LightORM.Providers.PostgreSQL/PostgreSQLMethodResolver.cs +++ b/src/Providers/LightORM.Providers.PostgreSQL/PostgreSQLMethodResolver.cs @@ -61,23 +61,7 @@ public override void StartsWith(IExpressionResolver resolver, MethodCallExpressi } public override void Contains(IExpressionResolver resolver, MethodCallExpression methodCall) { - if (methodCall.Object != null && (methodCall.Object.Type.FullName?.StartsWith("System.Collections.Generic") ?? false)) - { - resolver.Visit(methodCall.Arguments[0]); - resolver.Sql.Append(resolver.IsNot ? " NOT IN " : " IN "); - resolver.Sql.Append('('); - resolver.Visit(methodCall.Object); - resolver.Sql.Append(')'); - } - else if (methodCall.Method.DeclaringType == typeof(Enumerable)) - { - resolver.Visit(methodCall.Arguments[1]); - resolver.Sql.Append(resolver.IsNot ? " NOT IN " : " IN "); - resolver.Sql.Append('('); - resolver.Visit(methodCall.Arguments[0]); - resolver.Sql.Append(')'); - } - else + if (methodCall.Method.DeclaringType == typeof(string)) { // 字符串 resolver.Visit(methodCall.Object); @@ -86,6 +70,25 @@ public override void Contains(IExpressionResolver resolver, MethodCallExpression resolver.Visit(methodCall.Arguments[0]); resolver.Sql.Append("|| '%'"); } + else + { + if (methodCall.Method.IsStatic) + { + resolver.Visit(methodCall.Arguments[1]); + resolver.Sql.Append(resolver.IsNot ? " NOT IN " : " IN "); + resolver.Sql.Append('('); + resolver.Visit(methodCall.Arguments[0]); + resolver.Sql.Append(')'); + } + else + { + resolver.Visit(methodCall.Arguments[0]); + resolver.Sql.Append(resolver.IsNot ? " NOT IN " : " IN "); + resolver.Sql.Append('('); + resolver.Visit(methodCall.Object); + resolver.Sql.Append(')'); + } + } } public override void EndsWith(IExpressionResolver resolver, MethodCallExpression methodCall) { diff --git a/src/Providers/LightORM.Providers.PostgreSQL/Versions.props b/src/Providers/LightORM.Providers.PostgreSQL/Versions.props index cc7d2b73..c50229ab 100644 --- a/src/Providers/LightORM.Providers.PostgreSQL/Versions.props +++ b/src/Providers/LightORM.Providers.PostgreSQL/Versions.props @@ -1,5 +1,5 @@ - 0.0.8 + 0.0.9 \ No newline at end of file diff --git a/src/Providers/LightORM.Providers.SqlServer/SqlServerMethodResolver.cs b/src/Providers/LightORM.Providers.SqlServer/SqlServerMethodResolver.cs index eb075cf3..85816093 100644 --- a/src/Providers/LightORM.Providers.SqlServer/SqlServerMethodResolver.cs +++ b/src/Providers/LightORM.Providers.SqlServer/SqlServerMethodResolver.cs @@ -65,23 +65,7 @@ public override void StartsWith(IExpressionResolver resolver, MethodCallExpressi public override void Contains(IExpressionResolver resolver, MethodCallExpression methodCall) { - if (methodCall.Object != null && (methodCall.Object.Type.FullName?.StartsWith("System.Collections.Generic") ?? false)) - { - resolver.Visit(methodCall.Arguments[0]); - resolver.Sql.Append(resolver.IsNot ? " NOT IN " : " IN "); - resolver.Sql.Append('('); - resolver.Visit(methodCall.Object); - resolver.Sql.Append(')'); - } - else if (methodCall.Method.DeclaringType == typeof(Enumerable)) - { - resolver.Visit(methodCall.Arguments[1]); - resolver.Sql.Append(resolver.IsNot ? " NOT IN " : " IN "); - resolver.Sql.Append('('); - resolver.Visit(methodCall.Arguments[0]); - resolver.Sql.Append(')'); - } - else + if (methodCall.Method.DeclaringType == typeof(string)) { // 字符串 resolver.Visit(methodCall.Object); @@ -90,6 +74,25 @@ public override void Contains(IExpressionResolver resolver, MethodCallExpression resolver.Visit(methodCall.Arguments[0]); resolver.Sql.Append("+'%'"); } + else + { + if (methodCall.Method.IsStatic) + { + resolver.Visit(methodCall.Arguments[1]); + resolver.Sql.Append(resolver.IsNot ? " NOT IN " : " IN "); + resolver.Sql.Append('('); + resolver.Visit(methodCall.Arguments[0]); + resolver.Sql.Append(')'); + } + else + { + resolver.Visit(methodCall.Arguments[0]); + resolver.Sql.Append(resolver.IsNot ? " NOT IN " : " IN "); + resolver.Sql.Append('('); + resolver.Visit(methodCall.Object); + resolver.Sql.Append(')'); + } + } } public override void EndsWith(IExpressionResolver resolver, MethodCallExpression methodCall) diff --git a/src/Providers/LightORM.Providers.SqlServer/Versions.props b/src/Providers/LightORM.Providers.SqlServer/Versions.props index 43ff3297..85774828 100644 --- a/src/Providers/LightORM.Providers.SqlServer/Versions.props +++ b/src/Providers/LightORM.Providers.SqlServer/Versions.props @@ -1,5 +1,5 @@ - 0.1.4 + 0.1.5 \ No newline at end of file diff --git a/src/Providers/LightORM.Providers.Sqlite/SqliteMethodResolver.cs b/src/Providers/LightORM.Providers.Sqlite/SqliteMethodResolver.cs index 4fbd4c72..48e5bf38 100644 --- a/src/Providers/LightORM.Providers.Sqlite/SqliteMethodResolver.cs +++ b/src/Providers/LightORM.Providers.Sqlite/SqliteMethodResolver.cs @@ -77,23 +77,7 @@ public override void StartsWith(IExpressionResolver resolver, MethodCallExpressi public override void Contains(IExpressionResolver resolver, MethodCallExpression methodCall) { - if (methodCall.Object != null && (methodCall.Object.Type.FullName?.StartsWith("System.Collections.Generic") ?? false)) - { - resolver.Visit(methodCall.Arguments[0]); - resolver.Sql.Append(resolver.IsNot ? " NOT IN " : " IN "); - resolver.Sql.Append('('); - resolver.Visit(methodCall.Object); - resolver.Sql.Append(')'); - } - else if (methodCall.Method.DeclaringType == typeof(Enumerable)) - { - resolver.Visit(methodCall.Arguments[1]); - resolver.Sql.Append(resolver.IsNot ? " NOT IN " : " IN "); - resolver.Sql.Append('('); - resolver.Visit(methodCall.Arguments[0]); - resolver.Sql.Append(')'); - } - else + if (methodCall.Method.DeclaringType == typeof(string)) { // 字符串 resolver.Visit(methodCall.Object); @@ -102,6 +86,25 @@ public override void Contains(IExpressionResolver resolver, MethodCallExpression resolver.Visit(methodCall.Arguments[0]); resolver.Sql.Append("||'%'"); } + else + { + if (methodCall.Method.IsStatic) + { + resolver.Visit(methodCall.Arguments[1]); + resolver.Sql.Append(resolver.IsNot ? " NOT IN " : " IN "); + resolver.Sql.Append('('); + resolver.Visit(methodCall.Arguments[0]); + resolver.Sql.Append(')'); + } + else + { + resolver.Visit(methodCall.Arguments[0]); + resolver.Sql.Append(resolver.IsNot ? " NOT IN " : " IN "); + resolver.Sql.Append('('); + resolver.Visit(methodCall.Object); + resolver.Sql.Append(')'); + } + } } public override void EndsWith(IExpressionResolver resolver, MethodCallExpression methodCall) diff --git a/src/Providers/LightORM.Providers.Sqlite/Versions.props b/src/Providers/LightORM.Providers.Sqlite/Versions.props index fe71bb24..43ff3297 100644 --- a/src/Providers/LightORM.Providers.Sqlite/Versions.props +++ b/src/Providers/LightORM.Providers.Sqlite/Versions.props @@ -1,5 +1,5 @@ - 0.1.3 + 0.1.4 \ No newline at end of file diff --git a/test/LightORMTest.PostgreSQL/SelectResult.cs b/test/LightORMTest.PostgreSQL/SelectResult.cs new file mode 100644 index 00000000..e793703f --- /dev/null +++ b/test/LightORMTest.PostgreSQL/SelectResult.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LightORMTest.PostgreSQL; + +[TestClass] +public class SelectResult : LightORMTest.ResultTest.Select +{ + public override DbBaseType DbType => DbBaseType.PostgreSQL; + + protected override void Configura(IExpressionContextSetup option) + { + option.UsePostgreSQL(ConnectString.Value); + option.UseInterceptor(); + } +} diff --git a/test/LightORMTest/Models/User.cs b/test/LightORMTest/Models/User.cs index 63029f29..6403460a 100644 --- a/test/LightORMTest/Models/User.cs +++ b/test/LightORMTest/Models/User.cs @@ -15,7 +15,7 @@ public class User /// /// 自增ID /// - [LightColumn(Name = "ID", PrimaryKey = true, Comment = "自增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; } diff --git a/test/LightORMTest/Models/UserFlat.cs b/test/LightORMTest/Models/UserFlat.cs index 0be50663..f89d69e7 100644 --- a/test/LightORMTest/Models/UserFlat.cs +++ b/test/LightORMTest/Models/UserFlat.cs @@ -20,6 +20,9 @@ public class UserFlat [LightNavigate(ManyToMany = typeof(UserRole), MainName = nameof(UserId), SubName = nameof(UserRole.UserId))] public IEnumerable UserRoles { get; set; } = []; + [LightColumn(Name = "VER", Version = true)] + public int Version { get; set; } + [LightFlat] public PrivateInfo? PriInfo { get; set; } } diff --git a/test/LightORMTest/ResultTest/Select.cs b/test/LightORMTest/ResultTest/Select.cs index 720abdbb..4d578686 100644 --- a/test/LightORMTest/ResultTest/Select.cs +++ b/test/LightORMTest/ResultTest/Select.cs @@ -42,6 +42,7 @@ await db.Insert().InsertEachAsync([new User() Age = 12, IsLock = true, Password = "helloworld", + Sign = SignType.Svip }]); await db.Insert().InsertEachAsync([new UserRole() { @@ -97,7 +98,7 @@ await DoSomethingWithTempUserDataAsync(Db, async () => .Where(u => u.Age < age) .Count(out var total) .ToListAsync(); - Assert.IsTrue(total == 2); + Assert.AreEqual(2, total); Console.WriteLine($"Total: {total}"); foreach (var item in list) { @@ -128,28 +129,28 @@ await DoSomethingWithTempUserDataAsync(Db, async () => .Include(u => u.UserRoles) .Where(u => u.UserId == "test01") .FirstAsync(); - Assert.IsFalse(result is null); - Assert.IsFalse(result.UserRoles is null); - Assert.IsTrue(result.UserRoles.Count() == 1); + Assert.IsNotNull(result); + Assert.IsNotNull(result.UserRoles); + Assert.AreEqual(1, result.UserRoles.Count()); result = await Db.Select() .Include(u => u.UserRoles.Where(r => r.RoleId.StartsWith("Ad"))) .Where(u => u.UserId == "test03") .FirstAsync(); - Assert.IsFalse(result is null); - Assert.IsFalse(result.UserRoles is null); - Assert.IsTrue(result.UserRoles.Count() == 1); - Assert.IsTrue(result.UserRoles.FirstOrDefault()!.RoleId == "Admin"); + Assert.IsNotNull(result); + Assert.IsNotNull(result.UserRoles); + Assert.AreEqual(1, result.UserRoles.Count()); + Assert.AreEqual("Admin", result.UserRoles.FirstOrDefault()!.RoleId); result = await Db.Select() .Include(u => u.UserRoles.Where(r => r.RoleId.StartsWith("Su"))) .Where(u => u.UserId == "test03") .FirstAsync(); - Assert.IsFalse(result is null); - Assert.IsFalse(result.UserRoles is null); - Assert.IsTrue(result.UserRoles.Count() == 1); - Assert.IsTrue(result.UserRoles.FirstOrDefault()!.RoleId == "SuperAdmin"); + Assert.IsNotNull(result); + Assert.IsNotNull(result.UserRoles); + Assert.AreEqual(1, result.UserRoles.Count()); + Assert.AreEqual("SuperAdmin", result.UserRoles.FirstOrDefault()!.RoleId); }); } @@ -159,9 +160,9 @@ public async Task SelectSingleStringColumn() await DoSomethingWithTempUserDataAsync(Db, async () => { var list = await Db.Select().ToListAsync(u => u.UserName); - Assert.IsTrue(list.Count == 4); - Assert.IsTrue(list[0] == "Test1"); - Assert.IsTrue(list[1] == "Test2"); + Assert.HasCount(4, list); + Assert.AreEqual("Test1", list[0]); + Assert.AreEqual("Test2", list[1]); }); } @@ -171,9 +172,9 @@ public async Task SelectSingleIntColumn() await DoSomethingWithTempUserDataAsync(Db, async () => { var list = await Db.Select().ToListAsync(u => u.Age); - Assert.IsTrue(list.Count == 4); - Assert.IsTrue(list[0] == 11); - Assert.IsTrue(list[1] == 9); + Assert.HasCount(4, list); + Assert.AreEqual(11, list[0]); + Assert.AreEqual(9, list[1]); }); } @@ -183,10 +184,10 @@ public async Task SelectAnonymousResult() await DoSomethingWithTempUserDataAsync(Db, async () => { var list = await Db.Select().ToListAsync(u => new { u.UserId, u.UserName, u.Age }); - Assert.IsTrue(list.Count == 4); - Assert.IsTrue(list[0].UserId == "test01"); - Assert.IsTrue(list[0].UserName == "Test1"); - Assert.IsTrue(list[0].Age == 11); + Assert.HasCount(4, list); + Assert.AreEqual("test01", list[0].UserId); + Assert.AreEqual("Test1", list[0].UserName); + Assert.AreEqual(11, list[0].Age); }); } @@ -205,9 +206,76 @@ await DoSomethingWithTempUserDataAsync(Db, async () => MaxAge = g.Max(g.Tables.Age), MinAge = g.Min(g.Tables.Age), }); - Assert.IsTrue(list.Count == 2); + Assert.HasCount(2, list); Assert.IsTrue(list.Any(r => r.IsLock == true && r.Total == 2 && r.AvgAge == 10.5 && r.MaxAge == 12 && r.MinAge == 9)); Assert.IsTrue(list.Any(r => r.IsLock == false && r.Total == 2 && r.AvgAge == 9.5 && r.MaxAge == 11 && r.MinAge == 8)); }); } + + + class UserDto + { + public string? UserId { get; set; } + public SignType Sign { get; set; } + } + [TestMethod] + public async Task SelectProjection() + { + await DoSomethingWithTempUserDataAsync(Db, async () => + { + var dto = await Db.Select() + .Where(u => u.UserId == "test04") + .ToListAsync(u => new { u.UserId, u.Sign }); + Assert.HasCount(1, dto); + Assert.AreEqual(SignType.Svip, dto[0].Sign); + }); + } + + [TestMethod] + public async Task UpdateTest() + { + await DoSomethingWithTempUserDataAsync(Db, async () => + { + var users = await Db.Select().ToListAsync(); + var oldValue = users.Sum(u => u.Age); + foreach (var item in users) + { + item.Age *= 2; + } + await Db.Update([.. users]).UpdateColumns(u => u.Age).Set(u => u.Password, "123").ExecuteAsync(); + var newUsers = await Db.Select().ToListAsync(); + var newValue = newUsers.Sum(u => u.Age); + Assert.IsTrue(newUsers.All(u => u.Password == "123")); + Assert.AreEqual(oldValue * 2, newValue); + }); + } + + [TestMethod] + public async Task DeleteTest() + { + await DoSomethingWithTempUserDataAsync(Db, async () => + { + var users = await Db.Select().ToListAsync(); + var cc = users.Count; + var less10 = users.Where(u => u.Age < 10).ToArray(); + // 批量删除 + var dc = await Db.Delete(less10).ExecuteAsync(); + users = await Db.Select().ToListAsync(); + Assert.HasCount(cc - dc, users); + var vip = users.Where(u => u.Sign == SignType.Svip).First(); + + dc = await Db.Delete(vip).Where(u => u.Age > 20).ExecuteAsync(); + Assert.AreEqual(0, dc); + + var id = -1; + dc = await Db.Delete(vip).Where(u => u.Id == id).ExecuteAsync(); + Assert.AreEqual(0, dc); + + dc = await Db.Delete(vip).ExecuteAsync(); + Assert.AreEqual(1, dc); + + dc = await Db.Delete().Where(u => u.UserRoles.Any(ur => ur.RoleId == "Admin")).ExecuteAsync(); + Assert.AreEqual(1, dc); + }); + } } diff --git a/test/LightORMTest/SqlGenerate/InsertSql.cs b/test/LightORMTest/SqlGenerate/InsertSql.cs index c6009267..fa996aca 100644 --- a/test/LightORMTest/SqlGenerate/InsertSql.cs +++ b/test/LightORMTest/SqlGenerate/InsertSql.cs @@ -37,6 +37,13 @@ public void Insert_Flat_Entity() Console.WriteLine(sql); } + [TestMethod] + public void Insert_Flat_Entity_Array() + { + var sql = Db.Insert([new UserFlat(), new UserFlat(), new UserFlat(), new UserFlat()]).ToSql(); + Console.WriteLine(sql); + } + [TestMethod] public void InsertEntity_AutoIncrement() { diff --git a/test/LightORMTest/SqlGenerate/SelectSql.cs b/test/LightORMTest/SqlGenerate/SelectSql.cs index ad412708..e30abd26 100644 --- a/test/LightORMTest/SqlGenerate/SelectSql.cs +++ b/test/LightORMTest/SqlGenerate/SelectSql.cs @@ -93,13 +93,7 @@ public void TestMultiJoinThenAsSubJoin() var sql = Db.Select() .LeftJoin(w => w.Tb1.UserId == w.Tb2.PermissionId) .OuterJoin(temp, (u, _, t) => u.UserId == t.UserId) - .ToSql(w => new - { - w.Tb1.UserId, - w.Tb1.UserName, - w.Tb2.PermissionName, - w.Tb3.RoleName - }); + .ToSqlWithParameters(); Console.WriteLine(sql); AssertSqlResult(nameof(TestMultiJoinThenAsSubJoin), sql); } @@ -530,7 +524,7 @@ public void TestArrayAccess() int? i = GetIndex(); var ii = new { index = 5 }; var sql = Db.Select() - .Where(u => u.Age == arr[i.Value]) + .Where(u => u.Age == arr[0]) .ToSql(); Console.WriteLine(sql); int GetIndex() diff --git a/test/LightORMTest/SqlGenerate/UpdateSql.cs b/test/LightORMTest/SqlGenerate/UpdateSql.cs index 6e5d0108..f4f92724 100644 --- a/test/LightORMTest/SqlGenerate/UpdateSql.cs +++ b/test/LightORMTest/SqlGenerate/UpdateSql.cs @@ -99,7 +99,7 @@ public void Update_Flat_Column() var sql = Db.Update(p) .Set(s => s.PriInfo.Age, 100) .Where(s => s.PriInfo.Address == "123") - .ToSql(); + .ToSqlWithParameters(); Console.WriteLine(sql); //var result = """ // UPDATE `SMS_LOG` SET @@ -146,7 +146,8 @@ public void Update_Batch() var sql = Db.Update([.. datas]) .UpdateColumns(u => u.UserName) .Set(u => u.Sign, sign) - .ToSql(); + .Where(u => u.PriInfo.Age > 100) + .ToSqlWithParameters(); Console.WriteLine(sql); List GetList() { diff --git a/test/LightORMTest/TestBase.cs b/test/LightORMTest/TestBase.cs index d63c3ce6..ee76f052 100644 --- a/test/LightORMTest/TestBase.cs +++ b/test/LightORMTest/TestBase.cs @@ -56,7 +56,31 @@ public class LightOrmAop : AdoInterceptorBase { public override void AfterExecute(SqlExecuteContext context) { - Debug.WriteLine($"{context.TraceId} {Environment.NewLine}{context.Sql} {Environment.NewLine}耗时:{context.Elapsed}"); + //Debug.WriteLine($"{context.TraceId} {Environment.NewLine}{context.Sql} {Environment.NewLine}耗时:{context.Elapsed}"); + + Debug.WriteLine($""" + + {context.TraceId} + SQL: + {context.Sql} + =============== + 参数: + {string.Join($" {Environment.NewLine}", DisplayParameter(context.Parameter))} + + 耗时:{context.Elapsed} + + """); + + IEnumerable DisplayParameter(object? p) + { + if (p is Dictionary dic) + { + foreach (var item in dic) + { + yield return $"{item.Key} - {item.Value}"; + } + } + } } public override void BeforeExecute(SqlExecuteContext context)