From d42b2fc2b886d751d33217cafc63ba456db20c10 Mon Sep 17 00:00:00 2001 From: 28810 <28810@YEXIANGQIN> Date: Wed, 20 Nov 2019 13:32:49 +0800 Subject: [PATCH] =?UTF-8?q?-=20=E5=A2=9E=E5=8A=A0=20ExpressionCallAttribut?= =?UTF-8?q?e=20=E7=89=B9=E6=80=A7=EF=BC=8C=E5=AE=9E=E7=8E=B0=E8=A1=A8?= =?UTF-8?q?=E8=BE=BE=E5=BC=8F=E5=87=BD=E6=95=B0=E8=87=AA=E5=AE=9A=E4=B9=89?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FreeSql.Tests/FreeSql.Tests/UnitTest2.cs | 44 +++ .../ExpressionCallAttribute.cs | 28 ++ FreeSql/Extensions/FreeSqlGlobalExtensions.cs | 10 + FreeSql/FreeSql.xml | 303 +++++++++--------- FreeSql/Internal/CommonExpression.cs | 36 +++ FreeSql/Internal/UtilsExpressionTree.cs | 5 +- 6 files changed, 266 insertions(+), 160 deletions(-) create mode 100644 FreeSql/DataAnnotations/ExpressionCallAttribute.cs diff --git a/FreeSql.Tests/FreeSql.Tests/UnitTest2.cs b/FreeSql.Tests/FreeSql.Tests/UnitTest2.cs index 4a863174..431e8818 100644 --- a/FreeSql.Tests/FreeSql.Tests/UnitTest2.cs +++ b/FreeSql.Tests/FreeSql.Tests/UnitTest2.cs @@ -10,6 +10,7 @@ using Npgsql.LegacyPostgis; using System.Linq.Expressions; using System.Threading.Tasks; using System.ComponentModel.DataAnnotations; +using System.Threading; namespace FreeSql.Tests { @@ -167,6 +168,34 @@ namespace FreeSql.Tests [Fact] public void Test02() { + g.mysql.Aop.ParseExpression = (s, e) => + { + if (e.Expression.NodeType == ExpressionType.Call) + { + var callExp = e.Expression as MethodCallExpression; + if (callExp.Object?.Type == typeof(DateTime) && + callExp.Method.Name == "ToString" && + callExp.Arguments.Count == 1 && + callExp.Arguments[0].Type == typeof(string) && + callExp.Arguments[0].NodeType == ExpressionType.Constant) + { + var format = (callExp.Arguments[0] as ConstantExpression)?.Value?.ToString(); + + if (string.IsNullOrEmpty(format) == false) + { + var tmp = e.FreeParse(callExp.Object); + + switch (format) + { + case "yyyy-MM-dd HH:mm": + tmp = $"date_format({tmp}, '%Y-%m-%d %H:%i')"; + break; + } + e.Result = tmp; + } + } + } + }; var dbs = g.sqlserver.DbFirst.GetDatabases(); @@ -233,6 +262,21 @@ namespace FreeSql.Tests .IncludeMany(m => m.Permissions.Where(p => p.SysModuleId == m.Id), then => then.LeftJoin(p => p.Button.Id == p.SysModuleButtonId)) .ToList(); + + + var sql = g.sqlite.Select() + .ToSql(a => a.CreateTime.FormatDateTime("yyyy-MM-dd")); + } + } + + [ExpressionCall] + public static class DbFunc + { + static ThreadLocal context = new ThreadLocal(); + + public static string FormatDateTime(this DateTime that, string arg1) + { + return $"date_format({context.Value.Values["arg1"]})"; } } } diff --git a/FreeSql/DataAnnotations/ExpressionCallAttribute.cs b/FreeSql/DataAnnotations/ExpressionCallAttribute.cs new file mode 100644 index 00000000..4cd22f54 --- /dev/null +++ b/FreeSql/DataAnnotations/ExpressionCallAttribute.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace FreeSql.DataAnnotations +{ + /// + /// 自定义表达式函数解析 + /// 注意:请使用静态扩展类 + /// + [AttributeUsage(AttributeTargets.Class)] + public class ExpressionCallAttribute : Attribute + { + } + + public class ExpressionCallContext + { + /// + /// 数据库类型,可用于适配多种数据库环境 + /// + public DataType DataType { get; set; } + + /// + /// 已解析的表达式中参数内容 + /// + public Dictionary Values { get; } = new Dictionary(); + } +} diff --git a/FreeSql/Extensions/FreeSqlGlobalExtensions.cs b/FreeSql/Extensions/FreeSqlGlobalExtensions.cs index 684eb212..1bea2b5d 100644 --- a/FreeSql/Extensions/FreeSqlGlobalExtensions.cs +++ b/FreeSql/Extensions/FreeSqlGlobalExtensions.cs @@ -44,6 +44,16 @@ public static partial class FreeSqlGlobalExtensions public static bool IsArrayOrList(this Type that) => that == null ? false : (that.IsArray || typeof(IList).IsAssignableFrom(that)); public static Type NullableTypeOrThis(this Type that) => that?.IsNullableType() == true ? that.GetGenericArguments().First() : that; internal static string NotNullAndConcat(this string that, params object[] args) => string.IsNullOrEmpty(that) ? null : string.Concat(new object[] { that }.Concat(args)); + static ConcurrentDictionary _dicGetDefaultValueFirstConstructorsParameters = new ConcurrentDictionary(); + public static object CreateInstanceGetDefaultValue(this Type that) + { + if (that == null) return null; + if (that == typeof(string)) return default(string); + if (that.IsArray) return Array.CreateInstance(that, 0); + var ctorParms = _dicGetDefaultValueFirstConstructorsParameters.GetOrAdd(that, tp => tp.GetConstructors().FirstOrDefault()?.GetParameters()); + if (ctorParms == null || ctorParms.Any() == false) return Activator.CreateInstance(that, null); + return Activator.CreateInstance(that, ctorParms.Select(a => Activator.CreateInstance(a.ParameterType, null)).ToArray()); + } static ConcurrentDictionary> _dicGetPropertiesDictIgnoreCase = new ConcurrentDictionary>(); public static Dictionary GetPropertiesDictIgnoreCase(this Type that) => that == null ? null : _dicGetPropertiesDictIgnoreCase.GetOrAdd(that, tp => diff --git a/FreeSql/FreeSql.xml b/FreeSql/FreeSql.xml index 0f30703b..a773044d 100644 --- a/FreeSql/FreeSql.xml +++ b/FreeSql/FreeSql.xml @@ -152,6 +152,22 @@ + + + 自定义表达式函数解析 + 注意:请使用静态扩展类 + + + + + 数据库类型,可用于适配多种数据库环境 + + + + + 已解析的表达式中参数内容 + + 索引名 @@ -2000,6 +2016,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 }) + + + + + + 可自定义解析表达式 @@ -2710,159 +2857,3 @@ - - - - 使用 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 条件 - - - - diff --git a/FreeSql/Internal/CommonExpression.cs b/FreeSql/Internal/CommonExpression.cs index 759aaf19..593693be 100644 --- a/FreeSql/Internal/CommonExpression.cs +++ b/FreeSql/Internal/CommonExpression.cs @@ -9,6 +9,8 @@ using System.Linq.Expressions; using System.Reflection; using System.Text; using System.Text.RegularExpressions; +using FreeSql.DataAnnotations; +using System.Threading; namespace FreeSql.Internal { @@ -496,6 +498,8 @@ namespace FreeSql.Internal tsc.mapType = null; return $"{left} {oper} {right}"; } + static ConcurrentDictionary _dicTypeExistsExpressionCallAttribute = new ConcurrentDictionary(); + static ConcurrentDictionary _dicTypeExpressionCallClassContextFields = new ConcurrentDictionary(); public string ExpressionLambdaToSql(Expression exp, ExpTSC tsc) { if (exp == null) return ""; @@ -535,6 +539,38 @@ namespace FreeSql.Internal case ExpressionType.Call: tsc.mapType = null; var exp3 = exp as MethodCallExpression; + if (exp3.Object == null && _dicTypeExistsExpressionCallAttribute.GetOrAdd(exp3.Method.DeclaringType, dttp => dttp.GetCustomAttributes(typeof(ExpressionCallAttribute), true).Any())) + { + var ecc = new ExpressionCallContext { DataType = _ado.DataType }; + var exp3MethodParams = exp3.Method.GetParameters(); + for (var a = 0; a < exp3.Arguments.Count; a++) + if (exp3.Arguments[a].Type != typeof(ExpressionCallContext)) + ecc.Values.Add(exp3MethodParams[a].Name, ExpressionLambdaToSql(exp3.Arguments[a], tsc)); + + var exp3InvokeParams = new object[exp3.Arguments.Count]; + for (var a = 0; a < exp3.Arguments.Count; a++) + { + if (exp3.Arguments[a].Type != typeof(ExpressionCallContext)) + exp3InvokeParams[a] = exp3.Arguments[a].Type.CreateInstanceGetDefaultValue(); + else + exp3InvokeParams[a] = ecc; + } + var eccFields = _dicTypeExpressionCallClassContextFields.GetOrAdd(exp3.Method.DeclaringType, dttp => + dttp.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetField | BindingFlags.Static).Where(a => a.FieldType == typeof(ThreadLocal)).ToArray()); + if (eccFields.Any() == false) + throw new Exception($"自定义表达式解析错误:类型 {exp3.Method.DeclaringType} 需要定义 static ThreadLocal 字段、字段、字段(重要三次提醒)"); + foreach (var eccField in eccFields) + typeof(ThreadLocal).GetProperty("Value").SetValue(eccField.GetValue(null), ecc, null); + try + { + return string.Concat(exp3.Method.Invoke(null, exp3InvokeParams)); + } + finally + { + foreach (var eccField in eccFields) + typeof(ThreadLocal).GetProperty("Value").SetValue(eccField.GetValue(null), null, null); + } + } var callType = exp3.Object?.Type ?? exp3.Method.DeclaringType; string other3Exp = null; switch (callType.FullName) diff --git a/FreeSql/Internal/UtilsExpressionTree.cs b/FreeSql/Internal/UtilsExpressionTree.cs index 0b999a83..271c3ac9 100644 --- a/FreeSql/Internal/UtilsExpressionTree.cs +++ b/FreeSql/Internal/UtilsExpressionTree.cs @@ -170,10 +170,7 @@ namespace FreeSql.Internal if (colattr.IsNullable == false && colattr.DbDefautValue == null) { var citype = colattr.MapType.IsNullableType() ? colattr.MapType.GetGenericArguments().FirstOrDefault() : colattr.MapType; - if (citype.IsArray) - colattr.DbDefautValue = Array.CreateInstance(citype, 0); - else - colattr.DbDefautValue = Activator.CreateInstance(citype); + colattr.DbDefautValue = citype.CreateInstanceGetDefaultValue(); } trytb.Columns.Add(colattr.Name, col);