diff --git a/Directory.Build.props b/Directory.Build.props index 03a940e9..14935b0e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -9,7 +9,7 @@ - 3.2.651 + 3.2.660-preview22020525 diff --git a/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteSelectTest.cs b/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteSelectTest.cs index c438ae01..2e1300c0 100644 --- a/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteSelectTest.cs +++ b/FreeSql.Tests/FreeSql.Tests/Sqlite/Curd/SqliteSelectTest.cs @@ -1001,10 +1001,15 @@ limit 0,10", t1); var subquery = select.ToSql(a => new { all = a, - count = (long)select.As("b").Sum(b => b.Id) + count = (long)select.As("b").Sum(b => b.Id), + sum2 = (long)select.As("b").Limit(10).Sum(b => b.Id) }); Assert.Equal(@"SELECT a.""Id"" as1, a.""Clicks"" as2, a.""TypeGuid"" as3, a.""Title"" as4, a.""CreateTime"" as5, ifnull((SELECT sum(b.""Id"") - FROM ""tb_topic22"" b), 0) as6 + FROM ""tb_topic22"" b), 0) as6, ifnull((SELECT sum(ftba.""Id"") FROM ( + SELECT b.""Id"" + FROM ""tb_topic22"" b + limit 0,10 +) ftba), 0) as7 FROM ""tb_topic22"" a", subquery); var subqueryList = select.ToList(a => new { diff --git a/FreeSql/Internal/CommonExpression.cs b/FreeSql/Internal/CommonExpression.cs index c9b60954..72815953 100644 --- a/FreeSql/Internal/CommonExpression.cs +++ b/FreeSql/Internal/CommonExpression.cs @@ -1362,9 +1362,15 @@ namespace FreeSql.Internal var exp3Args0 = (exp3.Arguments.FirstOrDefault() as UnaryExpression)?.Operand as LambdaExpression; if (exp3Args0.Parameters.Count == 1 && exp3Args0.Parameters[0].Type.FullName.StartsWith("FreeSql.Internal.Model.HzyTuple`")) exp3Args0 = new ReplaceHzyTupleToMultiParam().Modify(exp3Args0, fsqltables); - var sqlSum = fsqlType.GetMethod("ToSql", new Type[] { typeof(string) })?.Invoke(fsql, new object[] { $"{exp3.Method.Name.ToLower()}({ExpressionLambdaToSql(exp3Args0, tscClone1)})" })?.ToString(); + var sqlSumField = $"{exp3.Method.Name.ToLower()}({ExpressionLambdaToSql(exp3Args0, tscClone1)})"; + var sqlSum = tscClone1.subSelect001._limit <= 0 && tscClone1.subSelect001._skip <= 0 ? + fsqlType.GetMethod("ToSql", new Type[] { typeof(string) })?.Invoke(fsql, new object[] { $"{exp3.Method.Name.ToLower()}({ExpressionLambdaToSql(exp3Args0, tscClone1)})" })?.ToString() : + tscClone1.subSelect001.GetNestSelectSql(exp3Args0, sqlSumField, tosqlField => + fsqlType.GetMethod("ToSql", new Type[] { typeof(string) })?.Invoke(fsql, new object[] { tosqlField })?.ToString()); if (string.IsNullOrEmpty(sqlSum) == false) - return _common.IsNull($"({sqlSum.Replace(" \r\n", " \r\n ")})", 0); + return tscClone1.subSelect001._limit <= 0 && tscClone1.subSelect001._skip <= 0 ? + _common.IsNull($"({sqlSum.Replace(" \r\n", " \r\n ")})", 0) : + _common.IsNull($"({sqlSum})", 0); break; case "ToList": case "ToOne": diff --git a/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs b/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs index 308f0353..5b4f2268 100644 --- a/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs +++ b/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs @@ -243,6 +243,113 @@ namespace FreeSql.Internal.CommonProvider } return newExp; } + + public ReadAnonymousTypeAfInfo GetExpressionField(Expression newexp, FieldAliasOptions fieldAlias = FieldAliasOptions.AsIndex) + { + var map = new ReadAnonymousTypeInfo(); + var field = new StringBuilder(); + var index = fieldAlias == FieldAliasOptions.AsProperty ? CommonExpression.ReadAnonymousFieldAsCsName : 0; + + _commonExpression.ReadAnonymousField(_tables, field, map, ref index, newexp, this, null, _whereGlobalFilter, null, null, true); + return new ReadAnonymousTypeAfInfo(map, field.Length > 0 ? field.Remove(0, 2).ToString() : null); + } + public string GetNestSelectSql(Expression select, string affield, Func ToSql) + { + var allMemExps = new FindAllMemberExpressionVisitor(this); + allMemExps.Visit(select); + var fieldAlias = new Dictionary(); + var fieldReplaced = new Dictionary(); + var field = new StringBuilder(); + foreach (var memExp in allMemExps.Result) + { + var gef = GetExpressionField(memExp.Item1, FieldAliasOptions.AsProperty); + var geffield = gef.field; + if (fieldReplaced.ContainsKey(geffield)) continue; + fieldReplaced.Add(geffield, true); + + field.Append(", ").Append(gef.field); + if (fieldAlias.ContainsKey(memExp.Item2.Attribute.Name)) + { + field.Append(_commonUtils.FieldAsAlias($"aas{fieldAlias.Count}")); + affield = affield.Replace(geffield, $"ftba.aas{fieldAlias.Count}"); + } + else + { + fieldAlias.Add(memExp.Item2.Attribute.Name, true); + affield = affield.Replace(geffield, $"ftba.{string.Join(".", geffield.Split('.').Skip(1))}"); + } + } + + var sql = ToSql(field.Remove(0, 2).ToString()); + sql = $"{_select} {affield} FROM ( \r\n {sql.Replace("\r\n", "\r\n ")}\r\n) ftba"; + field.Clear(); + return sql; + } + public class FindAllMemberExpressionVisitor : ExpressionVisitor + { + public List> Result { get; set; } = new List>(); + Select0Provider _select; + public FindAllMemberExpressionVisitor(Select0Provider select) => _select = select; + + protected override Expression VisitMember(MemberExpression node) + { + var exps = new Stack(); + Expression exp = node; + while (exp != null) + { + switch (exp.NodeType) + { + case ExpressionType.Parameter: + exps.Push(exp); + exp = null; + continue; + case ExpressionType.MemberAccess: + exps.Push(exp); + exp = (exp as MemberExpression)?.Expression; + continue; + } + return base.VisitMember(node); + } + if (exps.Any() == false) return base.VisitMember(node); + var firstExp = exps.Pop() as ParameterExpression; + if (firstExp == null) return base.VisitMember(node); + var tb = _select._tables.Find(a => a.Parameter == firstExp)?.Table; + if (tb == null) return base.VisitMember(node); + + while (exps.Any()) + { + var memExp = exps.Pop() as MemberExpression; + if (tb.ColumnsByCs.TryGetValue(memExp.Member.Name, out var trycol) && exps.Any() == false) + { + Result.Add(NativeTuple.Create(node, trycol)); + return node; + } + if (tb.Properties.ContainsKey(memExp.Member.Name)) + { + tb = _select._commonUtils.GetTableByEntity(memExp.Type); + if (tb == null) return base.VisitMember(node); + } + } + return base.VisitMember(node); + } + } + public class ReplaceMemberExpressionVisitor : ExpressionVisitor + { + Expression _findExp; + Expression _replaceExp; + public Expression Replace(Expression exp, Expression find, Expression replace) // object repval) + { + _findExp = find; + _replaceExp = replace; + //_replaceExp = Expression.Constant(repval, find.Type); + return this.Visit(exp); + } + protected override Expression VisitMember(MemberExpression node) + { + if (_findExp == node) return _replaceExp; + return base.VisitMember(node); + } + } } public abstract partial class Select0Provider : Select0Provider, ISelect0 where TSelect : class diff --git a/FreeSql/Internal/CommonProvider/SelectProvider/Select0ProviderReader.cs b/FreeSql/Internal/CommonProvider/SelectProvider/Select0ProviderReader.cs index 12988a7b..9af516b6 100644 --- a/FreeSql/Internal/CommonProvider/SelectProvider/Select0ProviderReader.cs +++ b/FreeSql/Internal/CommonProvider/SelectProvider/Select0ProviderReader.cs @@ -64,11 +64,12 @@ namespace FreeSql.Internal.CommonProvider return ret; } - public List ToList(string field) + public List ToList(string field) => ToListQfPrivate(this.ToSql(field), field); + public List ToListQfPrivate(string sql, string field) { var ret = new List(); if (_cancel?.Invoke() == true) return ret; - var sql = this.ToSql(field); + if (string.IsNullOrEmpty(sql)) return ret; var dbParms = _params.ToArray(); var type = typeof(TTuple); var before = new Aop.CurdBeforeEventArgs(_tables[0].Table.Type, _tables[0].Table, Aop.CurdType.Select, sql, dbParms); @@ -394,15 +395,6 @@ namespace FreeSql.Internal.CommonProvider return ToListMrPrivate(sql, af, otherData); } protected List ToListMapReader(ReadAnonymousTypeAfInfo af) => ToListMapReaderPrivate(af, null); - protected ReadAnonymousTypeAfInfo GetExpressionField(Expression newexp, FieldAliasOptions fieldAlias = FieldAliasOptions.AsIndex) - { - var map = new ReadAnonymousTypeInfo(); - var field = new StringBuilder(); - var index = fieldAlias == FieldAliasOptions.AsProperty ? CommonExpression.ReadAnonymousFieldAsCsName : 0; - - _commonExpression.ReadAnonymousField(_tables, field, map, ref index, newexp, this, null, _whereGlobalFilter, null, null, true); - return new ReadAnonymousTypeAfInfo(map, field.Length > 0 ? field.Remove(0, 2).ToString() : null); - } static ConcurrentDictionary _dicGetAllFieldExpressionTree = new ConcurrentDictionary(); public class GetAllFieldExpressionTreeInfo { @@ -771,12 +763,41 @@ namespace FreeSql.Internal.CommonProvider protected double InternalAvg(Expression exp) { - var list = this.ToList($"avg({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}"); - return list.Sum() / list.Count; + var field = $"avg({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}"; + if (_limit <= 0 && _skip <= 0) + { + var list = this.ToList(field); + return list.Sum() / list.Count; + } + + var sql = GetNestSelectSql(exp, field, ToSql); + var list2 = ToListQfPrivate(sql, field); + return list2.Sum() / list2.Count; + } + protected TMember InternalMax(Expression exp) + { + var field = $"max({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}"; + if (_limit <= 0 && _skip <= 0) return this.ToList(field).Max(); + + var sql = GetNestSelectSql(exp, field, ToSql); + return ToListQfPrivate(sql, field).Max(); + } + protected TMember InternalMin(Expression exp) + { + var field = $"min({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}"; + if (_limit <= 0 && _skip <= 0) return this.ToList(field).Min(); + + var sql = GetNestSelectSql(exp, field, ToSql); + return ToListQfPrivate(sql, field).Min(); + } + protected decimal InternalSum(Expression exp) + { + var field = $"sum({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}"; + if (_limit <= 0 && _skip <= 0) return this.ToList(field).Sum(); + + var sql = GetNestSelectSql(exp, field, ToSql); + return ToListQfPrivate(sql, field).Sum(); } - protected TMember InternalMax(Expression exp) => this.ToList($"max({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}").Max(); - protected TMember InternalMin(Expression exp) => this.ToList($"min({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}").Min(); - protected decimal InternalSum(Expression exp) => this.ToList($"sum({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}").Sum(); public ISelectGrouping InternalGroupBy(Expression columns) { @@ -829,71 +850,6 @@ namespace FreeSql.Internal.CommonProvider return this.OrderBy($"{_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, column, true, null)} DESC"); } - class FindAllMemberExpressionVisitor : ExpressionVisitor - { - public List> Result { get; set; } = new List>(); - Select0Provider _select; - public FindAllMemberExpressionVisitor(Select0Provider select) => _select = select; - - protected override Expression VisitMember(MemberExpression node) - { - var exps = new Stack(); - Expression exp = node; - while (exp != null) - { - switch (exp.NodeType) - { - case ExpressionType.Parameter: - exps.Push(exp); - exp = null; - continue; - case ExpressionType.MemberAccess: - exps.Push(exp); - exp = (exp as MemberExpression)?.Expression; - continue; - } - return base.VisitMember(node); - } - if (exps.Any() == false) return base.VisitMember(node); - var firstExp = exps.Pop() as ParameterExpression; - if (firstExp == null) return base.VisitMember(node); - var tb = _select._tables.Find(a => a.Parameter == firstExp)?.Table; - if (tb == null) return base.VisitMember(node); - - while (exps.Any()) - { - var memExp = exps.Pop() as MemberExpression; - if (tb.ColumnsByCs.TryGetValue(memExp.Member.Name, out var trycol) && exps.Any() == false) - { - Result.Add(NativeTuple.Create(node, trycol)); - return node; - } - if (tb.Properties.ContainsKey(memExp.Member.Name)) - { - tb = _select._commonUtils.GetTableByEntity(memExp.Type); - if (tb == null) return base.VisitMember(node); - } - } - return base.VisitMember(node); - } - } - class ReplaceMemberExpressionVisitor : ExpressionVisitor - { - Expression _findExp; - Expression _replaceExp; - public Expression Replace(Expression exp, Expression find, Expression replace) // object repval) - { - _findExp = find; - _replaceExp = replace; - //_replaceExp = Expression.Constant(repval, find.Type); - return this.Visit(exp); - } - protected override Expression VisitMember(MemberExpression node) - { - if (_findExp == node) return _replaceExp; - return base.VisitMember(node); - } - } public List InternalToList(Expression select) { var map = new ReadAnonymousTypeInfo(); @@ -1087,34 +1043,7 @@ namespace FreeSql.Internal.CommonProvider var af = new ReadAnonymousTypeAfInfo(map, field.Length > 0 ? field.Remove(0, 2).ToString() : null); if (GetTableRuleUnions().Count <= 1) return this.ToListMapReader(af).FirstOrDefault(); - var affield = af.field; - var allMemExps = new FindAllMemberExpressionVisitor(this); - allMemExps.Visit(select); - field.Clear(); - var fieldAlias = new Dictionary(); - var fieldReplaced = new Dictionary(); - foreach (var memExp in allMemExps.Result) - { - var gef = GetExpressionField(memExp.Item1, FieldAliasOptions.AsProperty); - var geffield = gef.field; - if (fieldReplaced.ContainsKey(geffield)) continue; - fieldReplaced.Add(geffield, true); - - field.Append(", ").Append(gef.field); - if (fieldAlias.ContainsKey(memExp.Item2.Attribute.Name)) - { - field.Append(_commonUtils.FieldAsAlias($"aas{fieldAlias.Count}")); - affield = affield.Replace(geffield, $"ftba.aas{fieldAlias.Count}"); - } - else - { - fieldAlias.Add(memExp.Item2.Attribute.Name, true); - affield = affield.Replace(geffield, $"ftba.{string.Join(".", geffield.Split('.').Skip(1))}"); - } - } - - var sql = this.ToSql(field.Remove(0, 2).ToString()); - sql = $"{_select} {affield} FROM ( \r\n {sql.Replace("\r\n", "\r\n ")}\r\n) ftba"; + var sql = GetNestSelectSql(select, af.field, ToSql); return ToListMrPrivate(sql, af, null).FirstOrDefault(); } finally @@ -1173,11 +1102,12 @@ namespace FreeSql.Internal.CommonProvider return ret; } - async public Task> ToListAsync(string field, CancellationToken cancellationToken) + public Task> ToListAsync(string field, CancellationToken cancellationToken) => ToListQfPrivateAsync(this.ToSql(field), field, cancellationToken); + async public Task> ToListQfPrivateAsync(string sql, string field, CancellationToken cancellationToken) { var ret = new List(); if (_cancel?.Invoke() == true) return ret; - var sql = this.ToSql(field); + if (string.IsNullOrEmpty(sql)) return ret; var dbParms = _params.ToArray(); var type = typeof(TTuple); var before = new Aop.CurdBeforeEventArgs(_tables[0].Table.Type, _tables[0].Table, Aop.CurdType.Select, sql, dbParms); @@ -1380,12 +1310,41 @@ namespace FreeSql.Internal.CommonProvider async protected Task InternalAvgAsync(Expression exp, CancellationToken cancellationToken) { - var list = await this.ToListAsync($"avg({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}", cancellationToken); - return list.Sum() / list.Count; + var field = $"avg({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}"; + if (_limit <= 0 && _skip <= 0) + { + var list = await this.ToListAsync(field, cancellationToken); + return list.Sum() / list.Count; + } + + var sql = GetNestSelectSql(exp, field, ToSql); + var list2 = await ToListQfPrivateAsync(sql, field, cancellationToken); + return list2.Sum() / list2.Count; + } + async protected Task InternalMaxAsync(Expression exp, CancellationToken cancellationToken) + { + var field = $"max({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}"; + if (_limit <= 0 && _skip <= 0) return (await this.ToListAsync(field, cancellationToken)).Max(); + + var sql = GetNestSelectSql(exp, field, ToSql); + return (await ToListQfPrivateAsync(sql, field, cancellationToken)).Max(); + } + async protected Task InternalMinAsync(Expression exp, CancellationToken cancellationToken) + { + var field = $"min({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}"; + if (_limit <= 0 && _skip <= 0) return (await this.ToListAsync(field, cancellationToken)).Min(); + + var sql = GetNestSelectSql(exp, field, ToSql); + return (await ToListQfPrivateAsync(sql, field, cancellationToken)).Min(); + } + async protected Task InternalSumAsync(Expression exp, CancellationToken cancellationToken) + { + var field = $"sum({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}"; + if (_limit <= 0 && _skip <= 0) return (await this.ToListAsync(field, cancellationToken)).Sum(); + + var sql = GetNestSelectSql(exp, field, ToSql); + return (await ToListQfPrivateAsync(sql, field, cancellationToken)).Sum(); } - async protected Task InternalMaxAsync(Expression exp, CancellationToken cancellationToken) => (await this.ToListAsync($"max({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}", cancellationToken)).Max(); - async protected Task InternalMinAsync(Expression exp, CancellationToken cancellationToken) => (await this.ToListAsync($"min({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}", cancellationToken)).Min(); - async protected Task InternalSumAsync(Expression exp, CancellationToken cancellationToken) => (await this.ToListAsync($"sum({_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, exp, true, null)}){_commonUtils.FieldAsAlias("as1")}", cancellationToken)).Sum(); static ConcurrentDictionary _dicGetMethodsByName = new ConcurrentDictionary(); async protected Task> InternalToListAsync(Expression select, CancellationToken cancellationToken) @@ -1577,34 +1536,7 @@ namespace FreeSql.Internal.CommonProvider var af = new ReadAnonymousTypeAfInfo(map, field.Length > 0 ? field.Remove(0, 2).ToString() : null); if (GetTableRuleUnions().Count <= 1) return (await this.ToListMapReaderAsync(af, cancellationToken)).FirstOrDefault(); - var affield = af.field; - var allMemExps = new FindAllMemberExpressionVisitor(this); - allMemExps.Visit(select); - field.Clear(); - var fieldAlias = new Dictionary(); - var fieldReplaced = new Dictionary(); - foreach (var memExp in allMemExps.Result) - { - var gef = GetExpressionField(memExp.Item1, FieldAliasOptions.AsProperty); - var geffield = gef.field; - if (fieldReplaced.ContainsKey(geffield)) continue; - fieldReplaced.Add(geffield, true); - - field.Append(", ").Append(gef.field); - if (fieldAlias.ContainsKey(memExp.Item2.Attribute.Name)) - { - field.Append(_commonUtils.FieldAsAlias($"aas{fieldAlias.Count}")); - affield = affield.Replace(geffield, $"ftba.aas{fieldAlias.Count}"); - } - else - { - fieldAlias.Add(memExp.Item2.Attribute.Name, true); - affield = affield.Replace(geffield, $"ftba.{string.Join(".", geffield.Split('.').Skip(1))}"); - } - } - - var sql = this.ToSql(field.Remove(0, 2).ToString()); - sql = $"{_select} {affield} FROM ( \r\n {sql.Replace("\r\n", "\r\n ")}\r\n) ftba"; + var sql = GetNestSelectSql(select, af.field, ToSql); return (await ToListMrPrivateAsync(sql, af, null, cancellationToken)).FirstOrDefault(); } finally