From 5e531b2521c87b26540aca930dc258efe0053496 Mon Sep 17 00:00:00 2001 From: 28810 <28810@YEXIANGQIN> Date: Fri, 10 Apr 2020 22:37:42 +0800 Subject: [PATCH] =?UTF-8?q?-=20=E5=A2=9E=E5=8A=A0=20IQueryable=20RestoreTo?= =?UTF-8?q?Select=20=E6=89=A9=E5=B1=95=E6=96=B9=E6=B3=95=EF=BC=8C=E5=B0=86?= =?UTF-8?q?=20IQueryable=20=E8=BD=AC=E5=9B=9E=E6=88=90=20ISelect?= =?UTF-8?q?=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FreeSql.Extensions.Linq.xml | 14 +- .../FreeSqlExtensionsLinq.cs | 17 +- .../QueryableProvider.cs | 99 +++--- .../{Queryable => Linq}/ExprHelperTest.cs | 0 .../ISelectLinqToSqlTests.cs} | 6 +- .../QueryableLinqToSqlTests.cs | 23 -- .../Linq/QueryableRestoreToSelectTest.cs | 50 +++ .../{Queryable => Linq}/QueryableTest.cs | 0 FreeSql/Extensions/FreeSqlGlobalExtensions.cs | 2 +- FreeSql/FreeSql.xml | 308 ++++++++---------- 10 files changed, 266 insertions(+), 253 deletions(-) rename FreeSql.Tests/FreeSql.Tests/{Queryable => Linq}/ExprHelperTest.cs (100%) rename FreeSql.Tests/FreeSql.Tests/{LinqToSql/SqliteLinqToSqlTests.cs => Linq/ISelectLinqToSqlTests.cs} (99%) rename FreeSql.Tests/FreeSql.Tests/{Queryable => Linq}/QueryableLinqToSqlTests.cs (88%) create mode 100644 FreeSql.Tests/FreeSql.Tests/Linq/QueryableRestoreToSelectTest.cs rename FreeSql.Tests/FreeSql.Tests/{Queryable => Linq}/QueryableTest.cs (100%) diff --git a/Extensions/FreeSql.Extensions.Linq/FreeSql.Extensions.Linq.xml b/Extensions/FreeSql.Extensions.Linq/FreeSql.Extensions.Linq.xml index 19871dd2..f5004a0a 100644 --- a/Extensions/FreeSql.Extensions.Linq/FreeSql.Extensions.Linq.xml +++ b/Extensions/FreeSql.Extensions.Linq/FreeSql.Extensions.Linq.xml @@ -7,11 +7,21 @@ 将 ISelect<T1> 转换为 IQueryable<T1> - 此方法主要用于扩展,比如:abp IRepository GetAll() 接口方法需要返回 IQueryable 对象 - 注意:IQueryable 方法污染较为严重,请尽量避免此转换 + 用于扩展如:abp IRepository GetAll() 接口方法需要返回 IQueryable 对象 + 提示:IQueryable 方法污染严重,查询功能的实现也不理想,应尽量避免此转换 + IQueryable<T1> 扩展方法 RestoreToSelect() 可以还原为 ISelect<T1> + + + 将 IQueryable<T1> 转换为 ISelect<T1> + 前提:IQueryable 必须由 FreeSql.Extensions.Linq.QueryableProvider 实现 + + + + + 【linq to sql】专用扩展方法,不建议直接使用 diff --git a/Extensions/FreeSql.Extensions.Linq/FreeSqlExtensionsLinq.cs b/Extensions/FreeSql.Extensions.Linq/FreeSqlExtensionsLinq.cs index de256cec..5df25a29 100644 --- a/Extensions/FreeSql.Extensions.Linq/FreeSqlExtensionsLinq.cs +++ b/Extensions/FreeSql.Extensions.Linq/FreeSqlExtensionsLinq.cs @@ -15,14 +15,27 @@ public static class FreeSqlExtensionsLinqSql /// /// 将 ISelect<T1> 转换为 IQueryable<T1> - /// 此方法主要用于扩展,比如:abp IRepository GetAll() 接口方法需要返回 IQueryable 对象 - /// 注意:IQueryable 方法污染较为严重,请尽量避免此转换 + /// 用于扩展如:abp IRepository GetAll() 接口方法需要返回 IQueryable 对象 + /// 提示:IQueryable 方法污染严重,查询功能的实现也不理想,应尽量避免此转换 + /// IQueryable<T1> 扩展方法 RestoreToSelect() 可以还原为 ISelect<T1> /// /// public static IQueryable AsQueryable(this ISelect that) where T1 : class { return new QueryableProvider(that as Select1Provider); } + /// + /// 将 IQueryable<T1> 转换为 ISelect<T1> + /// 前提:IQueryable 必须由 FreeSql.Extensions.Linq.QueryableProvider 实现 + /// + /// + /// + /// + public static ISelect RestoreToSelect(this IQueryable that) where T1 : class + { + var queryable = that as QueryableProvider ?? throw new Exception($"无法将 IQueryable<{typeof(T1).Name}> 转换为 ISelect<{typeof(T1).Name}>,因为他的实现不是 FreeSql.Extensions.Linq.QueryableProvider"); + return queryable._select; + } /// /// 【linq to sql】专用扩展方法,不建议直接使用 diff --git a/Extensions/FreeSql.Extensions.Linq/QueryableProvider.cs b/Extensions/FreeSql.Extensions.Linq/QueryableProvider.cs index b65147b1..35b2516c 100644 --- a/Extensions/FreeSql.Extensions.Linq/QueryableProvider.cs +++ b/Extensions/FreeSql.Extensions.Linq/QueryableProvider.cs @@ -13,15 +13,15 @@ namespace FreeSql.Extensions.Linq { class QueryableProvider : IQueryable, IOrderedQueryable where TSource : class { - private Expression _expression; - private IQueryProvider _provider; - private Select1Provider _select; + Expression _expression; + IQueryProvider _provider; + internal Select1Provider _select; public QueryableProvider(Select1Provider select) { _select = select; _expression = Expression.Constant(this); - _provider = new QueryProvider(_select); + _provider = new QueryProvider(_select, _expression); } public QueryableProvider(Expression expression, IQueryProvider provider, Select1Provider select) { @@ -49,56 +49,58 @@ namespace FreeSql.Extensions.Linq class QueryProvider : IQueryProvider where TSource : class { - private Select1Provider _select; + Select1Provider _select; + Expression _oldExpression; - public QueryProvider(Select1Provider select) + public QueryProvider(Select1Provider select, Expression oldExpression) { _select = select; + _oldExpression = oldExpression; } public IQueryable CreateQuery(Expression expression) { + ExecuteExp(expression, null, false); if (typeof(TElement) != typeof(TCurrent)) - return new QueryableProvider(expression, new QueryProvider(_select), _select); + return new QueryableProvider(expression, new QueryProvider(_select, expression), _select); + _oldExpression = expression; return new QueryableProvider(expression, this, _select); } public IQueryable CreateQuery(Expression expression) => throw new NotImplementedException(); public TResult Execute(Expression expression) { - var stackCallExps = new Stack(); - var callExp = expression as MethodCallExpression; - while(callExp != null) - { - stackCallExps.Push(callExp); - callExp = callExp?.Arguments.FirstOrDefault() as MethodCallExpression; - } + return (TResult)ExecuteExp(expression, typeof(TResult), _oldExpression == expression); + } + public object Execute(Expression expression) => throw new NotImplementedException(); - SelectGroupingProvider groupBy = null; + public object ExecuteExp(Expression expression, Type tresult, bool isProcessed) + { + var callExp = expression as MethodCallExpression; var isfirst = false; - while (stackCallExps.Any()) + if (callExp != null && isProcessed == false) { - callExp = stackCallExps.Pop(); - TResult throwCallExp(string message) => throw new Exception($"FreeSql Queryable 解析出错,执行的方法 {callExp.Method.Name} {message}"); + object throwCallExp(string message) => throw new Exception($"解析失败 {callExp.Method.Name} {message},提示:可以使用扩展方法 IQueryable.RestoreToSelect() 还原为 ISelect 再查询"); if (callExp.Method.DeclaringType != typeof(Queryable)) return throwCallExp($"必须属于 System.Linq.Queryable"); - TResult tplMaxMinAvgSum(string method) { + object tplMaxMinAvgSum(string method) + { if (callExp.Arguments.Count == 2) { var avgParam = (callExp.Arguments[1] as UnaryExpression)?.Operand as LambdaExpression; - return (TResult)Utils.GetDataReaderValue(typeof(TResult), + return Utils.GetDataReaderValue(tresult, _select.GetType().GetMethod(method).MakeGenericMethod(avgParam.ReturnType).Invoke(_select, new object[] { avgParam })); } return throwCallExp($" 不支持 {callExp.Arguments.Count}个参数的方法"); } - TResult tplOrderBy(string method, bool isDescending) + object tplOrderBy(string method, bool isDescending) { if (callExp.Arguments.Count == 2) { var arg1 = (callExp.Arguments[1] as UnaryExpression)?.Operand as LambdaExpression; _select.OrderByReflection(arg1, isDescending); - return default(TResult); + return tresult.CreateInstanceGetDefaultValue(); } return throwCallExp($" 不支持 {callExp.Arguments.Count}个参数的方法"); } @@ -106,7 +108,7 @@ namespace FreeSql.Extensions.Linq { case "Any": if (callExp.Arguments.Count == 2) _select.InternalWhere(callExp.Arguments[1]); - return (TResult)(object)_select.Any(); + return _select.Any(); case "AsQueryable": break; @@ -123,13 +125,13 @@ namespace FreeSql.Extensions.Linq var dywhere = callExp.Arguments[1].GetConstExprValue(); if (dywhere == null) return throwCallExp($" 参数值不能为 null"); _select.WhereDynamic(dywhere); - return (TResult)(object)_select.Any(); + return _select.Any(); } return throwCallExp($" 不支持 {callExp.Arguments.Count}个参数的方法"); case "Count": if (callExp.Arguments.Count == 2) _select.InternalWhere(callExp.Arguments[1]); - return (TResult)Utils.GetDataReaderValue(typeof(TResult), _select.Count()); - + return Utils.GetDataReaderValue(tresult, _select.Count()); + case "Distinct": if (callExp.Arguments.Count == 1) { @@ -153,25 +155,24 @@ namespace FreeSql.Extensions.Linq isfirst = true; break; - case "OrderBy": + case "OrderBy": tplOrderBy("OrderByReflection", false); break; - case "OrderByDescending": - tplOrderBy("OrderByReflection", true); + case "OrderByDescending": + tplOrderBy("OrderByReflection", true); break; - case "ThenBy": - tplOrderBy("OrderByReflection", false); + case "ThenBy": + tplOrderBy("OrderByReflection", false); break; - case "ThenByDescending": - tplOrderBy("OrderByReflection", true); + case "ThenByDescending": + tplOrderBy("OrderByReflection", true); break; case "Where": var whereParam = (callExp.Arguments[1] as UnaryExpression)?.Operand as LambdaExpression; if (whereParam.Parameters.Count == 1) { - if (groupBy != null) groupBy.InternalHaving(whereParam); - else _select.InternalWhere(whereParam); + _select.InternalWhere(whereParam); break; } return throwCallExp(" 不支持"); @@ -185,7 +186,7 @@ namespace FreeSql.Extensions.Linq case "ToList": if (callExp.Arguments.Count == 1) - return (TResult)(object)_select.ToList(); + return _select.ToList(); return throwCallExp(" 不支持"); case "Select": @@ -242,37 +243,21 @@ namespace FreeSql.Extensions.Linq case "GroupBy": return throwCallExp(" 不支持"); - if (callExp.Arguments.Count == 2) //TODO: 待实现 - { - var arg1 = (callExp.Arguments[1] as UnaryExpression)?.Operand as LambdaExpression; - - var map = new ReadAnonymousTypeInfo(); - var field = new StringBuilder(); - var index = -10000; //临时规则,不返回 as1 - - _select._commonExpression.ReadAnonymousField(_select._tables, field, map, ref index, arg1, null, _select._whereCascadeExpression, false); //不走 DTO 映射 - var sql = field.ToString(); - _select.GroupBy(sql.Length > 0 ? sql.Substring(2) : null); - groupBy = new SelectGroupingProvider(_select._orm, _select, map, sql, _select._commonExpression, _select._tables); - break; - } - return throwCallExp($" 不支持 {callExp.Arguments.Count}个参数的方法"); - default: return throwCallExp(" 不支持"); } } + if (tresult == null) return null; if (isfirst) { _select.Limit(1); if (_select._selectExpression != null) - return (TResult)(object)_select.InternalToList(_select._selectExpression).FirstOrDefault(); - return (TResult)(object)_select.ToList().FirstOrDefault(); + return _select.InternalToList(_select._selectExpression).FirstOrDefault(); + return _select.ToList().FirstOrDefault(); } if (_select._selectExpression != null) - return (TResult)(object)_select.InternalToList(_select._selectExpression); - return (TResult)(object)_select.ToList(); + return _select.InternalToList(_select._selectExpression); + return _select.ToList(); } - public object Execute(Expression expression) => throw new NotImplementedException(); } } diff --git a/FreeSql.Tests/FreeSql.Tests/Queryable/ExprHelperTest.cs b/FreeSql.Tests/FreeSql.Tests/Linq/ExprHelperTest.cs similarity index 100% rename from FreeSql.Tests/FreeSql.Tests/Queryable/ExprHelperTest.cs rename to FreeSql.Tests/FreeSql.Tests/Linq/ExprHelperTest.cs diff --git a/FreeSql.Tests/FreeSql.Tests/LinqToSql/SqliteLinqToSqlTests.cs b/FreeSql.Tests/FreeSql.Tests/Linq/ISelectLinqToSqlTests.cs similarity index 99% rename from FreeSql.Tests/FreeSql.Tests/LinqToSql/SqliteLinqToSqlTests.cs rename to FreeSql.Tests/FreeSql.Tests/Linq/ISelectLinqToSqlTests.cs index 555d7c83..584b94c3 100644 --- a/FreeSql.Tests/FreeSql.Tests/LinqToSql/SqliteLinqToSqlTests.cs +++ b/FreeSql.Tests/FreeSql.Tests/Linq/ISelectLinqToSqlTests.cs @@ -3,10 +3,8 @@ using System; using System.Linq; using Xunit; -namespace FreeSql.Tests.LinqToSql +namespace FreeSql.Tests.Linq { - - class TestLinqToSql { public Guid id { get; set; } @@ -29,7 +27,7 @@ namespace FreeSql.Tests.LinqToSql public DateTime createtime { get; set; } = DateTime.Now; } - public class SqliteLinqToSqlTests + public class ISelectLinqToSqlTests { [Fact] diff --git a/FreeSql.Tests/FreeSql.Tests/Queryable/QueryableLinqToSqlTests.cs b/FreeSql.Tests/FreeSql.Tests/Linq/QueryableLinqToSqlTests.cs similarity index 88% rename from FreeSql.Tests/FreeSql.Tests/Queryable/QueryableLinqToSqlTests.cs rename to FreeSql.Tests/FreeSql.Tests/Linq/QueryableLinqToSqlTests.cs index f028f93a..6d234d0b 100644 --- a/FreeSql.Tests/FreeSql.Tests/Queryable/QueryableLinqToSqlTests.cs +++ b/FreeSql.Tests/FreeSql.Tests/Linq/QueryableLinqToSqlTests.cs @@ -58,29 +58,6 @@ namespace FreeSql.Tests.Linq Assert.Equal(item.id, t1[0].id); } - [Fact] - public void GroupBy() - { - //var item = new TestQueryableLinqToSql { name = Guid.NewGuid().ToString() }; - //g.sqlite.Insert().AppendData(item).ExecuteAffrows(); - - //var t1 = (from a in g.sqlite.Select().AsQueryable() - // where a.id == item.id - // group a by new { a.id, a.name } into g - // select new - // { - // g.Key.id, - // g.Key.name, - // cou = g.Count(), - // avg = g.Average(x => x.click), - // sum = g.Sum(x => x.click), - // max = g.Max(x => x.click), - // min = g.Min(x => x.click) - // }).ToList(); - //Assert.True(t1.Any()); - //Assert.Equal(item.id, t1.First().id); - } - [Fact] public void CaseWhen() { diff --git a/FreeSql.Tests/FreeSql.Tests/Linq/QueryableRestoreToSelectTest.cs b/FreeSql.Tests/FreeSql.Tests/Linq/QueryableRestoreToSelectTest.cs new file mode 100644 index 00000000..6bc941f9 --- /dev/null +++ b/FreeSql.Tests/FreeSql.Tests/Linq/QueryableRestoreToSelectTest.cs @@ -0,0 +1,50 @@ +using FreeSql.DataAnnotations; +using FreeSql; +using System; +using System.Collections.Generic; +using Xunit; +using System.Linq; +using Newtonsoft.Json.Linq; +using NpgsqlTypes; +using Npgsql.LegacyPostgis; +using System.Linq.Expressions; +using System.Threading.Tasks; +using System.ComponentModel.DataAnnotations; +using System.Threading; +using System.Data.SqlClient; +using kwlib; +using System.Diagnostics; +using System.IO; +using System.Text; + +namespace FreeSql.Tests.Linq +{ + public class QueryableRestoreToSelectTest + { + class qt01 + { + [Column(IsIdentity = true)] + public int id { get; set; } + public string name { get; set; } + + [Navigate(nameof(qt01_item.qt01id))] + public List items { get; set; } + } + class qt01_item + { + [Column(IsIdentity = true)] + public int id { get; set; } + public string title { get; set; } + public int qt01id { get; set; } + } + IFreeSql fsql => g.sqlite; + + [Fact] + public void RestoreToSelect() + { + Assert.Equal(fsql.Select().Skip(2).First(a => a.name), fsql.Select().AsQueryable().Skip(2).Take(1).RestoreToSelect().First(a => a.name)); + Assert.Equal(fsql.Select().Skip(2).First(a => new { a.name }).name, fsql.Select().AsQueryable().Skip(2).Take(1).RestoreToSelect().First(a => new { a.name }).name); + } + } + +} diff --git a/FreeSql.Tests/FreeSql.Tests/Queryable/QueryableTest.cs b/FreeSql.Tests/FreeSql.Tests/Linq/QueryableTest.cs similarity index 100% rename from FreeSql.Tests/FreeSql.Tests/Queryable/QueryableTest.cs rename to FreeSql.Tests/FreeSql.Tests/Linq/QueryableTest.cs diff --git a/FreeSql/Extensions/FreeSqlGlobalExtensions.cs b/FreeSql/Extensions/FreeSqlGlobalExtensions.cs index 45ce906d..c787b5eb 100644 --- a/FreeSql/Extensions/FreeSqlGlobalExtensions.cs +++ b/FreeSql/Extensions/FreeSqlGlobalExtensions.cs @@ -214,7 +214,7 @@ public static partial class FreeSqlGlobalExtensions } /// - /// 将 IEnumable<T> 转成 ISelect<T>,以便使用 FreeSql 的查询功能。此方法用于 Lambad 表达式中,快速进行集合导航的查询。 + /// 将 IEnumable<T> 转成 ISelect<T>,以便使用 FreeSql 的查询功能。此方法用于 Lambda 表达式中,快速进行集合导航的查询。 /// /// /// diff --git a/FreeSql/FreeSql.xml b/FreeSql/FreeSql.xml index 12b3cc3e..18542e5f 100644 --- a/FreeSql/FreeSql.xml +++ b/FreeSql/FreeSql.xml @@ -2281,6 +2281,137 @@ + + + 查询,若使用读写分离,查询【从库】条件cmdText.StartsWith("SELECT "),否则查询【主库】 + + + + + + + + + 查询,ExecuteReaderAsync(dr => {}, "select * from user where age > ?age", new { age = 25 }) + + + + + + + 查询 + + + + + + + 查询,ExecuteArrayAsync("select * from user where age > ?age", new { age = 25 }) + + + + + + + + 查询 + + + + + + + 查询,ExecuteDataSetAsync("select * from user where age > ?age; select 2", new { age = 25 }) + + + + + + + + 查询 + + + + + + + 查询,ExecuteDataTableAsync("select * from user where age > ?age", new { age = 25 }) + + + + + + + + 在【主库】执行 + + + + + + + + 在【主库】执行,ExecuteNonQueryAsync("delete from user where age > ?age", new { age = 25 }) + + + + + + + + 在【主库】执行 + + + + + + + + 在【主库】执行,ExecuteScalarAsync("select 1 from user where age > ?age", new { age = 25 }) + + + + + + + + 执行SQL返回对象集合,QueryAsync<User>("select * from user where age > ?age", new SqlParameter { ParameterName = "age", Value = 25 }) + + + + + + + + + + 执行SQL返回对象集合,QueryAsync<User>("select * from user where age > ?age", new { age = 25 }) + + + + + + + + + 执行SQL返回对象集合,Query<User>("select * from user where age > ?age; select * from address", new SqlParameter { ParameterName = "age", Value = 25 }) + + + + + + + + + + 执行SQL返回对象集合,Query<User>("select * from user where age > ?age; select * from address", new { age = 25 }) + + + + + + 可自定义解析表达式 @@ -2801,6 +2932,12 @@ 超时 + + + 获取资源 + + + 使用完毕后,归还资源 @@ -2871,6 +3008,12 @@ 资源对象 + + + 从对象池获取对象成功的时候触发,通过该方法统计或初始化对象 + + 资源对象 + 归还对象给对象池的时候触发 @@ -3092,7 +3235,7 @@ - 将 IEnumable<T> 转成 ISelect<T>,以便使用 FreeSql 的查询功能。此方法用于 Lambad 表达式中,快速进行集合导航的查询。 + 将 IEnumable<T> 转成 ISelect<T>,以便使用 FreeSql 的查询功能。此方法用于 Lambda 表达式中,快速进行集合导航的查询。 @@ -3503,167 +3646,4 @@ - - - - - - 使用 or 拼接两个 lambda 表达式 - - - - - - 使用 or 拼接两个 lambda 表达式 - - - true 时生效 - - - - - - 将 lambda 表达式取反 - - - true 时生效 - - - - - 生成类似Mongodb的ObjectId有序、不重复Guid - - - - - - 插入数据 - - - - - - - 插入数据,传入实体 - - - - - - - - 插入数据,传入实体数组 - - - - - - - - 插入数据,传入实体集合 - - - - - - - - 插入数据,传入实体集合 - - - - - - - - 修改数据 - - - - - - - 修改数据,传入动态对象如:主键值 | new[]{主键值1,主键值2} | TEntity1 | new[]{TEntity1,TEntity2} | new{id=1} - - - 主键值、主键值集合、实体、实体集合、匿名对象、匿名对象集合 - - - - - 查询数据 - - - - - - - 查询数据,传入动态对象如:主键值 | new[]{主键值1,主键值2} | TEntity1 | new[]{TEntity1,TEntity2} | new{id=1} - - - 主键值、主键值集合、实体、实体集合、匿名对象、匿名对象集合 - - - - - 删除数据 - - - - - - - 删除数据,传入动态对象如:主键值 | new[]{主键值1,主键值2} | TEntity1 | new[]{TEntity1,TEntity2} | new{id=1} - - - 主键值、主键值集合、实体、实体集合、匿名对象、匿名对象集合 - - - - - 开启事务(不支持异步),60秒未执行完成(可能)被其他线程事务自动提交 - - 事务体 () => {} - - - - 开启事务(不支持异步) - - 超时,未执行完成(可能)被其他线程事务自动提交 - 事务体 () => {} - - - - 开启事务(不支持异步) - - - 事务体 () => {} - 超时,未执行完成(可能)被其他线程事务自动提交 - - - - 数据库访问对象 - - - - - 所有拦截方法都在这里 - - - - - CodeFirst 模式开发相关方法 - - - - - DbFirst 模式开发相关方法 - - - - - 全局过滤设置,可默认附加为 Select/Update/Delete 条件 - - -