ToB企服应用市场:ToB评测及商务社交产业平台

标题: 浅究一下Freesql对Json列的实现 [打印本页]

作者: 勿忘初心做自己    时间: 2025-1-13 21:35
标题: 浅究一下Freesql对Json列的实现
前几天发了一篇随笔,聊了一下在利用Sqlsugar的Json列碰见的一些题目,其时对其解决方案并不是很满意。
今天没啥使命,突发奇想的想看看Freesql是如何实现的,由于国产ORM,目前就这两者争锋了。
上一篇随笔的传送门:继续聊一聊sqlsugar的一个机制题目
省流总结

sqlsugar的json列在查询时,如果利用了dto映射,那么dto对应的属性上,必须利用[SugarColumn(IsJson = true)]进行标记,否则该列无法正确的绑定值。 我对这个解决办法不是很满意,由于dto不是实体,它的属性不应该逼迫要求是利用SugarColumn特性,这有些过于耦合了。
接下来,我们来看看Freesql在这块的表现如何。
去其官方查看文档,发现是支持json列的,需要利用[JsonMap]特性进行标记。
先看测试代码:
  1. internal class Program
  2. {
  3.      static void Main(string[] args)
  4.      {
  5.          var freesql = MysqlProvider.Instance;
  6.          freesql.UseJsonMap(); //这行代码是关键
  7.          var entity = freesql.Select<Student>().Where(a => a.Name == "张三").First();
  8.          Console.WriteLine($"entity:{JsonConvert.SerializeObject(entity)}");
  9.          var dto = freesql.Select<Student>().Where(a => a.Name == "张三")
  10.                    .First(x => new StudentDto
  11.                    {
  12.                        StudentAddress = x.Address,
  13.                        StudentId = x.Id,
  14.                        StudentName = x.Name
  15.                    });
  16.          Console.WriteLine($"dto:{JsonConvert.SerializeObject(dto)}");
  17.      }
  18.      [Table(Name = "students")]
  19.      public class Student
  20.      {
  21.          [Column(Name = "id", IsPrimary = true, IsIdentity = true)]
  22.          public int Id { get; set; }
  23.          [Column(Name = "name")]
  24.          public string Name { get; set; }
  25.          [Column(Name = "address")]
  26.          [JsonMap]
  27.          public Address Address { get; set; }
  28.      }
  29.      public class Address
  30.      {
  31.          public string Province { get; set; }
  32.          public string City { get; set; }
  33.          public string Street { get; set; }
  34.      }
  35.      public class StudentDto
  36.      {
  37.          public int StudentId { get; set; }
  38.          public string StudentName { get; set; }
  39.          public Address StudentAddress { get; set; } //请注意这里没有使用 [JsonMap]特性进行标记
  40.      }
  41.      public class MysqlProvider
  42.      {
  43.          private static Lazy<IFreeSql> mysqlLazy = new Lazy<IFreeSql>(() => new FreeSql.FreeSqlBuilder()
  44.       .UseConnectionString(FreeSql.DataType.MySql, "server=127.0.0.1;port=3306;user=root;password=abc123;database=json_test;Charset=utf8;sslMode=None;AllowLoadLocalInfile=true")
  45.       .UseAutoSyncStructure(false)
  46.       .UseMonitorCommand(
  47.           cmd => Trace.WriteLine("\r\n线程" + Thread.CurrentThread.ManagedThreadId + ": " + cmd.CommandText) //监听SQL命令对象,在执行前
  48.                                                                                                            //, (cmd, traceLog) => Console.WriteLine(traceLog)
  49.           )
  50.       .UseLazyLoading(true)
  51.       .Build());
  52.          public static IFreeSql Instance => mysqlLazy.Value;
  53.      }
  54. }
复制代码
直接上结果:

非常棒!Dto没有利用[JsonMap]特性,依然能成功并且正确的绑定值,这完全符合我的盼望,而且也应该就是云云!
调试源码,一探究竟

看到结果后,让我有点兴奋,接下来开始调试源码,看看它是如何实现的。
源码的调试较为繁琐,后面仅贴出一些我以为比较关键的代码。
根据官方文档的示例,利用Json列相关功能前,必须要加上这样一行代码:
fsql.UseJsonMap();
很明显,这个方法里面应该就是核心关键,这个方法的部分代码如下:
  1. /// <summary>
  2. /// When the entity class property is <see cref="object"/> and the attribute is marked as <see cref="JsonMapAttribute"/>, map storage in JSON format. <br />
  3. /// 当实体类属性为【对象】时,并且标记特性 [JsonMap] 时,该属性将以JSON形式映射存储
  4. /// </summary>
  5. /// <returns></returns>
  6. public static void UseJsonMap(this IFreeSql fsql)
  7. {
  8.     UseJsonMap(fsql, JsonConvert.DefaultSettings?.Invoke() ?? new JsonSerializerSettings());
  9. }
  10. public static void UseJsonMap(this IFreeSql fsql, JsonSerializerSettings settings)
  11. {
  12.     if (Interlocked.CompareExchange(ref _isAoped, 1, 0) == 0)
  13.     {
  14.         FreeSql.Internal.Utils.GetDataReaderValueBlockExpressionSwitchTypeFullName.Add((LabelTarget returnTarget, Expression valueExp, Type type) =>
  15.         {
  16.             if (_dicTypes.ContainsKey(type)) return Expression.IfThenElse(
  17.                 Expression.TypeIs(valueExp, type),
  18.                 Expression.Return(returnTarget, valueExp),
  19.                 Expression.Return(returnTarget, Expression.TypeAs(Expression.Call(MethodJsonConvertDeserializeObject, Expression.Convert(valueExp, typeof(string)), Expression.Constant(type)), type))
  20.             );
  21.             return null;
  22.         });
  23.     }
  24.     //关键代码,实体属性配置事件,整个功能实现原理的核心
  25.     fsql.Aop.ConfigEntityProperty += (s, e) =>
  26.     {
  27.         var isJsonMap = e.Property.GetCustomAttributes(typeof(JsonMapAttribute), false).Any() || _dicJsonMapFluentApi.TryGetValue(e.EntityType, out var tryjmfu) && tryjmfu.ContainsKey(e.Property.Name);
  28.         if (isJsonMap)
  29.         {
  30.             if (_dicTypes.ContainsKey(e.Property.PropertyType) == false &&
  31.                 FreeSql.Internal.Utils.dicExecuteArrayRowReadClassOrTuple.ContainsKey(e.Property.PropertyType))
  32.                 return; //基础类型使用 JsonMap 无效
  33.             if (e.ModifyResult.MapType == null)
  34.             {
  35.                 e.ModifyResult.MapType = typeof(string);
  36.                 e.ModifyResult.StringLength = -2;
  37.             }
  38.             if (_dicTypes.TryAdd(e.Property.PropertyType, true))
  39.             {
  40.                 lock (_concurrentObj)
  41.                 {
  42.                     FreeSql.Internal.Utils.dicExecuteArrayRowReadClassOrTuple[e.Property.PropertyType] = true;
  43.                     //关键代码,将json列类型进行了存储。
  44.                     FreeSql.Internal.Utils.GetDataReaderValueBlockExpressionObjectToStringIfThenElse.Add((LabelTarget returnTarget, Expression valueExp, Expression elseExp, Type type) =>
  45.                     {
  46.                         return Expression.IfThenElse(
  47.                             Expression.TypeIs(valueExp, e.Property.PropertyType),
  48.                             Expression.Return(returnTarget, Expression.Call(MethodJsonConvertSerializeObject, Expression.Convert(valueExp, typeof(object)), Expression.Constant(settings)), typeof(object)),
  49.                             elseExp);
  50.                     });
  51.                 }
  52.             }
  53.         }
  54.     };
  55.     //省略....
  56. }
复制代码
从上述代码可以看到,UseJsonMap方法,注册了ConfigEntityProperty事件,如果该属性有JsonMap特性,则将其保存到FreeSql.Internal.Utils.GetDataReaderValueBlockExpressionObjectToStringIfThenElse里面。
那么,在那里触发的这个事件呢?继续调试...
在Uitls类里面,有一个方法GetTableByEntity,这里遍历实体的属性,然后调用CommonUtils的GetEntityColumnAttribute方法,处置惩罚实体属性的PropertyInfo:
  1. internal static TableInfo GetTableByEntity(Type entity, CommonUtils common)
  2. {
  3.     // ....省略
  4.     foreach (var p in trytb.Properties.Values)
  5.     {
  6.         var setMethod = p.GetSetMethod(true); //trytb.Type.GetMethod($"set_{p.Name}");
  7.         var colattr = common.GetEntityColumnAttribute(entity, p); //核心
  8.     }
  9.     //....省略
  10. }
复制代码
  1. public ColumnAttribute GetEntityColumnAttribute(Type type, PropertyInfo proto)
  2. {
  3.     var attr = new ColumnAttribute();
  4.     foreach (var mp in _mappingPriorityTypes)
  5.     {
  6.         switch (mp)
  7.         {
  8.             case MappingPriorityType.Aop:
  9.                 if (_orm.Aop.ConfigEntityPropertyHandler != null)
  10.                 {
  11.                     var aope = new Aop.ConfigEntityPropertyEventArgs(type, proto)
  12.                     {
  13.                        //....省略
  14.                     };
  15.                     _orm.Aop.ConfigEntityPropertyHandler(_orm, aope); //这里触发ConfigEntityProperty事件
  16.                     //....省略
  17.                 }
  18.                 break;
  19.             case MappingPriorityType.FluentApi:
  20.                 //省略
  21.                 break;
  22.             case MappingPriorityType.Attribute:
  23.                 //省略
  24.                 break;
  25.         }
  26.     }
  27.     ColumnAttribute ret = null;
  28.     //....省略
  29.     return ret;
  30. }
复制代码



此时,json列对应的类型信息已经被freesql拿到,那么只需要在赋值的时间,从FreeSql.Internal.Utils.GetDataReaderValueBlockExpressionObjectToStringIfThenElse拿出即可。
颠末一系列调试,来到了ReadAnonymous方法,该方法里面调用了GetDataReaderValue的方法,终极调用GetDataReaderValueBlockExpression方法,生成终极的表达式树,然后执行拿到值并且完成Json列的数据绑定:
  1. public static object GetDataReaderValue(Type type, object value)
  2. {
  3.     //if (value == null || value == DBNull.Value) return Activator.CreateInstance(type);
  4.     if (type == null) return value;
  5.     var valueType = value?.GetType() ?? type;
  6.     if (TypeHandlers.TryGetValue(valueType, out var typeHandler)) return typeHandler.Serialize(value);
  7.     var func = _dicGetDataReaderValue.GetOrAdd(type, k1 => new ConcurrentDictionary<Type, Func<object, object>>()).GetOrAdd(valueType, valueType2 =>
  8.     {
  9.         var parmExp = Expression.Parameter(typeof(object), "value");
  10.         var exp = GetDataReaderValueBlockExpression(type, parmExp); //核心代码
  11.         return Expression.Lambda<Func<object, object>>(exp, parmExp).Compile();
  12.     });
  13.     try
  14.     {
  15.         return func(value);
  16.     }
  17.     catch (Exception ex)
  18.     {
  19.         throw new ArgumentException(CoreErrorStrings.ExpressionTree_Convert_Type_Error(string.Concat(value), value.GetType().FullName, type.FullName, ex.Message));
  20.     }
  21. }
复制代码
  1. public static Expression GetDataReaderValueBlockExpression(Type type, Expression value)
  2. {
  3.     var returnTarget = Expression.Label(typeof(object));
  4.     var valueExp = Expression.Variable(typeof(object), "locvalue");
  5.     Expression LocalFuncGetExpression(bool ignoreArray = false)
  6.     {
  7.         //....省略
  8.         var typeOrg = type;
  9.         if (type.IsNullableType()) type = type.GetGenericArguments().First();
  10.         Expression tryparseExp = null;
  11.         Expression tryparseBooleanExp = null;
  12.         ParameterExpression tryparseVarExp = null;
  13.         ParameterExpression tryparseVarExpDecimal = null;
  14.         switch (type.FullName)
  15.         {
  16.             case "System.Guid":
  17.             case "System.Numerics.BigInteger":
  18.             case "System.TimeOnly": //....省略,下同
  19.             case "System.TimeSpan":
  20.             case "System.DateOnly":
  21.             case "System.DateTime":
  22.             case "System.DateTimeOffset":
  23.             case "System.Char":
  24.             case "System.SByte":
  25.             case "System.Int16":
  26.             case "System.Int32":
  27.             case "System.Int64":
  28.             case "System.Byte":
  29.             case "System.UInt16":
  30.             case "System.UInt32":
  31.             case "System.UInt64":
  32.             case "System.Single":
  33.             case "System.Double":
  34.             case "System.Decimal":
  35.             case "System.Boolean":
  36.             case "System.Collections.BitArray":
  37.             default:
  38.                 if (type.IsEnum && TypeHandlers.ContainsKey(type) == false)
  39.                     return Expression.IfThenElse(
  40.                         Expression.Equal(Expression.TypeAs(valueExp, typeof(string)), Expression.Constant(string.Empty)),
  41.                         Expression.Return(returnTarget, Expression.Convert(Expression.Default(type), typeof(object))),
  42.                         Expression.Return(returnTarget, Expression.Call(MethodEnumParse, Expression.Constant(type, typeof(Type)), Expression.Call(MethodToString, valueExp), Expression.Constant(true, typeof(bool))))
  43.                     );
  44.                 foreach (var switchFunc in GetDataReaderValueBlockExpressionSwitchTypeFullName)
  45.                 {
  46.                     var switchFuncRet = switchFunc(returnTarget, valueExp, type);//拿出最开始存储的Json列相关委托并且执行
  47.                     if (switchFuncRet != null) return switchFuncRet;
  48.                 }
  49.                 break;
  50.         }
  51.     };
  52.     return Expression.Block(
  53.         new[] { valueExp },
  54.         Expression.Assign(valueExp, Expression.Convert(value, typeof(object))),
  55.         Expression.IfThenElse(
  56.             Expression.OrElse(
  57.                 Expression.Equal(valueExp, Expression.Constant(null)),
  58.                 Expression.Equal(valueExp, Expression.Constant(DBNull.Value))
  59.             ),
  60.             Expression.Return(returnTarget, Expression.Convert(Expression.Default(type), typeof(object))),
  61.             LocalFuncGetExpression()
  62.         ),
  63.         Expression.Label(returnTarget, Expression.Default(typeof(object)))
  64.     );
  65. }
复制代码


总结

总的来说,Freesql基于其核心的表达式树(Expression),利用事件注册,全局缓存了Json列对象的信息,实现了Json列的查询和绑定操作,并且DTO的属性映射也能主动完成,这点照旧非常棒的。
后面有时间,看看能否鉴戒这个思路,去改造一下sqlsugar。
快过年了,提前给大家拜个早年,祝大家生活幸福,工作顺利!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4