mirror of
				https://github.com/nsnail/FreeSql.git
				synced 2025-11-04 09:15:27 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			298 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			298 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
using FreeSql.Internal.Model;
 | 
						|
using System;
 | 
						|
using System.Collections;
 | 
						|
using System.Collections.Generic;
 | 
						|
using System.Data;
 | 
						|
using System.Linq;
 | 
						|
using System.Linq.Expressions;
 | 
						|
using System.Reflection;
 | 
						|
using System.Text;
 | 
						|
using System.Threading;
 | 
						|
using System.Threading.Tasks;
 | 
						|
 | 
						|
namespace FreeSql.Internal.CommonProvider
 | 
						|
{
 | 
						|
    public class SelectGroupingProvider : BaseDiyMemberExpression
 | 
						|
    {
 | 
						|
        public IFreeSql _orm;
 | 
						|
        public Select0Provider _select;
 | 
						|
        public CommonExpression _comonExp;
 | 
						|
        public List<SelectTableInfo> _tables;
 | 
						|
        public int _groupByLimit, _groupBySkip;
 | 
						|
 | 
						|
        public SelectGroupingProvider(IFreeSql orm, Select0Provider select, ReadAnonymousTypeInfo map, string field, CommonExpression comonExp, List<SelectTableInfo> tables)
 | 
						|
        {
 | 
						|
            _orm = orm;
 | 
						|
            _select = select;
 | 
						|
            _map = map;
 | 
						|
            _field = field;
 | 
						|
            _comonExp = comonExp;
 | 
						|
            _tables = tables;
 | 
						|
        }
 | 
						|
 | 
						|
        public override string ParseExp(Expression[] members)
 | 
						|
        {
 | 
						|
            if (members.Any() == false) return _map.DbField;
 | 
						|
            var parentName = ((members.FirstOrDefault() as MemberExpression)?.Expression as MemberExpression)?.Member.Name;
 | 
						|
            switch (parentName)
 | 
						|
            {
 | 
						|
                case "Key":
 | 
						|
                    var read = _map;
 | 
						|
                    for (var a = 0; a < members.Length; a++)
 | 
						|
                    {
 | 
						|
                        read = read.Childs.Where(z => z.CsName == (members[a] as MemberExpression)?.Member.Name).FirstOrDefault();
 | 
						|
                        if (read == null) return null;
 | 
						|
                    }
 | 
						|
                    return read.DbField;
 | 
						|
                case "Value":
 | 
						|
                    var tb = _tables.First();
 | 
						|
                    var foridx = 0;
 | 
						|
                    if (members.Length > 1)
 | 
						|
                    {
 | 
						|
                        var mem0 = (members.FirstOrDefault() as MemberExpression);
 | 
						|
                        var mem0Name = mem0?.Member.Name;
 | 
						|
                        if (mem0Name?.StartsWith("Item") == true && int.TryParse(mem0Name.Substring(4), out var tryitemidx))
 | 
						|
                        {
 | 
						|
                            if (tryitemidx == 1) foridx++;
 | 
						|
                            else
 | 
						|
                            {
 | 
						|
                                //var alias = $"SP10{(char)(96 + tryitemidx)}";
 | 
						|
                                var tmptb = _tables.Where((a, idx) => //a.AliasInit == alias && 
 | 
						|
                                    a.Table.Type == mem0.Type && idx == tryitemidx - 1).FirstOrDefault();
 | 
						|
                                if (tmptb != null)
 | 
						|
                                {
 | 
						|
                                    tb = tmptb;
 | 
						|
                                    foridx++;
 | 
						|
                                }
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                    var parmExp = Expression.Parameter(tb.Table.Type, tb.Alias);
 | 
						|
                    Expression retExp = parmExp;
 | 
						|
                    for (var a = foridx; a < members.Length; a++)
 | 
						|
                    {
 | 
						|
                        switch (members[a].NodeType)
 | 
						|
                        {
 | 
						|
                            case ExpressionType.Call:
 | 
						|
                                retExp = Expression.Call(retExp, (members[a] as MethodCallExpression).Method);
 | 
						|
                                break;
 | 
						|
                            case ExpressionType.MemberAccess:
 | 
						|
                                retExp = Expression.MakeMemberAccess(retExp, (members[a] as MemberExpression).Member);
 | 
						|
                                break;
 | 
						|
                            default:
 | 
						|
                                return null;
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                    return _comonExp.ExpressionLambdaToSql(retExp, new CommonExpression.ExpTSC { _tables = _tables, _tableRule = _select._tableRule, tbtype = SelectTableInfoType.From, isQuoteName = true, isDisableDiyParse = true, style = CommonExpression.ExpressionStyle.Where });
 | 
						|
            }
 | 
						|
            return null;
 | 
						|
        }
 | 
						|
 | 
						|
        public void InternalHaving(Expression exp)
 | 
						|
        {
 | 
						|
            var sql = _comonExp.ExpressionWhereLambda(null, _select._tableRule, exp, this, null, null);
 | 
						|
            var method = _select.GetType().GetMethod("Having", new[] { typeof(string), typeof(object) });
 | 
						|
            method.Invoke(_select, new object[] { sql, null });
 | 
						|
        }
 | 
						|
        public void InternalOrderBy(Expression exp, bool isDescending)
 | 
						|
        {
 | 
						|
            if (exp.NodeType == ExpressionType.Lambda) exp = (exp as LambdaExpression)?.Body;
 | 
						|
            if (exp?.NodeType == ExpressionType.New)
 | 
						|
            {
 | 
						|
                var newExp = exp as NewExpression;
 | 
						|
                if (newExp != null)
 | 
						|
                    for (var a = 0; a < newExp.Members.Count; a++)
 | 
						|
                        InternalOrderBy(newExp.Arguments[a], isDescending);
 | 
						|
                return;
 | 
						|
            }
 | 
						|
            var sql = _comonExp.ExpressionWhereLambda(null, _select._tableRule, exp, this, null, null);
 | 
						|
            var method = _select.GetType().GetMethod("OrderBy", new[] { typeof(string), typeof(object) });
 | 
						|
            method.Invoke(_select, new object[] { isDescending ? $"{sql} DESC" : sql, null });
 | 
						|
        }
 | 
						|
        public object InternalToList(Expression select, Type elementType)
 | 
						|
        {
 | 
						|
            var map = new ReadAnonymousTypeInfo();
 | 
						|
            var field = new StringBuilder();
 | 
						|
            var index = 0;
 | 
						|
 | 
						|
            _comonExp.ReadAnonymousField(null, _select._tableRule, field, map, ref index, select, _select, 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);
 | 
						|
            var fieldSql = field.Length > 0 ? field.Remove(0, 2).ToString() : null;
 | 
						|
            return method.Invoke(_select, new object[] { InternalToSql(fieldSql), new ReadAnonymousTypeAfInfo(map, fieldSql), null });
 | 
						|
        }
 | 
						|
        public IEnumerable<KeyValuePair<object, object>> InternalToKeyValuePairs(Expression elementSelector, Type elementType)
 | 
						|
        {
 | 
						|
            var map = new ReadAnonymousTypeInfo();
 | 
						|
            var field = new StringBuilder();
 | 
						|
            var index = 0;
 | 
						|
 | 
						|
            _comonExp.ReadAnonymousField(null, _select._tableRule, field, map, ref index, elementSelector, _select, 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);
 | 
						|
            var fieldSql = field.Length > 0 ? field.Remove(0, 2).ToString() : null;
 | 
						|
            var otherAf = new ReadAnonymousTypeOtherInfo(_field, _map, new List<object>());
 | 
						|
            var values = method.Invoke(_select, new object[] { InternalToSql($"{fieldSql}{_field}"), new ReadAnonymousTypeAfInfo(map, fieldSql), new[] { otherAf } }) as IList;
 | 
						|
            return otherAf.retlist.Select((a, b) => new KeyValuePair<object, object>(a, values[b]));
 | 
						|
        }
 | 
						|
        public string InternalToSql(Expression select, FieldAliasOptions fieldAlias = FieldAliasOptions.AsIndex)
 | 
						|
        {
 | 
						|
            var map = new ReadAnonymousTypeInfo();
 | 
						|
            var field = new StringBuilder();
 | 
						|
            var index = fieldAlias == FieldAliasOptions.AsProperty ? CommonExpression.ReadAnonymousFieldAsCsName : 0;
 | 
						|
 | 
						|
            _comonExp.ReadAnonymousField(null, _select._tableRule, field, map, ref index, select, _select, this, null, null, null, false);
 | 
						|
            var fieldSql = field.Length > 0 ? field.Remove(0, 2).ToString() : null;
 | 
						|
            return InternalToSql(fieldSql);
 | 
						|
        }
 | 
						|
        public string InternalToSql(string field)
 | 
						|
        {
 | 
						|
            if (string.IsNullOrEmpty(field))
 | 
						|
                throw new ArgumentException(CoreStrings.Parameter_Field_NotSpecified);
 | 
						|
 | 
						|
            var isNestedPageSql = false;
 | 
						|
            switch (_orm.Ado.DataType)
 | 
						|
            {
 | 
						|
                case DataType.Oracle:
 | 
						|
                case DataType.OdbcOracle:
 | 
						|
                case DataType.Dameng:
 | 
						|
                case DataType.OdbcDameng: //Oracle、Dameng 分组时,嵌套分页
 | 
						|
                case DataType.GBase:
 | 
						|
                    isNestedPageSql = true;
 | 
						|
                    break;
 | 
						|
                default:
 | 
						|
                    _select._limit = _groupByLimit;
 | 
						|
                    _select._skip = _groupBySkip;
 | 
						|
                    break;
 | 
						|
            }
 | 
						|
            var method = _select.GetType().GetMethod("ToSql", new[] { typeof(string) });
 | 
						|
            var sql = method.Invoke(_select, new object[] { field }) as string;
 | 
						|
            if (isNestedPageSql == false)
 | 
						|
            {
 | 
						|
                _select._limit = 0;
 | 
						|
                _select._skip = 0;
 | 
						|
                return sql;
 | 
						|
            }
 | 
						|
            if (_groupByLimit == 0 && _groupBySkip == 0) return sql;
 | 
						|
            return _orm.Select<object>().As("t").WithSql(sql).Limit(_groupByLimit).Skip(_groupBySkip).ToSql("t.*");
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    public class SelectGroupingProvider<TKey, TValue> : SelectGroupingProvider, ISelectGrouping<TKey, TValue>
 | 
						|
    {
 | 
						|
        public SelectGroupingProvider(IFreeSql orm, Select0Provider select, ReadAnonymousTypeInfo map, string field, CommonExpression comonExp, List<SelectTableInfo> tables)
 | 
						|
            :base(orm, select, map, field, comonExp, tables) { }
 | 
						|
 | 
						|
        public string ToSql<TReturn>(Expression<Func<ISelectGroupingAggregate<TKey, TValue>, TReturn>> select, FieldAliasOptions fieldAlias = FieldAliasOptions.AsIndex)
 | 
						|
        {
 | 
						|
            _lambdaParameter = select?.Parameters[0];
 | 
						|
            return InternalToSql(select, fieldAlias);
 | 
						|
        }
 | 
						|
        public string ToSql(string field) => InternalToSql(field);
 | 
						|
 | 
						|
        public ISelectGrouping<TKey, TValue> Skip(int offset)
 | 
						|
        {
 | 
						|
            _groupBySkip = offset;
 | 
						|
            return this;
 | 
						|
        }
 | 
						|
        public ISelectGrouping<TKey, TValue> Offset(int offset) => this.Skip(offset);
 | 
						|
        public ISelectGrouping<TKey, TValue> Limit(int limit)
 | 
						|
        {
 | 
						|
            _groupByLimit = limit;
 | 
						|
            return this;
 | 
						|
        }
 | 
						|
        public ISelectGrouping<TKey, TValue> Take(int limit) => this.Limit(limit);
 | 
						|
        public ISelectGrouping<TKey, TValue> Page(int pageNumber, int pageSize)
 | 
						|
        {
 | 
						|
            _groupBySkip = Math.Max(0, pageNumber - 1) * pageSize;
 | 
						|
            _groupByLimit = pageSize;
 | 
						|
            return this;
 | 
						|
        }
 | 
						|
 | 
						|
        public ISelectGrouping<TKey, TValue> Page(BasePagingInfo pagingInfo)
 | 
						|
        {
 | 
						|
            pagingInfo.Count = this.Count();
 | 
						|
            _groupBySkip = Math.Max(0, pagingInfo.PageNumber - 1) * pagingInfo.PageSize;
 | 
						|
            _groupByLimit = pagingInfo.PageSize;
 | 
						|
            return this;
 | 
						|
        }
 | 
						|
 | 
						|
        public long Count() => _select._cancel?.Invoke() == true ? 0 : long.TryParse(string.Concat(_orm.Ado.ExecuteScalar(_select._connection, _select._transaction, CommandType.Text, $"select count(1) from ({this.ToSql($"1{_comonExp._common.FieldAsAlias("as1")}")}) fta", _select._commandTimeout, _select._params.ToArray())), out var trylng) ? trylng : default(long);
 | 
						|
        public ISelectGrouping<TKey, TValue> Count(out long count)
 | 
						|
        {
 | 
						|
            count = this.Count();
 | 
						|
            return this;
 | 
						|
        }
 | 
						|
 | 
						|
        public ISelectGrouping<TKey, TValue> HavingIf(bool condition, Expression<Func<ISelectGroupingAggregate<TKey, TValue>, bool>> exp) => condition ? Having(exp) : this;
 | 
						|
        public ISelectGrouping<TKey, TValue> Having(Expression<Func<ISelectGroupingAggregate<TKey, TValue>, bool>> exp)
 | 
						|
        {
 | 
						|
            _lambdaParameter = exp?.Parameters[0];
 | 
						|
            InternalHaving(exp);
 | 
						|
            return this;
 | 
						|
        }
 | 
						|
        public ISelectGrouping<TKey, TValue> OrderBy<TMember>(Expression<Func<ISelectGroupingAggregate<TKey, TValue>, TMember>> column)
 | 
						|
        {
 | 
						|
            _lambdaParameter = column?.Parameters[0];
 | 
						|
            InternalOrderBy(column, false);
 | 
						|
            return this;
 | 
						|
        }
 | 
						|
        public ISelectGrouping<TKey, TValue> OrderByDescending<TMember>(Expression<Func<ISelectGroupingAggregate<TKey, TValue>, TMember>> column)
 | 
						|
        {
 | 
						|
            _lambdaParameter = column?.Parameters[0];
 | 
						|
            InternalOrderBy(column, true);
 | 
						|
            return this;
 | 
						|
        }
 | 
						|
 | 
						|
        public List<TReturn> Select<TReturn>(Expression<Func<ISelectGroupingAggregate<TKey, TValue>, TReturn>> select) => ToList(select);
 | 
						|
        public List<TReturn> ToList<TReturn>(Expression<Func<ISelectGroupingAggregate<TKey, TValue>, TReturn>> select)
 | 
						|
        {
 | 
						|
            _lambdaParameter = select?.Parameters[0];
 | 
						|
            return InternalToList(select, typeof(TReturn)) as List<TReturn>;
 | 
						|
        }
 | 
						|
        public Dictionary<TKey, TElement> ToDictionary<TElement>(Expression<Func<ISelectGroupingAggregate<TKey, TValue>, TElement>> elementSelector)
 | 
						|
        {
 | 
						|
            _lambdaParameter = elementSelector?.Parameters[0];
 | 
						|
            return InternalToKeyValuePairs(elementSelector, typeof(TElement)).ToDictionary(a => (TKey)a.Key, a => (TElement)a.Value);
 | 
						|
        }
 | 
						|
 | 
						|
#if net40
 | 
						|
#else
 | 
						|
        async public Task<long> CountAsync(CancellationToken cancellationToken = default) => _select._cancel?.Invoke() == true ? 0 : long.TryParse(string.Concat(await _orm.Ado.ExecuteScalarAsync(_select._connection, _select._transaction, CommandType.Text, $"select count(1) from ({this.ToSql($"1{_comonExp._common.FieldAsAlias("as1")}")}) fta", _select._commandTimeout, _select._params.ToArray(), cancellationToken)), out var trylng) ? trylng : default(long);
 | 
						|
 | 
						|
        public Task<List<TReturn>> ToListAsync<TReturn>(Expression<Func<ISelectGroupingAggregate<TKey, TValue>, TReturn>> select, CancellationToken cancellationToken = default)
 | 
						|
        {
 | 
						|
            var map = new ReadAnonymousTypeInfo();
 | 
						|
            var field = new StringBuilder();
 | 
						|
            var index = 0;
 | 
						|
 | 
						|
            _lambdaParameter = select?.Parameters[0];
 | 
						|
            _comonExp.ReadAnonymousField(null, _select._tableRule, field, map, ref index, select, _select, 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));
 | 
						|
            var fieldSql = field.Length > 0 ? field.Remove(0, 2).ToString() : null;
 | 
						|
            return method.Invoke(_select, new object[] { InternalToSql(fieldSql), new ReadAnonymousTypeAfInfo(map, fieldSql), null, cancellationToken }) as Task<List<TReturn>>;
 | 
						|
        }
 | 
						|
        async public Task<Dictionary<TKey, TElement>> ToDictionaryAsync<TElement>(Expression<Func<ISelectGroupingAggregate<TKey, TValue>, TElement>> elementSelector, CancellationToken cancellationToken = default)
 | 
						|
        {
 | 
						|
            var map = new ReadAnonymousTypeInfo();
 | 
						|
            var field = new StringBuilder();
 | 
						|
            var index = 0;
 | 
						|
 | 
						|
            _lambdaParameter = elementSelector?.Parameters[0];
 | 
						|
            _comonExp.ReadAnonymousField(null, _select._tableRule, field, map, ref index, elementSelector, _select, 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));
 | 
						|
            var fieldSql = field.Length > 0 ? field.Remove(0, 2).ToString() : null;
 | 
						|
            var otherAf = new ReadAnonymousTypeOtherInfo(_field, _map, new List<object>());
 | 
						|
            var values = await (method.Invoke(_select, new object[] { InternalToSql($"{fieldSql}{_field}"), new ReadAnonymousTypeAfInfo(map, fieldSql), new[] { otherAf }, cancellationToken }) as Task<List<TElement>>);
 | 
						|
            return otherAf.retlist.Select((a, b) => new KeyValuePair<TKey, TElement>((TKey)a, values[b])).ToDictionary(a => a.Key, a => a.Value);
 | 
						|
        }
 | 
						|
#endif
 | 
						|
    }
 | 
						|
}
 |