- 增加 ExpressionCallAttribute 特性,实现表达式函数自定义解析;

This commit is contained in:
28810 2019-11-20 13:32:49 +08:00
parent 91f0bb92a1
commit d42b2fc2b8
6 changed files with 266 additions and 160 deletions

View File

@ -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<SysModule>()
.ToSql(a => a.CreateTime.FormatDateTime("yyyy-MM-dd"));
}
}
[ExpressionCall]
public static class DbFunc
{
static ThreadLocal<ExpressionCallContext> context = new ThreadLocal<ExpressionCallContext>();
public static string FormatDateTime(this DateTime that, string arg1)
{
return $"date_format({context.Value.Values["arg1"]})";
}
}
}

View File

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace FreeSql.DataAnnotations
{
/// <summary>
/// 自定义表达式函数解析<para></para>
/// 注意:请使用静态扩展类
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class ExpressionCallAttribute : Attribute
{
}
public class ExpressionCallContext
{
/// <summary>
/// 数据库类型,可用于适配多种数据库环境
/// </summary>
public DataType DataType { get; set; }
/// <summary>
/// 已解析的表达式中参数内容
/// </summary>
public Dictionary<string, string> Values { get; } = new Dictionary<string, string>();
}
}

View File

@ -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<Type, ParameterInfo[]> _dicGetDefaultValueFirstConstructorsParameters = new ConcurrentDictionary<Type, ParameterInfo[]>();
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<Type, Dictionary<string, PropertyInfo>> _dicGetPropertiesDictIgnoreCase = new ConcurrentDictionary<Type, Dictionary<string, PropertyInfo>>();
public static Dictionary<string, PropertyInfo> GetPropertiesDictIgnoreCase(this Type that) => that == null ? null : _dicGetPropertiesDictIgnoreCase.GetOrAdd(that, tp =>

View File

@ -152,6 +152,22 @@
<param name="value"></param>
<returns></returns>
</member>
<member name="T:FreeSql.DataAnnotations.ExpressionCallAttribute">
<summary>
自定义表达式函数解析<para></para>
注意:请使用静态扩展类
</summary>
</member>
<member name="P:FreeSql.DataAnnotations.ExpressionCallContext.DataType">
<summary>
数据库类型,可用于适配多种数据库环境
</summary>
</member>
<member name="P:FreeSql.DataAnnotations.ExpressionCallContext.Values">
<summary>
已解析的表达式中参数内容
</summary>
</member>
<member name="P:FreeSql.DataAnnotations.IndexAttribute.Name">
<summary>
索引名
@ -2000,6 +2016,137 @@
<param name="parms"></param>
<returns></returns>
</member>
<member name="M:FreeSql.IAdo.ExecuteReaderAsync(System.Func{System.Data.Common.DbDataReader,System.Threading.Tasks.Task},System.Data.CommandType,System.String,System.Data.Common.DbParameter[])">
<summary>
查询若使用读写分离查询【从库】条件cmdText.StartsWith("SELECT "),否则查询【主库】
</summary>
<param name="readerHander"></param>
<param name="cmdType"></param>
<param name="cmdText"></param>
<param name="cmdParms"></param>
</member>
<member name="M:FreeSql.IAdo.ExecuteReaderAsync(System.Func{System.Data.Common.DbDataReader,System.Threading.Tasks.Task},System.String,System.Object)">
<summary>
查询ExecuteReaderAsync(dr => {}, "select * from user where age > @age", new { age = 25 })
</summary>
<param name="cmdText"></param>
<param name="parms"></param>
</member>
<member name="M:FreeSql.IAdo.ExecuteArrayAsync(System.Data.CommandType,System.String,System.Data.Common.DbParameter[])">
<summary>
查询
</summary>
<param name="cmdText"></param>
<param name="cmdParms"></param>
</member>
<member name="M:FreeSql.IAdo.ExecuteArrayAsync(System.String,System.Object)">
<summary>
查询ExecuteArrayAsync("select * from user where age > @age", new { age = 25 })
</summary>
<param name="cmdText"></param>
<param name="parms"></param>
<returns></returns>
</member>
<member name="M:FreeSql.IAdo.ExecuteDataSetAsync(System.Data.CommandType,System.String,System.Data.Common.DbParameter[])">
<summary>
查询
</summary>
<param name="cmdText"></param>
<param name="cmdParms"></param>
</member>
<member name="M:FreeSql.IAdo.ExecuteDataSetAsync(System.String,System.Object)">
<summary>
查询ExecuteDataSetAsync("select * from user where age > @age; select 2", new { age = 25 })
</summary>
<param name="cmdText"></param>
<param name="parms"></param>
<returns></returns>
</member>
<member name="M:FreeSql.IAdo.ExecuteDataTableAsync(System.Data.CommandType,System.String,System.Data.Common.DbParameter[])">
<summary>
查询
</summary>
<param name="cmdText"></param>
<param name="cmdParms"></param>
</member>
<member name="M:FreeSql.IAdo.ExecuteDataTableAsync(System.String,System.Object)">
<summary>
查询ExecuteDataTableAsync("select * from user where age > @age", new { age = 25 })
</summary>
<param name="cmdText"></param>
<param name="parms"></param>
<returns></returns>
</member>
<member name="M:FreeSql.IAdo.ExecuteNonQueryAsync(System.Data.CommandType,System.String,System.Data.Common.DbParameter[])">
<summary>
在【主库】执行
</summary>
<param name="cmdType"></param>
<param name="cmdText"></param>
<param name="cmdParms"></param>
</member>
<member name="M:FreeSql.IAdo.ExecuteNonQueryAsync(System.String,System.Object)">
<summary>
在【主库】执行ExecuteNonQueryAsync("delete from user where age > @age", new { age = 25 })
</summary>
<param name="cmdText"></param>
<param name="parms"></param>
<returns></returns>
</member>
<member name="M:FreeSql.IAdo.ExecuteScalarAsync(System.Data.CommandType,System.String,System.Data.Common.DbParameter[])">
<summary>
在【主库】执行
</summary>
<param name="cmdType"></param>
<param name="cmdText"></param>
<param name="cmdParms"></param>
</member>
<member name="M:FreeSql.IAdo.ExecuteScalarAsync(System.String,System.Object)">
<summary>
在【主库】执行ExecuteScalarAsync("select 1 from user where age > @age", new { age = 25 })
</summary>
<param name="cmdText"></param>
<param name="parms"></param>
<returns></returns>
</member>
<member name="M:FreeSql.IAdo.QueryAsync``1(System.Data.CommandType,System.String,System.Data.Common.DbParameter[])">
<summary>
执行SQL返回对象集合QueryAsync&lt;User&gt;("select * from user where age > @age", new SqlParameter { ParameterName = "age", Value = 25 })
</summary>
<typeparam name="T"></typeparam>
<param name="cmdType"></param>
<param name="cmdText"></param>
<param name="cmdParms"></param>
<returns></returns>
</member>
<member name="M:FreeSql.IAdo.QueryAsync``1(System.String,System.Object)">
<summary>
执行SQL返回对象集合QueryAsync&lt;User&gt;("select * from user where age > @age", new { age = 25 })
</summary>
<typeparam name="T"></typeparam>
<param name="cmdText"></param>
<param name="parms"></param>
<returns></returns>
</member>
<member name="M:FreeSql.IAdo.QueryAsync``2(System.Data.CommandType,System.String,System.Data.Common.DbParameter[])">
<summary>
执行SQL返回对象集合Query&lt;User&gt;("select * from user where age > @age; select * from address", new SqlParameter { ParameterName = "age", Value = 25 })
</summary>
<typeparam name="T1"></typeparam>
<param name="cmdType"></param>
<param name="cmdText"></param>
<param name="cmdParms"></param>
<returns></returns>
</member>
<member name="M:FreeSql.IAdo.QueryAsync``2(System.String,System.Object)">
<summary>
执行SQL返回对象集合Query&lt;User&gt;("select * from user where age > @age; select * from address", new { age = 25 })
</summary>
<typeparam name="T1"></typeparam>
<param name="cmdText"></param>
<param name="parms"></param>
<returns></returns>
</member>
<member name="P:FreeSql.IAop.ParseExpression">
<summary>
可自定义解析表达式
@ -2710,159 +2857,3 @@
</member>
</members>
</doc>
</member>
<member name="M:System.Linq.Expressions.LambadaExpressionExtensions.Or``1(System.Linq.Expressions.Expression{System.Func{``0,System.Boolean}},System.Linq.Expressions.Expression{System.Func{``0,System.Boolean}})">
<summary>
使用 or 拼接两个 lambda 表达式
</summary>
<returns></returns>
</member>
<member name="M:System.Linq.Expressions.LambadaExpressionExtensions.Or``1(System.Linq.Expressions.Expression{System.Func{``0,System.Boolean}},System.Boolean,System.Linq.Expressions.Expression{System.Func{``0,System.Boolean}})">
<summary>
使用 or 拼接两个 lambda 表达式
</summary>
<typeparam name="T"></typeparam>
<param name="exp1"></param>
<param name="condition">true 时生效</param>
<param name="exp2"></param>
<returns></returns>
</member>
<member name="M:System.Linq.Expressions.LambadaExpressionExtensions.Not``1(System.Linq.Expressions.Expression{System.Func{``0,System.Boolean}},System.Boolean)">
<summary>
将 lambda 表达式取反
</summary>
<typeparam name="T"></typeparam>
<param name="exp"></param>
<param name="condition">true 时生效</param>
<returns></returns>
</member>
<member name="M:FreeUtil.NewMongodbId">
<summary>
生成类似Mongodb的ObjectId有序、不重复Guid
</summary>
<returns></returns>
</member>
<member name="M:IFreeSql.Insert``1">
<summary>
插入数据
</summary>
<typeparam name="T1"></typeparam>
<returns></returns>
</member>
<member name="M:IFreeSql.Insert``1(``0)">
<summary>
插入数据,传入实体
</summary>
<typeparam name="T1"></typeparam>
<param name="source"></param>
<returns></returns>
</member>
<member name="M:IFreeSql.Insert``1(``0[])">
<summary>
插入数据,传入实体数组
</summary>
<typeparam name="T1"></typeparam>
<param name="source"></param>
<returns></returns>
</member>
<member name="M:IFreeSql.Insert``1(System.Collections.Generic.List{``0})">
<summary>
插入数据,传入实体集合
</summary>
<typeparam name="T1"></typeparam>
<param name="source"></param>
<returns></returns>
</member>
<member name="M:IFreeSql.Insert``1(System.Collections.Generic.IEnumerable{``0})">
<summary>
插入数据,传入实体集合
</summary>
<typeparam name="T1"></typeparam>
<param name="source"></param>
<returns></returns>
</member>
<member name="M:IFreeSql.Update``1">
<summary>
修改数据
</summary>
<typeparam name="T1"></typeparam>
<returns></returns>
</member>
<member name="M:IFreeSql.Update``1(System.Object)">
<summary>
修改数据,传入动态对象如:主键值 | new[]{主键值1,主键值2} | TEntity1 | new[]{TEntity1,TEntity2} | new{id=1}
</summary>
<typeparam name="T1"></typeparam>
<param name="dywhere">主键值、主键值集合、实体、实体集合、匿名对象、匿名对象集合</param>
<returns></returns>
</member>
<member name="M:IFreeSql.Select``1">
<summary>
查询数据
</summary>
<typeparam name="T1"></typeparam>
<returns></returns>
</member>
<member name="M:IFreeSql.Select``1(System.Object)">
<summary>
查询数据,传入动态对象如:主键值 | new[]{主键值1,主键值2} | TEntity1 | new[]{TEntity1,TEntity2} | new{id=1}
</summary>
<typeparam name="T1"></typeparam>
<param name="dywhere">主键值、主键值集合、实体、实体集合、匿名对象、匿名对象集合</param>
<returns></returns>
</member>
<member name="M:IFreeSql.Delete``1">
<summary>
删除数据
</summary>
<typeparam name="T1"></typeparam>
<returns></returns>
</member>
<member name="M:IFreeSql.Delete``1(System.Object)">
<summary>
删除数据,传入动态对象如:主键值 | new[]{主键值1,主键值2} | TEntity1 | new[]{TEntity1,TEntity2} | new{id=1}
</summary>
<typeparam name="T1"></typeparam>
<param name="dywhere">主键值、主键值集合、实体、实体集合、匿名对象、匿名对象集合</param>
<returns></returns>
</member>
<member name="M:IFreeSql.Transaction(System.Action)">
<summary>
开启事务不支持异步60秒未执行完将自动提交
</summary>
<param name="handler">事务体 () => {}</param>
</member>
<member name="M:IFreeSql.Transaction(System.Action,System.TimeSpan)">
<summary>
开启事务(不支持异步)
</summary>
<param name="handler">事务体 () => {}</param>
<param name="timeout">超时,未执行完将自动提交</param>
</member>
<member name="P:IFreeSql.Ado">
<summary>
数据库访问对象
</summary>
</member>
<member name="P:IFreeSql.Aop">
<summary>
所有拦截方法都在这里
</summary>
</member>
<member name="P:IFreeSql.CodeFirst">
<summary>
CodeFirst 模式开发相关方法
</summary>
</member>
<member name="P:IFreeSql.DbFirst">
<summary>
DbFirst 模式开发相关方法
</summary>
</member>
<member name="P:IFreeSql.GlobalFilter">
<summary>
全局过滤设置,可默认附加为 Select/Update/Delete 条件
</summary>
</member>
</members>
</doc>

View File

@ -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<Type, bool> _dicTypeExistsExpressionCallAttribute = new ConcurrentDictionary<Type, bool>();
static ConcurrentDictionary<Type, FieldInfo[]> _dicTypeExpressionCallClassContextFields = new ConcurrentDictionary<Type, FieldInfo[]>();
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<ExpressionCallContext>)).ToArray());
if (eccFields.Any() == false)
throw new Exception($"自定义表达式解析错误:类型 {exp3.Method.DeclaringType} 需要定义 static ThreadLocal<ExpressionCallContext> 字段、字段、字段(重要三次提醒)");
foreach (var eccField in eccFields)
typeof(ThreadLocal<ExpressionCallContext>).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<ExpressionCallContext>).GetProperty("Value").SetValue(eccField.GetValue(null), null, null);
}
}
var callType = exp3.Object?.Type ?? exp3.Method.DeclaringType;
string other3Exp = null;
switch (callType.FullName)

View File

@ -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);