diff --git a/Examples/base_entity/Program.cs b/Examples/base_entity/Program.cs index 4df5ae7c..d2b4ccb5 100644 --- a/Examples/base_entity/Program.cs +++ b/Examples/base_entity/Program.cs @@ -209,7 +209,7 @@ namespace base_entity //.UseConnectionString(FreeSql.DataType.OdbcDameng, "Driver={DM8 ODBC DRIVER};Server=127.0.0.1:5236;Persist Security Info=False;Trusted_Connection=Yes;UID=USER1;PWD=123456789") - .UseMonitorCommand(null, (umcmd, log) => Console.WriteLine(umcmd.Connection.ConnectionString + ":" + umcmd.CommandText)) + .UseMonitorCommand(null, (umcmd, log) => Console.WriteLine(umcmd.Connection.ConnectionString + ":" + umcmd.CommandText + "\r\n")) .UseLazyLoading(true) .UseGenerateCommandParameterWithLambda(true) .Build(); @@ -289,17 +289,26 @@ namespace base_entity }); sql1 = sql1.Replace("INNER JOIN ", "FULL JOIN "); - //fsql.Select() - // .ToList(a => new - // { - // users1 = fsql.Select().Where(b => b.GroupId == a.Id).ToList(false), - // users2 = fsql.Select().Where(b => b.GroupId == a.Id).ToList(b => new - // { - // userid = b.Id, - // username = b.Username - // }), - // //users3 = fsql.Ado.Query("select * from user1 where groupid = @id", new { id = a.Id }) - // }); + var tinc01 = fsql.Select().IncludeMany(a => a.User1s.Where(b => b.GroupId == a.Id)).ToList(); + var tsub01 = fsql.Select() + .ToList(a => new + { + users1 = fsql.Select().Where(b => b.GroupId == a.Id).ToList(), + users2 = fsql.Select().Where(b => b.GroupId == a.Id).ToList(b => new + { + userid = b.Id, + username = b.Username + }), + users3 = fsql.Select().Limit(10).ToList(), + users4 = fsql.Select().Limit(10).ToList(b => new + { + userid = b.Id, + username = b.Username + }) + //users3 = fsql.Ado.Query("select * from user1 where groupid = @id", new { id = a.Id }) + }); + + diff --git a/Extensions/FreeSql.Extensions.Linq/SelectedQueryProvider.cs b/Extensions/FreeSql.Extensions.Linq/SelectedQueryProvider.cs index 5c3124a8..bc2428ca 100644 --- a/Extensions/FreeSql.Extensions.Linq/SelectedQueryProvider.cs +++ b/Extensions/FreeSql.Extensions.Linq/SelectedQueryProvider.cs @@ -67,7 +67,7 @@ namespace FreeSql.Internal.CommonProvider var index = -10000; //临时规则,不返回 as1 if (selector != null) - _comonExp.ReadAnonymousField(_select._tables, field, _map, ref index, selector, null, null, _select._whereGlobalFilter, null, false); //不走 DTO 映射,不处理 IncludeMany + _comonExp.ReadAnonymousField(_select._tables, field, _map, ref index, selector, null, null, _select._whereGlobalFilter, null, null, false); //不走 DTO 映射,不处理 IncludeMany _field = field.ToString(); } diff --git a/FreeSql/FreeSql.xml b/FreeSql/FreeSql.xml index 03fb1cc6..3a83da5e 100644 --- a/FreeSql/FreeSql.xml +++ b/FreeSql/FreeSql.xml @@ -1871,7 +1871,7 @@ - + 执行SQL查询,返回 T1 实体所有字段的记录,记录不存在时返回 Count 为 0 的列表 注意: @@ -1880,6 +1880,12 @@ 3、ToList((a, b, c) => new { a, b, c }) 这样也可以 4、abc 怎么来的?请试试 fsql.Select<T1, T2, T3>() + + + + + 执行SQL查询,返回 T1 实体、以及 LeftJoin/InnerJoin/RightJoin 对象 + false: 返回 2级 LeftJoin/InnerJoin/RightJoin 对象;true: 返回所有 LeftJoin/InnerJoin/RightJoin 的导航数据 diff --git a/FreeSql/Interface/Curd/ISelect/ISelect0.cs b/FreeSql/Interface/Curd/ISelect/ISelect0.cs index 1a3e6510..24970727 100644 --- a/FreeSql/Interface/Curd/ISelect/ISelect0.cs +++ b/FreeSql/Interface/Curd/ISelect/ISelect0.cs @@ -97,9 +97,14 @@ namespace FreeSql /// 3、ToList((a, b, c) => new { a, b, c }) 这样也可以 /// 4、abc 怎么来的?请试试 fsql.Select<T1, T2, T3>() /// + /// + List ToList(); //因为 LambdaExpression 不支持默认参数方法,所以与 ToList(includeNestedMembers) 单独定义 + /// + /// 执行SQL查询,返回 T1 实体、以及 LeftJoin/InnerJoin/RightJoin 对象 + /// /// false: 返回 2级 LeftJoin/InnerJoin/RightJoin 对象;true: 返回所有 LeftJoin/InnerJoin/RightJoin 的导航数据 /// - List ToList(bool includeNestedMembers = false); + List ToList(bool includeNestedMembers); /// /// 执行SQL查询,分块返回数据,可减少内存开销。比如读取10万条数据,每次返回100条处理。 /// diff --git a/FreeSql/Internal/CommonExpression.cs b/FreeSql/Internal/CommonExpression.cs index 8977c56e..11de73f2 100644 --- a/FreeSql/Internal/CommonExpression.cs +++ b/FreeSql/Internal/CommonExpression.cs @@ -46,13 +46,14 @@ namespace FreeSql.Internal if (_common.CodeFirst.IsSyncStructureToUpper) csname = csname.ToUpper(); return csname; } - public bool ReadAnonymousField(List _tables, StringBuilder field, ReadAnonymousTypeInfo parent, ref int index, Expression exp, Select0Provider select, BaseDiyMemberExpression diymemexp, List whereGlobalFilter, List findIncludeMany, bool isAllDtoMap) + public bool ReadAnonymousField(List _tables, StringBuilder field, ReadAnonymousTypeInfo parent, ref int index, Expression exp, Select0Provider select, + BaseDiyMemberExpression diymemexp, List whereGlobalFilter, List findIncludeMany, List findSubSelectMany, bool isAllDtoMap) { Func getTSC = () => new ExpTSC { _tables = _tables, diymemexp = diymemexp, tbtype = SelectTableInfoType.From, isQuoteName = true, isDisableDiyParse = false, style = ExpressionStyle.Where, whereGlobalFilter = whereGlobalFilter, dbParams = select?._params }; //#462 添加 DbParams 解决 switch (exp.NodeType) { - case ExpressionType.Quote: return ReadAnonymousField(_tables, field, parent, ref index, (exp as UnaryExpression)?.Operand, select, diymemexp, whereGlobalFilter, findIncludeMany, isAllDtoMap); - case ExpressionType.Lambda: return ReadAnonymousField(_tables, field, parent, ref index, (exp as LambdaExpression)?.Body, select, diymemexp, whereGlobalFilter, findIncludeMany, isAllDtoMap); + case ExpressionType.Quote: return ReadAnonymousField(_tables, field, parent, ref index, (exp as UnaryExpression)?.Operand, select, diymemexp, whereGlobalFilter, findIncludeMany, findSubSelectMany, isAllDtoMap); + case ExpressionType.Lambda: return ReadAnonymousField(_tables, field, parent, ref index, (exp as LambdaExpression)?.Body, select, diymemexp, whereGlobalFilter, findIncludeMany, findSubSelectMany, isAllDtoMap); case ExpressionType.Negate: case ExpressionType.NegateChecked: parent.DbField = $"-({ExpressionLambdaToSql(exp, getTSC())})"; @@ -61,7 +62,7 @@ namespace FreeSql.Internal else if (index == ReadAnonymousFieldAsCsName && string.IsNullOrEmpty(parent.CsName) == false) field.Append(_common.FieldAsAlias(GetFieldAsCsName(parent.CsName))); if (parent.CsType == null && exp.Type.IsValueType) parent.CsType = exp.Type; return false; - case ExpressionType.Convert: return ReadAnonymousField(_tables, field, parent, ref index, (exp as UnaryExpression)?.Operand, select, diymemexp, whereGlobalFilter, findIncludeMany, isAllDtoMap); + case ExpressionType.Convert: return ReadAnonymousField(_tables, field, parent, ref index, (exp as UnaryExpression)?.Operand, select, diymemexp, whereGlobalFilter, findIncludeMany, findSubSelectMany, isAllDtoMap); case ExpressionType.Constant: var constExp = exp as ConstantExpression; //处理自定义SQL语句,如: ToList(new { @@ -84,10 +85,17 @@ namespace FreeSql.Internal case ExpressionType.Conditional: var condExp = exp as ConditionalExpression; if (condExp.Test.IsParameter() == false) return ReadAnonymousField(_tables, field, parent, ref index, - (bool)Expression.Lambda(condExp.Test).Compile().DynamicInvoke() ? condExp.IfTrue : condExp.IfFalse, select, diymemexp, whereGlobalFilter, findIncludeMany, isAllDtoMap); + (bool)Expression.Lambda(condExp.Test).Compile().DynamicInvoke() ? condExp.IfTrue : condExp.IfFalse, select, diymemexp, whereGlobalFilter, findIncludeMany, findSubSelectMany, isAllDtoMap); break; case ExpressionType.Call: var callExp = exp as MethodCallExpression; + if (callExp.Method.Name == "ToList" && callExp.Object?.Type.FullName.StartsWith("FreeSql.ISelect`") == true) + { + parent.SubSelectMany = exp; + parent.CsType = exp.Type.GetGenericArguments().FirstOrDefault(); + findSubSelectMany?.Add(exp); + return false; + } //处理自定义SQL语句,如: ToList(new { // ccc = Convert.ToDateTime("now()"), // partby = Convert.ToDecimal("sum(num) over(PARTITION BY server_id,os,rid,chn order by id desc)") @@ -157,7 +165,7 @@ namespace FreeSql.Internal MapType = memProp.PropertyType }; parent.Childs.Add(child); - ReadAnonymousField(_tables, field, child, ref index, Expression.MakeMemberAccess(exp, memProp), select, diymemexp, whereGlobalFilter, findIncludeMany, false); + ReadAnonymousField(_tables, field, child, ref index, Expression.MakeMemberAccess(exp, memProp), select, diymemexp, whereGlobalFilter, findIncludeMany, findSubSelectMany, false); } } } @@ -231,7 +239,7 @@ namespace FreeSql.Internal MapType = initExp.NewExpression.Arguments[a].Type }; parent.Childs.Add(child); - ReadAnonymousField(_tables, field, child, ref index, initExp.NewExpression.Arguments[a], select, diymemexp, whereGlobalFilter, findIncludeMany, false); + ReadAnonymousField(_tables, field, child, ref index, initExp.NewExpression.Arguments[a], select, diymemexp, whereGlobalFilter, findIncludeMany, findSubSelectMany, false); } } else if (isAllDtoMap && _tables != null && _tables.Any() && initExp.NewExpression.Type != _tables.FirstOrDefault().Table.Type) @@ -256,7 +264,7 @@ namespace FreeSql.Internal }; parent.Childs.Add(child); if (dtTb.Parameter != null) - ReadAnonymousField(_tables, field, child, ref index, Expression.Property(dtTb.Parameter, dtTb.Table.Properties[trydtocol.CsName]), select, diymemexp, whereGlobalFilter, findIncludeMany, isAllDtoMap); + ReadAnonymousField(_tables, field, child, ref index, Expression.Property(dtTb.Parameter, dtTb.Table.Properties[trydtocol.CsName]), select, diymemexp, whereGlobalFilter, findIncludeMany, findSubSelectMany, isAllDtoMap); else { child.DbField = $"{dtTb.Alias}.{_common.QuoteSqlName(trydtocol.Attribute.Name)}"; @@ -283,7 +291,7 @@ namespace FreeSql.Internal }; if (child.Property == null) child.ReflectionField = initExp.Type.GetField(initExp.Bindings[a].Member.Name, BindingFlags.Public | BindingFlags.Instance); parent.Childs.Add(child); - ReadAnonymousField(_tables, field, child, ref index, initAssignExp.Expression, select, diymemexp, whereGlobalFilter, findIncludeMany, false); + ReadAnonymousField(_tables, field, child, ref index, initAssignExp.Expression, select, diymemexp, whereGlobalFilter, findIncludeMany, findSubSelectMany, false); } } if (parent.Childs.Any() == false) throw new Exception(CoreStrings.Mapping_Exception_HasNo_SamePropertyName(initExp.NewExpression.Type.Name)); @@ -316,7 +324,7 @@ namespace FreeSql.Internal MapType = newExp.Arguments[a].Type }; parent.Childs.Add(child); - ReadAnonymousField(_tables, field, child, ref index, newExp.Arguments[a], select, diymemexp, whereGlobalFilter, findIncludeMany, false); + ReadAnonymousField(_tables, field, child, ref index, newExp.Arguments[a], select, diymemexp, whereGlobalFilter, findIncludeMany, findSubSelectMany, false); } } else @@ -340,7 +348,7 @@ namespace FreeSql.Internal }; parent.Childs.Add(child); if (dtTb.Parameter != null) - ReadAnonymousField(_tables, field, child, ref index, Expression.Property(dtTb.Parameter, dtTb.Table.Properties[trydtocol.CsName]), select, diymemexp, whereGlobalFilter, findIncludeMany, isAllDtoMap); + ReadAnonymousField(_tables, field, child, ref index, Expression.Property(dtTb.Parameter, dtTb.Table.Properties[trydtocol.CsName]), select, diymemexp, whereGlobalFilter, findIncludeMany, findSubSelectMany, isAllDtoMap); else { child.DbField = _common.RereadColumn(trydtocol, $"{dtTb.Alias}.{_common.QuoteSqlName(trydtocol.Attribute.Name)}"); @@ -361,9 +369,10 @@ namespace FreeSql.Internal if (parent.CsType == null && exp.Type.IsValueType) parent.CsType = exp.Type; return false; } - public object ReadAnonymous(ReadAnonymousTypeInfo parent, DbDataReader dr, ref int index, bool notRead, ReadAnonymousDbValueRef dbValue, int rowIndex, List> fillIncludeMany) + public object ReadAnonymous(ReadAnonymousTypeInfo parent, DbDataReader dr, ref int index, bool notRead, ReadAnonymousDbValueRef dbValue, int rowIndex, + List> fillIncludeMany, List> fillSubSelectMany) { - if (parent.Childs.Any() == false && string.IsNullOrEmpty(parent.IncludeManyKey)) + if (parent.Childs.Any() == false && string.IsNullOrEmpty(parent.IncludeManyKey) && parent.SubSelectMany == null) { if (notRead) { @@ -389,13 +398,18 @@ namespace FreeSql.Internal ret = typeof(List<>).MakeGenericType(parent.CsType).CreateInstanceGetDefaultValue(); fillIncludeMany?.Add(NativeTuple.Create(parent.IncludeManyKey, ret as IList, rowIndex)); } + else if (parent.SubSelectMany != null) + { + ret = typeof(List<>).MakeGenericType(parent.CsType).CreateInstanceGetDefaultValue(); + fillSubSelectMany?.Add(NativeTuple.Create(parent.SubSelectMany, ret as IList, rowIndex)); + } else if (parent.IsDefaultCtor || parent.IsEntity || (ctorParmsLength = parent.Consturctor.GetParameters()?.Length ?? 0) == 0) ret = parent.CsType?.CreateInstanceGetDefaultValue() ?? parent.Consturctor.Invoke(null); else { var ctorParms = new object[ctorParmsLength]; for (var c = 0; c < ctorParmsLength; c++) - ctorParms[c] = ReadAnonymous(parent.Childs[c], dr, ref index, notRead, null, rowIndex, fillIncludeMany); + ctorParms[c] = ReadAnonymous(parent.Childs[c], dr, ref index, notRead, null, rowIndex, fillIncludeMany, fillSubSelectMany); ret = parent.Consturctor.Invoke(ctorParms); } @@ -403,7 +417,7 @@ namespace FreeSql.Internal for (var b = ctorParmsLength; b < parent.Childs.Count; b++) { var dbval = parent.IsEntity ? new ReadAnonymousDbValueRef() : null; - var objval = ReadAnonymous(parent.Childs[b], dr, ref index, notRead, dbval, rowIndex, fillIncludeMany); + var objval = ReadAnonymous(parent.Childs[b], dr, ref index, notRead, dbval, rowIndex, fillIncludeMany, fillSubSelectMany); if (isnull == false && parent.IsEntity && dbval.DbValue == null && parent.Table != null && parent.Table.ColumnsByCs.TryGetValue(parent.Childs[b].CsName, out var trycol) && trycol.Attribute.IsPrimary) isnull = true; @@ -1262,7 +1276,7 @@ namespace FreeSql.Internal var index = -1; for (var a = 0; a < exp3Args0.Parameters.Count; a++) fsqls0p._tables[a].Parameter = exp3Args0.Parameters[a]; - ReadAnonymousField(fsqls0p._tables, field, map, ref index, exp3Args0, null, null, null, null, false); + ReadAnonymousField(fsqls0p._tables, field, map, ref index, exp3Args0, null, null, null, null, null, false); var fieldSql = field.Length > 0 ? field.Remove(0, 2).ToString() : null; var sql4 = fsqlType.GetMethod("ToSql", new Type[] { typeof(string) })?.Invoke(fsql, new object[] { $"{exp3.Method.Name.ToLower()}({fieldSql})" })?.ToString(); @@ -2134,7 +2148,7 @@ namespace FreeSql.Internal var field = new StringBuilder(); var index = -1; - commonExp.ReadAnonymousField(select._tables, field, map, ref index, callExp.Arguments[1], null, null, null, null, false); + commonExp.ReadAnonymousField(select._tables, field, map, ref index, callExp.Arguments[1], null, null, null, null, null, false); var fieldSql = field.Length > 0 ? field.Remove(0, 2).ToString() : null; e.Result = commonExp._common.IsNull($"({select.ToSql($"{aggregateMethodName}({fieldSql})").Replace(" \r\n", " \r\n ")})", 0); diff --git a/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs b/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs index 102689fa..20fba909 100644 --- a/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs +++ b/FreeSql/Internal/CommonProvider/SelectProvider/Select0Provider.cs @@ -180,6 +180,31 @@ namespace FreeSql.Internal.CommonProvider return methods.Where(a => a.GetParameters()[1].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>)).FirstOrDefault(); return methods.FirstOrDefault(); }); + + + internal static ConcurrentDictionary>> _SameSelectPendingOnlySync = new ConcurrentDictionary>>(); + internal List> CurrentSameSelectPendingOnlySync => _SameSelectPendingOnlySync.TryGetValue(Thread.CurrentThread.ManagedThreadId, out var trycur) ? trycur : null; + internal bool ProcessSameSelectPendingOnlySync(List> cssps, ref string sql, ReadAnonymousTypeOtherInfo csspsod) + { + if (cssps != null) + { + if (cssps.Any() == false || cssps.Last() != null) + { + cssps.Add(NativeTuple.Create(sql, _params.ToArray(), csspsod)); + return true; + } + cssps[cssps.Count - 1] = NativeTuple.Create(sql, _params.ToArray(), csspsod); + var sbSql = new StringBuilder(); //last == null flush flag + for (var a = 0; a < cssps.Count; a++) + sbSql.Append("\r\nUNION ALL\r\nselect * from (").Append(cssps[a].Item1).Append(") ftb"); + sbSql.Remove(0, 13); + if (cssps.Count == 1) sbSql.Remove(0, 15).Remove(sbSql.Length - 5, 5); + sql = sbSql.ToString(); + //dbParms = cssps.Select(a => a.Item2).SelectMany(a => a).ToArray(); + sbSql.Clear(); + } + return false; + } } public abstract partial class Select0Provider : Select0Provider, ISelect0 where TSelect : class @@ -192,6 +217,7 @@ namespace FreeSql.Internal.CommonProvider _tables.Add(new SelectTableInfo { Table = _commonUtils.GetTableByEntity(typeof(T1)), Alias = "a", On = null, Type = SelectTableInfoType.From }); this.Where(_commonUtils.WhereObject(_tables.First().Table, "a.", dywhere)); if (_orm.CodeFirst.IsAutoSyncStructure && typeof(T1) != typeof(object)) _orm.CodeFirst.SyncStructure(); + CurrentSameSelectPendingOnlySync?.ForEach(a => _params.AddRange(a?.Item2 ?? new DbParameter[0])); } public TSelect TrackToList(Action track) @@ -876,7 +902,8 @@ namespace FreeSql.Internal.CommonProvider count = this.Count(); return this as TSelect; } - public virtual List ToList(bool includeNestedMembers = false) + public List ToList() => ToList(false); + public virtual List ToList(bool includeNestedMembers) { if (_selectExpression != null) return this.InternalToList(_selectExpression); return this.ToListPrivate(includeNestedMembers == false ? this.GetAllFieldExpressionTreeLevel2() : this.GetAllFieldExpressionTreeLevelAll(), null); diff --git a/FreeSql/Internal/CommonProvider/SelectProvider/Select0ProviderReader.cs b/FreeSql/Internal/CommonProvider/SelectProvider/Select0ProviderReader.cs index 6de1859e..9d410ba7 100644 --- a/FreeSql/Internal/CommonProvider/SelectProvider/Select0ProviderReader.cs +++ b/FreeSql/Internal/CommonProvider/SelectProvider/Select0ProviderReader.cs @@ -69,8 +69,8 @@ namespace FreeSql.Internal.CommonProvider var ret = new List(); if (_cancel?.Invoke() == true) return ret; var sql = this.ToSql(field); - var type = typeof(TTuple); 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); _orm.Aop.CurdBeforeHandler?.Invoke(this, before); Exception exception = null; @@ -118,7 +118,7 @@ namespace FreeSql.Internal.CommonProvider { var idx = af.FieldCount - 1; foreach (var other in otherData) - other.retlist.Add(_commonExpression.ReadAnonymous(other.read, fetch.Object, ref idx, false, null, retCount, null)); + other.retlist.Add(_commonExpression.ReadAnonymous(other.read, fetch.Object, ref idx, false, null, retCount, null, null)); } retCount++; }, CommandType.Text, sql, _commandTimeout, dbParms); @@ -139,17 +139,28 @@ namespace FreeSql.Internal.CommonProvider } internal List ToListPrivate(GetAllFieldExpressionTreeInfo af, ReadAnonymousTypeOtherInfo[] otherData) { + var cssps = CurrentSameSelectPendingOnlySync; + ReadAnonymousTypeOtherInfo csspsod = null; + if (cssps != null) + { + var ods = new List(); + if (otherData?.Any() == true) ods.AddRange(otherData); + ods.Add(csspsod = new ReadAnonymousTypeOtherInfo($", {(cssps.Any() && cssps.Last() == null ? cssps.Count - 1 : cssps.Count)}{_commonUtils.FieldAsAlias("fsql_subsel_rowidx")}", new ReadAnonymousTypeInfo { CsType = typeof(int) }, new List())); + otherData = ods.ToArray(); + } + string sql = null; if (otherData?.Length > 0) { var sbField = new StringBuilder().Append(af.Field); foreach (var other in otherData) sbField.Append(other.field); - sql = this.ToSql(sbField.ToString()); + sql = this.ToSql(sbField.ToString().TrimStart(',')); } else sql = this.ToSql(af.Field); + if (ProcessSameSelectPendingOnlySync(cssps, ref sql, csspsod)) return new List(); return ToListAfPrivate(sql, af, otherData); } #region ToChunk @@ -172,7 +183,7 @@ namespace FreeSql.Internal.CommonProvider { var idx = af.FieldCount - 1; foreach (var other in otherData) - other.retlist.Add(_commonExpression.ReadAnonymous(other.read, fetch.Object, ref idx, false, null, ret.Object.Count - 1, null)); + other.retlist.Add(_commonExpression.ReadAnonymous(other.read, fetch.Object, ref idx, false, null, ret.Object.Count - 1, null, null)); } retCount++; if (chunkSize > 0 && chunkSize == ret.Object.Count) @@ -216,7 +227,7 @@ namespace FreeSql.Internal.CommonProvider var sbField = new StringBuilder().Append(af.Field); foreach (var other in otherData) sbField.Append(other.field); - sql = this.ToSql(sbField.ToString()); + sql = this.ToSql(sbField.ToString().TrimStart(',')); } else sql = this.ToSql(af.Field); @@ -245,7 +256,7 @@ namespace FreeSql.Internal.CommonProvider _orm.Ado.ExecuteReader(_connection, _transaction, fetch => { var index = -1; - ret.Object.Add((TReturn)_commonExpression.ReadAnonymous(af.map, fetch.Object, ref index, false, null, ret.Object.Count, af.fillIncludeMany)); + ret.Object.Add((TReturn)_commonExpression.ReadAnonymous(af.map, fetch.Object, ref index, false, null, ret.Object.Count, af.fillIncludeMany, af.fillSubSelectMany)); retCount++; if (chunkSize > 0 && chunkSize == ret.Object.Count) { @@ -325,8 +336,8 @@ namespace FreeSql.Internal.CommonProvider { var ret = new List(); if (_cancel?.Invoke() == true) return ret; - var type = typeof(TReturn); var dbParms = _params.ToArray(); + var type = typeof(TReturn); var before = new Aop.CurdBeforeEventArgs(_tables[0].Table.Type, _tables[0].Table, Aop.CurdType.Select, sql, dbParms); _orm.Aop.CurdBeforeHandler?.Invoke(this, before); var retCount = 0; @@ -336,10 +347,10 @@ namespace FreeSql.Internal.CommonProvider _orm.Ado.ExecuteReader(_connection, _transaction, fetch => { var index = -1; - ret.Add((TReturn)_commonExpression.ReadAnonymous(af.map, fetch.Object, ref index, false, null, retCount, af.fillIncludeMany)); + ret.Add((TReturn)_commonExpression.ReadAnonymous(af.map, fetch.Object, ref index, false, null, retCount, af.fillIncludeMany, af.fillSubSelectMany)); if (otherData != null) foreach (var other in otherData) - other.retlist.Add(_commonExpression.ReadAnonymous(other.read, fetch.Object, ref index, false, null, retCount, null)); + other.retlist.Add(_commonExpression.ReadAnonymous(other.read, fetch.Object, ref index, false, null, retCount, null, null)); retCount++; }, CommandType.Text, sql, _commandTimeout, dbParms); } @@ -360,17 +371,28 @@ namespace FreeSql.Internal.CommonProvider } internal List ToListMapReaderPrivate(ReadAnonymousTypeAfInfo af, ReadAnonymousTypeOtherInfo[] otherData) { + var cssps = CurrentSameSelectPendingOnlySync; + ReadAnonymousTypeOtherInfo csspsod = null; + if (cssps != null) + { + var ods = new List(); + if (otherData?.Any() == true) ods.AddRange(otherData); + ods.Add(csspsod = new ReadAnonymousTypeOtherInfo($", {(cssps.Any() && cssps.Last() == null ? cssps.Count - 1 : cssps.Count)}{_commonUtils.FieldAsAlias("fsql_subsel_rowidx")}", new ReadAnonymousTypeInfo { CsType = typeof(int) }, new List())); + otherData = ods.ToArray(); + } + string sql = null; if (otherData?.Length > 0) { var sbField = new StringBuilder().Append(af.field); foreach (var other in otherData) sbField.Append(other.field); - sql = this.ToSql(sbField.ToString()); + sql = this.ToSql(sbField.ToString().TrimStart(',')); } else sql = this.ToSql(af.field); + if (ProcessSameSelectPendingOnlySync(cssps, ref sql, csspsod)) return new List(); return ToListMrPrivate(sql, af, otherData); } protected List ToListMapReader(ReadAnonymousTypeAfInfo af) => ToListMapReaderPrivate(af, null); @@ -380,7 +402,7 @@ namespace FreeSql.Internal.CommonProvider var field = new StringBuilder(); var index = fieldAlias == FieldAliasOptions.AsProperty ? CommonExpression.ReadAnonymousFieldAsCsName : 0; - _commonExpression.ReadAnonymousField(_tables, field, map, ref index, newexp, this, null, _whereGlobalFilter, null, true); + _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(); @@ -731,6 +753,7 @@ namespace FreeSql.Internal.CommonProvider return new GetAllFieldExpressionTreeInfo { Field = field.ToString(), + FieldCount = index, Read = Expression.Lambda>(Expression.Block(new[] { retExp, dataIndexExp, readExp }, blockExp), new[] { ormExp, rowExp }).Compile() }; }); @@ -763,7 +786,7 @@ namespace FreeSql.Internal.CommonProvider var field = new StringBuilder(); var index = -10000; //临时规则,不返回 as1 - _commonExpression.ReadAnonymousField(_tables, field, map, ref index, columns, null, null, _whereGlobalFilter, null, false); //不走 DTO 映射,不处理 IncludeMany + _commonExpression.ReadAnonymousField(_tables, field, map, ref index, columns, null, null, _whereGlobalFilter, null, null, false); //不走 DTO 映射,不处理 IncludeMany var sql = field.ToString(); this.GroupBy(sql.Length > 0 ? sql.Substring(2) : null); return new SelectGroupingProvider(_orm, this, map, sql, _commonExpression, _tables); @@ -808,7 +831,164 @@ namespace FreeSql.Internal.CommonProvider return this.OrderBy($"{_commonExpression.ExpressionSelectColumn_MemberAccess(_tables, null, SelectTableInfoType.From, column, true, null)} DESC"); } - public List InternalToList(Expression select) => this.ToListMapReader(this.GetExpressionField(select)); + 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(); + var field = new StringBuilder(); + var index = 0; + var findSubSelectMany = new List(); + + _commonExpression.ReadAnonymousField(_tables, field, map, ref index, select, this, null, _whereGlobalFilter, null, findSubSelectMany, true); + var af = new ReadAnonymousTypeAfInfo(map, field.Length > 0 ? field.Remove(0, 2).ToString() : null); + if (findSubSelectMany.Any() == false) return this.ToListMapReaderPrivate(af, new ReadAnonymousTypeOtherInfo[0]); + + af.fillSubSelectMany = new List>(); + //查询 SubSelectMany + var otherAfmanys = findSubSelectMany.Select(a => + { + var vst = new FindAllMemberExpressionVisitor(this); + vst.Visit(a); + var finds = vst.Result; + + var afs = new List>(); + foreach (var find in finds) + { + var otherMap = new ReadAnonymousTypeInfo(); + field.Clear(); + _commonExpression.ReadAnonymousField(_tables, field, otherMap, ref index, find.Item1, this, null, _whereGlobalFilter, null, null, true); + var otherRet = new List(); + var otherAf = new ReadAnonymousTypeOtherInfo(field.ToString(), otherMap, otherRet); + afs.Add(NativeTuple.Create(find.Item1, find.Item2, otherAf)); + } + return afs; + }).ToList(); + var otherAfdic = otherAfmanys.SelectMany(a => a).GroupBy(a => a.Item1.ToString()).ToDictionary(a => a.Key, a => a.ToList()); + var otherAfs = otherAfdic.Select(a => a.Value.First().Item3).ToArray(); + var ret = this.ToListMapReaderPrivate(af, otherAfs); + if (ret.Any() == false || otherAfmanys.Any() == false) return ret; + + var rmev = new ReplaceMemberExpressionVisitor(); + + for (var a = 0; a < otherAfmanys.Count; a++) + { + if (otherAfmanys[a].Any() == false) + { + var otherList = Expression.Lambda(findSubSelectMany[a]).Compile().DynamicInvoke() as IEnumerable; + foreach (var otherListItem in otherList) + for (int b = a, c = 0; b < af.fillSubSelectMany?.Count; b += otherAfmanys.Count, c++) + af.fillSubSelectMany[b].Item2.Add(otherListItem); + continue; + } + try + { + _SameSelectPendingOnlySync.TryAdd(Thread.CurrentThread.ManagedThreadId, new List>()); + var newexp = findSubSelectMany[a]; + var newexpParms = otherAfmanys[a].Select(d => + { + var newexpParm = Expression.Parameter(d.Item1.Type); + newexp = rmev.Replace(newexp, d.Item1, newexpParm); + return newexpParm; + }).ToArray(); + var newexpFunc = Expression.Lambda(newexp, newexpParms).Compile(); + + var newexpParamVals = otherAfmanys[a].Select(d => otherAfdic[d.Item1.ToString()].First().Item3.retlist).ToArray(); + for (int b = a, c = 0; b < af.fillSubSelectMany?.Count; b += otherAfmanys.Count, c++) + { + var vals = newexpParamVals.Select(d => d[c]).ToArray(); + if (c == ret.Count - 1) CurrentSameSelectPendingOnlySync.Add(null); //flush flag + var diret = newexpFunc.DynamicInvoke(vals); + + if (c < ret.Count - 1) continue; + var otherList = diret as IEnumerable; + var cssps = CurrentSameSelectPendingOnlySync; + var retlistidx = 0; + foreach (var otherListItem in otherList) + { + var retlist = cssps[0].Item3.retlist; + while (retlistidx >= retlist.Count) + { + cssps.RemoveAt(0); + retlist = cssps[0].Item3.retlist; + retlistidx = 0; + } + int.TryParse(retlist[retlistidx++]?.ToString(), out var tryrowidx); + af.fillSubSelectMany[tryrowidx * otherAfmanys.Count + a].Item2.Add(otherListItem); + } + } + CurrentSameSelectPendingOnlySync.Clear(); + } + finally + { + _SameSelectPendingOnlySync.TryRemove(Thread.CurrentThread.ManagedThreadId, out var oldssps); + } + } + return ret; + } protected string InternalToSql(Expression select, FieldAliasOptions fieldAlias = FieldAliasOptions.AsIndex) { var af = this.GetExpressionField(select, fieldAlias); @@ -826,7 +1006,7 @@ namespace FreeSql.Internal.CommonProvider var field = new StringBuilder(); var index = -10000; //临时规则,不返回 as1 - _commonExpression.ReadAnonymousField(_tables, field, map, ref index, select, null, null, _whereGlobalFilter, null, false); //不走 DTO 映射,不处理 IncludeMany + _commonExpression.ReadAnonymousField(_tables, field, map, ref index, select, null, null, _whereGlobalFilter, null, null, false); //不走 DTO 映射,不处理 IncludeMany var childs = map.Childs; if (childs.Any() == false) throw new ArgumentException(CoreStrings.InsertInto_No_Property_Selected(typeof(TTargetEntity).DisplayCsharp())); @@ -907,7 +1087,7 @@ namespace FreeSql.Internal.CommonProvider var field = new StringBuilder(); var index = 0; - _commonExpression.ReadAnonymousField(_tables, field, map, ref index, select, null, null, _whereGlobalFilter, null, false); //不走 DTO 映射,不处理 IncludeMany + _commonExpression.ReadAnonymousField(_tables, field, map, ref index, select, null, null, _whereGlobalFilter, null, null, false); //不走 DTO 映射,不处理 IncludeMany return this.ToListMapReader(new ReadAnonymousTypeAfInfo(map, field.Length > 0 ? field.Remove(0, 2).ToString() : null)).FirstOrDefault(); } finally @@ -971,8 +1151,8 @@ namespace FreeSql.Internal.CommonProvider var ret = new List(); if (_cancel?.Invoke() == true) return ret; var sql = this.ToSql(field); - var type = typeof(TTuple); 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); _orm.Aop.CurdBeforeHandler?.Invoke(this, before); Exception exception = null; @@ -1022,7 +1202,7 @@ namespace FreeSql.Internal.CommonProvider { var idx = af.FieldCount - 1; foreach (var other in otherData) - other.retlist.Add(_commonExpression.ReadAnonymous(other.read, fetch.Object, ref idx, false, null, retCount, null)); + other.retlist.Add(_commonExpression.ReadAnonymous(other.read, fetch.Object, ref idx, false, null, retCount, null, null)); } retCount++; return Task.FromResult(false); @@ -1051,7 +1231,7 @@ namespace FreeSql.Internal.CommonProvider var sbField = new StringBuilder().Append(af.Field); foreach (var other in otherData) sbField.Append(other.field); - sql = this.ToSql(sbField.ToString()); + sql = this.ToSql(sbField.ToString().TrimStart(',')); } else sql = this.ToSql(af.Field); @@ -1100,8 +1280,8 @@ namespace FreeSql.Internal.CommonProvider { var ret = new List(); if (_cancel?.Invoke() == true) return ret; - var type = typeof(TReturn); var dbParms = _params.ToArray(); + var type = typeof(TReturn); var before = new Aop.CurdBeforeEventArgs(_tables[0].Table.Type, _tables[0].Table, Aop.CurdType.Select, sql, dbParms); _orm.Aop.CurdBeforeHandler?.Invoke(this, before); var retCount = 0; @@ -1111,10 +1291,10 @@ namespace FreeSql.Internal.CommonProvider await _orm.Ado.ExecuteReaderAsync(_connection, _transaction, fetch => { var index = -1; - ret.Add((TReturn)_commonExpression.ReadAnonymous(af.map, fetch.Object, ref index, false, null, retCount, af.fillIncludeMany)); + ret.Add((TReturn)_commonExpression.ReadAnonymous(af.map, fetch.Object, ref index, false, null, retCount, af.fillIncludeMany, af.fillSubSelectMany)); if (otherData != null) foreach (var other in otherData) - other.retlist.Add(_commonExpression.ReadAnonymous(other.read, fetch.Object, ref index, false, null, retCount, null)); + other.retlist.Add(_commonExpression.ReadAnonymous(other.read, fetch.Object, ref index, false, null, retCount, null, null)); retCount++; return Task.FromResult(false); }, CommandType.Text, sql, _commandTimeout, dbParms, cancellationToken); @@ -1142,7 +1322,7 @@ namespace FreeSql.Internal.CommonProvider var sbField = new StringBuilder().Append(af.field); foreach (var other in otherData) sbField.Append(other.field); - sql = this.ToSql(sbField.ToString()); + sql = this.ToSql(sbField.ToString().TrimStart(',')); } else sql = this.ToSql(af.field); @@ -1225,7 +1405,7 @@ namespace FreeSql.Internal.CommonProvider var field = new StringBuilder(); var index = 0; - _commonExpression.ReadAnonymousField(_tables, field, map, ref index, select, null, null, _whereGlobalFilter, null, false); //不走 DTO 映射,不处理 IncludeMany + _commonExpression.ReadAnonymousField(_tables, field, map, ref index, select, null, null, _whereGlobalFilter, null, null, false); //不走 DTO 映射,不处理 IncludeMany return (await this.ToListMapReaderAsync(new ReadAnonymousTypeAfInfo(map, field.Length > 0 ? field.Remove(0, 2).ToString() : null), cancellationToken)).FirstOrDefault(); } finally diff --git a/FreeSql/Internal/CommonProvider/SelectProvider/Select1Provider.cs b/FreeSql/Internal/CommonProvider/SelectProvider/Select1Provider.cs index b95269db..722b44c6 100644 --- a/FreeSql/Internal/CommonProvider/SelectProvider/Select1Provider.cs +++ b/FreeSql/Internal/CommonProvider/SelectProvider/Select1Provider.cs @@ -197,7 +197,7 @@ namespace FreeSql.Internal.CommonProvider var map = new ReadAnonymousTypeInfo(); var field = new StringBuilder(); var index = 0; - _commonExpression.ReadAnonymousField(_tables, field, map, ref index, select.Body, this, null, _whereGlobalFilter, findIncludeMany, true); + _commonExpression.ReadAnonymousField(_tables, field, map, ref index, select.Body, this, null, _whereGlobalFilter, findIncludeMany, null, true); var af = new ReadAnonymousTypeAfInfo(map, field.Length > 0 ? field.Remove(0, 2).ToString() : null); if (findIncludeMany.Any() == false) return this.ToListMapReaderPrivate(af, null); @@ -230,7 +230,7 @@ namespace FreeSql.Internal.CommonProvider var otherMap = new ReadAnonymousTypeInfo(); field.Clear(); - _commonExpression.ReadAnonymousField(_tables, field, otherMap, ref index, otherNewInit, this, null, _whereGlobalFilter, null, true); + _commonExpression.ReadAnonymousField(_tables, field, otherMap, ref index, otherNewInit, this, null, _whereGlobalFilter, null, null, true); var otherRet = new List(); var otherAf = new ReadAnonymousTypeOtherInfo(field.ToString(), otherMap, otherRet); @@ -1238,7 +1238,7 @@ namespace FreeSql.Internal.CommonProvider var map = new ReadAnonymousTypeInfo(); var field = new StringBuilder(); var index = 0; - _commonExpression.ReadAnonymousField(_tables, field, map, ref index, select.Body, this, null, _whereGlobalFilter, findIncludeMany, true); + _commonExpression.ReadAnonymousField(_tables, field, map, ref index, select.Body, this, null, _whereGlobalFilter, findIncludeMany, null, true); var af = new ReadAnonymousTypeAfInfo(map, field.Length > 0 ? field.Remove(0, 2).ToString() : null); if (findIncludeMany.Any() == false) return await this.ToListMapReaderPrivateAsync(af, null, cancellationToken); @@ -1271,7 +1271,7 @@ namespace FreeSql.Internal.CommonProvider var otherMap = new ReadAnonymousTypeInfo(); field.Clear(); - _commonExpression.ReadAnonymousField(_tables, field, otherMap, ref index, otherNewInit, this, null, _whereGlobalFilter, null, true); + _commonExpression.ReadAnonymousField(_tables, field, otherMap, ref index, otherNewInit, this, null, _whereGlobalFilter, null, null, true); var otherRet = new List(); var otherAf = new ReadAnonymousTypeOtherInfo(field.ToString(), otherMap, otherRet); diff --git a/FreeSql/Internal/CommonProvider/SelectProvider/SelectGroupingProvider.cs b/FreeSql/Internal/CommonProvider/SelectProvider/SelectGroupingProvider.cs index d9b2bba3..7fe54310 100644 --- a/FreeSql/Internal/CommonProvider/SelectProvider/SelectGroupingProvider.cs +++ b/FreeSql/Internal/CommonProvider/SelectProvider/SelectGroupingProvider.cs @@ -115,7 +115,7 @@ namespace FreeSql.Internal.CommonProvider var field = new StringBuilder(); var index = 0; - _comonExp.ReadAnonymousField(null, field, map, ref index, select, null, this, null, null, false); + _comonExp.ReadAnonymousField(null, field, map, ref index, select, null, this, null, null, null, false); if (map.Childs.Any() == false && map.MapType == null) map.MapType = elementType; var method = _select.GetType().GetMethod("ToListMrPrivate", BindingFlags.Instance | BindingFlags.NonPublic); method = method.MakeGenericMethod(elementType); @@ -128,7 +128,7 @@ namespace FreeSql.Internal.CommonProvider var field = new StringBuilder(); var index = 0; - _comonExp.ReadAnonymousField(null, field, map, ref index, elementSelector, null, this, null, null, false); + _comonExp.ReadAnonymousField(null, field, map, ref index, elementSelector, null, this, null, null, null, false); if (map.Childs.Any() == false && map.MapType == null) map.MapType = elementType; var method = _select.GetType().GetMethod("ToListMrPrivate", BindingFlags.Instance | BindingFlags.NonPublic); method = method.MakeGenericMethod(elementType); @@ -143,7 +143,7 @@ namespace FreeSql.Internal.CommonProvider var field = new StringBuilder(); var index = fieldAlias == FieldAliasOptions.AsProperty ? CommonExpression.ReadAnonymousFieldAsCsName : 0; - _comonExp.ReadAnonymousField(null, field, map, ref index, select, null, this, null, null, false); + _comonExp.ReadAnonymousField(null, field, map, ref index, select, null, this, null, null, null, false); var fieldSql = field.Length > 0 ? field.Remove(0, 2).ToString() : null; return InternalToSql(fieldSql); } @@ -269,7 +269,7 @@ namespace FreeSql.Internal.CommonProvider var index = 0; _lambdaParameter = select?.Parameters[0]; - _comonExp.ReadAnonymousField(null, field, map, ref index, select, null, this, null, null, false); + _comonExp.ReadAnonymousField(null, field, map, ref index, select, null, this, null, null, null, false); if (map.Childs.Any() == false && map.MapType == null) map.MapType = typeof(TReturn); var method = _select.GetType().GetMethod("ToListMrPrivateAsync", BindingFlags.Instance | BindingFlags.NonPublic); method = method.MakeGenericMethod(typeof(TReturn)); @@ -283,7 +283,7 @@ namespace FreeSql.Internal.CommonProvider var index = 0; _lambdaParameter = elementSelector?.Parameters[0]; - _comonExp.ReadAnonymousField(null, field, map, ref index, elementSelector, null, this, null, null, false); + _comonExp.ReadAnonymousField(null, field, map, ref index, elementSelector, null, this, null, null, null, false); if (map.Childs.Any() == false && map.MapType == null) map.MapType = typeof(TElement); var method = _select.GetType().GetMethod("ToListMrPrivateAsync", BindingFlags.Instance | BindingFlags.NonPublic); method = method.MakeGenericMethod(typeof(TElement)); diff --git a/FreeSql/Internal/Model/ReadAnonymousTypeInfo.cs b/FreeSql/Internal/Model/ReadAnonymousTypeInfo.cs index 1416caa9..d118b0aa 100644 --- a/FreeSql/Internal/Model/ReadAnonymousTypeInfo.cs +++ b/FreeSql/Internal/Model/ReadAnonymousTypeInfo.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq.Expressions; using System.Reflection; using System.Text; @@ -20,6 +21,7 @@ namespace FreeSql.Internal.Model public bool IsEntity { get; set; } public bool IsDefaultCtor { get; set; } public string IncludeManyKey { get; set; } //ToList(a => new { a.Childs }) 集合属性指定加载 + public Expression SubSelectMany { get; set; } //ToList(a => new { sublist = fsql.Select().ToList() }) 子集合查询 public void CopyTo(ReadAnonymousTypeInfo target) { @@ -42,6 +44,7 @@ namespace FreeSql.Internal.Model public ReadAnonymousTypeInfo map { get; } public string field { get; } public List> fillIncludeMany { get; set; } //回填集合属性的数据 + public List> fillSubSelectMany { get; set; } //回填集合属性的数据 public ReadAnonymousTypeAfInfo(ReadAnonymousTypeInfo map, string field) { this.map = map;