using FreeSql; using FreeSql.DataAnnotations; using FreeSql.Internal.CommonProvider; using Newtonsoft.Json; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Threading; public static class FreeSqlJsonMapCoreExtensions { static int _isAoped = 0; static ConcurrentDictionary _dicTypes = new ConcurrentDictionary(); static MethodInfo MethodJsonConvertDeserializeObject = typeof(JsonConvert).GetMethod("DeserializeObject", new[] { typeof(string), typeof(Type), typeof(JsonSerializerSettings) }); static MethodInfo MethodJsonConvertSerializeObject = typeof(JsonConvert).GetMethod("SerializeObject", new[] { typeof(object), typeof(JsonSerializerSettings) }); static ConcurrentDictionary> _dicJsonMapFluentApi = new ConcurrentDictionary>(); static object _concurrentObj = new object(); public static ColumnFluent JsonMap(this ColumnFluent col) { _dicJsonMapFluentApi.GetOrAdd(col._entityType, et => new ConcurrentDictionary()) .GetOrAdd(col._property.Name, pn => true); return col; } /// /// When the entity class property is and the attribute is marked as , map storage in JSON format.
/// 当实体类属性为【对象】时,并且标记特性 [JsonMap] 时,该属性将以JSON形式映射存储 ///
/// public static void UseJsonMap(this IFreeSql fsql) { UseJsonMap(fsql, JsonConvert.DefaultSettings?.Invoke() ?? new JsonSerializerSettings()); } public static void UseJsonMap(this IFreeSql fsql, JsonSerializerSettings settings) { if (Interlocked.CompareExchange(ref _isAoped, 1, 0) == 0) { FreeSql.Internal.Utils.GetDataReaderValueBlockExpressionSwitchTypeFullName.Add((LabelTarget returnTarget, Expression valueExp, Type type) => { if (_dicTypes.ContainsKey(type)) return Expression.IfThenElse( Expression.TypeIs(valueExp, type), Expression.Return(returnTarget, valueExp), Expression.Return(returnTarget, Expression.TypeAs(Expression.Call(MethodJsonConvertDeserializeObject, Expression.Convert(valueExp, typeof(string)), Expression.Constant(type),Expression.Constant(settings)), type)) ); return null; }); } fsql.Aop.ConfigEntityProperty += (s, e) => { var isJsonMap = e.Property.GetCustomAttributes(typeof(JsonMapAttribute), false).Any() || _dicJsonMapFluentApi.TryGetValue(e.EntityType, out var tryjmfu) && tryjmfu.ContainsKey(e.Property.Name); if (isJsonMap) { if (_dicTypes.ContainsKey(e.Property.PropertyType) == false && FreeSql.Internal.Utils.dicExecuteArrayRowReadClassOrTuple.ContainsKey(e.Property.PropertyType)) return; //基础类型使用 JsonMap 无效 if (e.ModifyResult.MapType == null) { e.ModifyResult.MapType = typeof(string); e.ModifyResult.StringLength = -2; } if (_dicTypes.TryAdd(e.Property.PropertyType, true)) { lock (_concurrentObj) { FreeSql.Internal.Utils.dicExecuteArrayRowReadClassOrTuple[e.Property.PropertyType] = true; FreeSql.Internal.Utils.GetDataReaderValueBlockExpressionObjectToStringIfThenElse.Add((LabelTarget returnTarget, Expression valueExp, Expression elseExp, Type type) => { return Expression.IfThenElse( Expression.TypeIs(valueExp, e.Property.PropertyType), Expression.Return(returnTarget, Expression.Call(MethodJsonConvertSerializeObject, Expression.Convert(valueExp, typeof(object)), Expression.Constant(settings)), typeof(object)), elseExp); }); } } } }; switch (fsql.Ado.DataType) { case DataType.Sqlite: case DataType.MySql: case DataType.OdbcMySql: case DataType.CustomMySql: case DataType.SqlServer: case DataType.OdbcSqlServer: case DataType.CustomSqlServer: case DataType.Oracle: case DataType.OdbcOracle: case DataType.CustomOracle: case DataType.Dameng: case DataType.DuckDB: case DataType.PostgreSQL: case DataType.OdbcPostgreSQL: case DataType.CustomPostgreSQL: case DataType.KingbaseES: case DataType.ShenTong: fsql.Aop.ParseExpression += (_, e) => { //if (e.Expression is MethodCallExpression callExp) //{ // var objExp = callExp.Object; // var objType = objExp?.Type; // if (objType?.FullName == "System.Byte[]") return; // if (objType == null && callExp.Method.DeclaringType == typeof(Enumerable)) // { // objExp = callExp.Arguments.FirstOrDefault(); // objType = objExp?.Type; // } // if (objType == null) objType = callExp.Method.DeclaringType; // if (objType != null || objType.IsArrayOrList()) // { // string left = null; // switch (callExp.Method.Name) // { // case "Any": // left = objExp == null ? null : getExp(objExp); // if (left.StartsWith("(") || left.EndsWith(")")) left = $"array[{left.TrimStart('(').TrimEnd(')')}]"; // return $"(case when {left} is null then 0 else array_length({left},1) end > 0)"; // case "Contains": // } // } //} //处理 mysql enum -> int switch (fsql.Ado.DataType) { case DataType.MySql: case DataType.OdbcMySql: case DataType.CustomMySql: if (e.Expression.NodeType == ExpressionType.Equal && e.Expression is BinaryExpression binaryExpression) { var comonExp = (fsql.Select() as Select0Provider)._commonExpression; var leftExp = binaryExpression.Left; var rightExp = binaryExpression.Right; if ( leftExp.NodeType == ExpressionType.Convert && leftExp is UnaryExpression leftExpUexp && leftExpUexp.Operand?.Type.NullableTypeOrThis().IsEnum == true && rightExp.NodeType == ExpressionType.Convert && rightExp is UnaryExpression rightExpUexp && rightExpUexp.Operand?.Type.NullableTypeOrThis().IsEnum == true) { string leftSql = null, rightSql = null; if (leftExpUexp.Operand.NodeType == ExpressionType.MemberAccess && LocalParseMemberExp(leftExpUexp.Operand as MemberExpression)) leftSql = e.Result; if (rightExpUexp.Operand.NodeType == ExpressionType.MemberAccess && LocalParseMemberExp(rightExpUexp.Operand as MemberExpression)) rightSql = e.Result; if (!string.IsNullOrEmpty(leftSql) && string.IsNullOrEmpty(rightSql) && !rightExpUexp.Operand.IsParameter()) rightSql = comonExp.formatSql(Expression.Lambda(rightExpUexp.Operand).Compile().DynamicInvoke(), typeof(int), null, null); if (string.IsNullOrEmpty(leftSql) && !string.IsNullOrEmpty(rightSql) && !leftExpUexp.Operand.IsParameter()) leftSql = comonExp.formatSql(Expression.Lambda(leftExpUexp.Operand).Compile().DynamicInvoke(), typeof(int), null, null); if (!string.IsNullOrEmpty(leftSql) && !string.IsNullOrEmpty(rightSql)) { e.Result = $"{leftSql} = {rightSql}"; return; } e.Result = null; return; } } if (e.Expression.NodeType == ExpressionType.Call && e.Expression is MethodCallExpression callExp && callExp.Method.Name == "Contains") { var objExp = callExp.Object; var objType = objExp?.Type; if (objType?.FullName == "System.Byte[]") return; var argIndex = 0; if (objType == null && callExp.Method.DeclaringType == typeof(Enumerable)) { objExp = callExp.Arguments.FirstOrDefault(); objType = objExp?.Type; argIndex++; if (objType == typeof(string)) return; } if (objType == null) objType = callExp.Method.DeclaringType; if (objType != null || objType.IsArrayOrList()) { var memExp = callExp.Arguments[argIndex]; if (memExp.NodeType == ExpressionType.MemberAccess && memExp.Type.NullableTypeOrThis().IsEnum && LocalParseMemberExp(memExp as MemberExpression)) { if (!objExp.IsParameter()) { var comonExp = (fsql.Select() as Select0Provider)._commonExpression; var rightSql = comonExp.formatSql(Expression.Lambda(objExp).Compile().DynamicInvoke(), typeof(int), null, null); e.Result = $"{e.Result} in {rightSql.Replace(", \r\n \r\n", $") \r\n OR {e.Result} in (")}"; return; } e.Result = null; return; } } } break; } //解析 POCO Json a.Customer.Name if (e.Expression.NodeType == ExpressionType.MemberAccess) LocalParseMemberExp(e.Expression as MemberExpression); bool LocalParseMemberExp(MemberExpression memExp) { if (memExp == null) return false; if (e.Expression.IsParameter() == false) return false; var parentMemExps = new Stack(); parentMemExps.Push(memExp); while (true) { switch (memExp.Expression.NodeType) { case ExpressionType.MemberAccess: case ExpressionType.Parameter: break; default: return false; } switch (memExp.Expression.NodeType) { case ExpressionType.MemberAccess: memExp = memExp.Expression as MemberExpression; if (memExp == null) return false; parentMemExps.Push(memExp); break; case ExpressionType.Parameter: var tb = fsql.CodeFirst.GetTableByEntity(memExp.Expression.Type); if (tb == null) return false; if (tb.ColumnsByCs.TryGetValue(parentMemExps.Pop().Member.Name, out var trycol) == false) return false; if (_dicTypes.ContainsKey(trycol.CsType) == false) return false; var result = e.FreeParse(Expression.MakeMemberAccess(memExp.Expression, tb.Properties[trycol.CsName])); if (parentMemExps.Any() == false) { e.Result = result; return true; } var jsonPath = ""; switch (fsql.Ado.DataType) { case DataType.Sqlite: case DataType.MySql: case DataType.OdbcMySql: case DataType.CustomMySql: StyleJsonExtract(); return true; case DataType.SqlServer: case DataType.OdbcSqlServer: case DataType.CustomSqlServer: case DataType.Oracle: case DataType.OdbcOracle: case DataType.CustomOracle: case DataType.Dameng: StyleJsonValue(); return true; case DataType.DuckDB: StyleDotAccess(); return true; } StylePgJson(); return true; void StyleJsonExtract() { while (parentMemExps.Any()) { memExp = parentMemExps.Pop(); jsonPath = $"{jsonPath}.{memExp.Member.Name}"; } e.Result = $"json_extract({result},'${jsonPath}')"; } void StyleJsonValue() { while (parentMemExps.Any()) { memExp = parentMemExps.Pop(); jsonPath = $"{jsonPath}.{memExp.Member.Name}"; } e.Result = $"json_value({result},'${jsonPath}')"; } void StyleDotAccess() { while (parentMemExps.Any()) { memExp = parentMemExps.Pop(); result = $"{result}['{memExp.Member.Name}']"; } e.Result = result; } void StylePgJson() { while (parentMemExps.Any()) { memExp = parentMemExps.Pop(); var opt = parentMemExps.Any() ? "->" : $"->>{(memExp.Type.IsArrayOrList() ? "/*json array*/" : "")}"; result = $"{result}{opt}'{memExp.Member.Name}'"; } e.Result = result; } } } } }; break; } } }