首页 > 数据库 >继续聊一聊sqlsugar的一个机制问题

继续聊一聊sqlsugar的一个机制问题

时间:2025-01-03 18:24:23浏览次数:6  
标签:OpCodes dto generator public 聊一聊 机制 columnInfo Emit sqlsugar

几个月前换了新工作,从技术负责人的岗位上下来,继续回归码农写代码,在新公司中,我不是技术负责人,没太多的话语权。

公司这边项目统一都是使用了SqlSguar这个orm,我也跟着使用了几个月,期间碰见了不少奇奇怪怪的问题,甚至之前特意写文章“骂”过,但是今天要聊的这个问题,至今快月余,依旧让我记忆深刻,以至于控制不住自己要再写一篇文章来聊聊这件事。

 

 

准备工作

我准备了这样一张表来进行模拟:

image

显而易见,address里面存储的是一个json对象。 这张表对应的实体是这样的:


    [SugarTable("students", "学生表")]
    public class Student
    {
        [SugarColumn(ColumnName = "id", IsPrimaryKey = true, IsIdentity = true)]
        public int Id { get; set; }

        [SugarColumn(ColumnName = "name")]
        public string Name { get; set; }

        [SugarColumn(ColumnName = "address", IsJson = true)]
        public Address Address { get; set; }
    }

    public class Address
    {
        public string Province { get; set; }

        public string City { get; set; }

        public string Street { get; set; }
    }

只是作演示使用,所以就没有按照规范写注释,请大家谅解。

有一个Dto对象:

 public class StudentDto
 {
     public int StudentId { get; set; }

     public string StudentName { get; set; }

     public Address StudentAddress { get; set; }
 }

起因

为了模拟我当时的情况,准备了下面几行代码:

  var entity= client.Queryable<Student>().Where(t=>t.Name=="张三").First();

  Console.WriteLine($"entity:{JsonConvert.SerializeObject(entity)}");

  var dto = client.Queryable<Student>().Where(t => t.Name == "张三").Select(t => new StudentDto()
  {
      StudentId = t.Id,
      StudentName = t.Name,
      StudentAddress = t.Address
  }).First();

  Console.WriteLine($"dto:{JsonConvert.SerializeObject(dto)}");

然后得到了下面的输出结果:

entity:{"Id":1,"Name":"张三","Address":{"Province":"湖北省","City":"武汉市","Street":"发展大道234号"}}

dto:{"StudentId":1,"StudentName":"张三","StudentAddress":null}

Why???? 为什么 dtoStudentAddress的值会是NULL???

我尝试了半个多小时,依旧没有解决这个问题,也没找出原因,最后在万能的群友的帮助下,我找到了解决办法:在dto的StudentAddress属性上,加上这样一个特性标记:[SugarColumn(IsJson = true)]

 public class StudentDto
 {
     public int StudentId { get; set; }

     public string StudentName { get; set; }

     [SugarColumn(IsJson = true)]
     public Address StudentAddress { get; set; }
 }

我再试了一下,问题解决:

entity:{"Id":1,"Name":"张三","Address":{"Province":"湖北省","City":"武汉市","Street":"发展大道234号"}}

dto:{"StudentId":1,"StudentName":"张三","StudentAddress":{"Province":"湖北省","City":"武汉市","Street":"发展大道234号"}}

问题真的解决了吗?

说实话,这个解决方案我是不太满意的。
首先,StudentDto是一个dto对象,它的属性为何非要被打上SugarColumn特性标记?它又不是数据表对应的实体对象。
其次,现在的开发框架体系,无论是多层架构,还是基于DDD模式的那一套,dto对象通常都是单独的一层,(一般命名为 shard或者contract等),这一层不会去引用基础设施层或者是持久化层(Repository),那该如何给dto打上SugarColumn特性标记?强行引用,就破坏了项目的整体引用结构。我不知道你们是不是能接受,反正我这个中度处女座强迫症洁癖症患者是真接受不了。
最后,这种解决方式,真的很违反直觉

刨根问底

我打算空闲了,去研究研究Sqlsugar的源码,看看有没有办法优雅的解决掉这个问题。

后面我在群里吹下了牛逼,为了不被打脸,我花了点时间研究源码,好戏正式开始:

调试过程较为繁琐,这里就只展示结果以及部分关键点

开始之前,先把DTO恢复到最开始的样子:

public class StudentDto
{
    public int StudentId { get; set; }

    public string StudentName { get; set; }

    public Address StudentAddress { get; set; }
}

1.釜底抽薪,先找到最后赋值的地方,看看是根据如何进行的数据绑定

经过繁琐的调试,最后找到了关键的地方,在IDataReaderEntityBuilder文件的第300行,CreateBuilder方法里面,找到了数据行的处理逻辑。(该文件在 SqlSugar项目的Abstract\DbBindProvider文件夹内)

image

上图有一个非常重要的信息:
sqlguar将dto当作了跟数据表对应的实体类型,并且将其属性包装成了EntityColumnInfo类型。
上图中那个foreach 循环的源码如下:

 foreach (var columnInfo in columnInfos)
 {
     string fileName = columnInfo.DbColumnName ?? columnInfo.PropertyName;
     if (columnInfo.IsIgnore && !this.ReaderKeys.Any(it => it.Equals(fileName, StringComparison.CurrentCultureIgnoreCase)))
     {
         continue;
     }
     else if (columnInfo.ForOwnsOnePropertyInfo!=null) 
     {
         continue;
     }
     if (columnInfo != null && columnInfo.PropertyInfo.GetSetMethod(true) != null)
     {
         var isGemo = columnInfo.PropertyInfo?.PropertyType?.FullName=="NetTopologySuite.Geometries.Geometry";
         if (!isGemo&&columnInfo.PropertyInfo.PropertyType.IsClass() && columnInfo.PropertyInfo.PropertyType != UtilConstants.ByteArrayType && columnInfo.PropertyInfo.PropertyType != UtilConstants.ObjType)
         {
             if (this.ReaderKeys.Any(it => it.Equals(fileName, StringComparison.CurrentCultureIgnoreCase)))
             {
                 BindClass(generator, result, columnInfo, ReaderKeys.First(it => it.Equals(fileName, StringComparison.CurrentCultureIgnoreCase)));
             }
             else if (this.ReaderKeys.Any(it => it.Equals(columnInfo.PropertyName, StringComparison.CurrentCultureIgnoreCase)))
             {
                 BindClass(generator, result, columnInfo, ReaderKeys.First(it => it.Equals(columnInfo.PropertyName, StringComparison.CurrentCultureIgnoreCase)));
             }
         }
         else if (!isGemo && columnInfo.IsJson && columnInfo.PropertyInfo.PropertyType != UtilConstants.StringType)
         {   //json is struct
             if (this.ReaderKeys.Any(it => it.Equals(fileName, StringComparison.CurrentCultureIgnoreCase)))
             {
                 BindClass(generator, result, columnInfo, ReaderKeys.First(it => it.Equals(fileName, StringComparison.CurrentCultureIgnoreCase)));
             }
         }
         else
         {
             if (this.ReaderKeys.Any(it => it.Equals(fileName, StringComparison.CurrentCultureIgnoreCase)))
             {
                 BindField(generator, result, columnInfo, ReaderKeys.First(it => it.Equals(fileName, StringComparison.CurrentCultureIgnoreCase)));
             }
             else if (this.ReaderKeys.Any(it => it.Equals(columnInfo.PropertyName, StringComparison.CurrentCultureIgnoreCase)))
             {
                 BindField(generator, result, columnInfo, ReaderKeys.First(it => it.Equals(columnInfo.PropertyName, StringComparison.CurrentCultureIgnoreCase)));
             }
         }
     }
 }

通过这个方法可以看到,依据类型的判断,以及columnInfo的相关属性判断,来决定究竟是走BindClass()方法还是BindField()。经过调试,最终发现StudentAddress列进入了如下图所示的逻辑分支,并且调用了BindClass()方法。
image
继续看看BindClass()方法的代码:

private void BindClass(ILGenerator generator, LocalBuilder result, EntityColumnInfo columnInfo, string fieldName)
{

    if (columnInfo.SqlParameterDbType is Type)
    {
        BindCustomFunc(generator, result, columnInfo, fieldName);
        return;
    }

    if (columnInfo.IsJson)
    {
        MethodInfo jsonMethod = getJson.MakeGenericMethod(columnInfo.PropertyInfo.PropertyType);
        int i = DataRecord.GetOrdinal(fieldName);
        Label endIfLabel = generator.DefineLabel();
        generator.Emit(OpCodes.Ldarg_0);
        generator.Emit(OpCodes.Ldc_I4, i);
        generator.Emit(OpCodes.Callvirt, isDBNullMethod);
        generator.Emit(OpCodes.Brtrue, endIfLabel);
        generator.Emit(OpCodes.Ldloc, result);
        generator.Emit(OpCodes.Ldarg_0);
        generator.Emit(OpCodes.Ldc_I4, i);
        generator.Emit(OpCodes.Call, jsonMethod);
        generator.Emit(OpCodes.Callvirt, columnInfo.PropertyInfo.GetSetMethod(true));
        generator.MarkLabel(endIfLabel);
    }
    if (columnInfo.IsArray)
    {
        MethodInfo arrayMehtod = getArray.MakeGenericMethod(columnInfo.PropertyInfo.PropertyType);
        int i = DataRecord.GetOrdinal(fieldName);
        Label endIfLabel = generator.DefineLabel();
        generator.Emit(OpCodes.Ldarg_0);
        generator.Emit(OpCodes.Ldc_I4, i);
        generator.Emit(OpCodes.Callvirt, isDBNullMethod);
        generator.Emit(OpCodes.Brtrue, endIfLabel);
        generator.Emit(OpCodes.Ldloc, result);
        generator.Emit(OpCodes.Ldarg_0);
        generator.Emit(OpCodes.Ldc_I4, i);
        generator.Emit(OpCodes.Call, arrayMehtod);
        generator.Emit(OpCodes.Callvirt, columnInfo.PropertyInfo.GetSetMethod(true));
        generator.MarkLabel(endIfLabel);
    }
    else if (columnInfo.UnderType == typeof(XElement))
    {
        int i = DataRecord.GetOrdinal(fieldName);
        Label endIfLabel = generator.DefineLabel();
        generator.Emit(OpCodes.Ldarg_0);
        generator.Emit(OpCodes.Ldc_I4, i);
        generator.Emit(OpCodes.Callvirt, isDBNullMethod);
        generator.Emit(OpCodes.Brtrue, endIfLabel);
        generator.Emit(OpCodes.Ldloc, result);
        generator.Emit(OpCodes.Ldarg_0);
        generator.Emit(OpCodes.Ldc_I4, i);
        BindMethod(generator, columnInfo, i);
        generator.Emit(OpCodes.Callvirt, columnInfo.PropertyInfo.GetSetMethod(true));
        generator.MarkLabel(endIfLabel);
    }
}

这个方法就非常的直白明了了: 就是根据columnInfo的几个属性进行判断,来决定使用不同的数据绑定方式。 ,而且也不难看出,如果columnInfo.IsJson==true,那么应该就能实现我要效果。

2.看见曙光,直接釜底抽薪

总结一下上面的结论:

  1. sqlguar将dto当作了跟数据表对应的实体类型,并且将其属性包装成了EntityColumnInfo类型。
  2. 根据columnInfo的几个属性进行判断,来决定使用不同的数据绑定方式。

所以不难猜出,使用[SugarColumn(IsJson = true)]对dto的属性进行修饰,最终应该就是用在了BindClass()方法里的if (columnInfo.IsJson)判断上,根据这个决定数据绑定方式。

那么,做一个大胆的假设:如果不使用[SugarColumn(IsJson = true)],但是想办法在让它的IsJson属性变成true,问题是不是就完美解决了?

说干就干, 要给其赋值,首先要明白将dto的属性包装成EntityColumnInfo究竟发生在哪,它的IsJson属性又是如何确定值得。
于是又进入了漫长得源码调试阶段。省略其中的繁琐,我们直接看关键部分:

image

这个方法,核心就是将dto类,包裹成了EntityInfo类,并且在最下方的SetColumns(result)方法,对column进行了设置。继续去看这个方法的代码:

private void SetColumns(EntityInfo result)
{
    foreach (var property in result.Type.GetProperties())
    {
        EntityColumnInfo column = new EntityColumnInfo();

        //省略部分代码

        var sugarColumn = property.GetCustomAttributes(typeof(SugarColumn), true)
        .Where(it => it is SugarColumn)
        .Select(it => (SugarColumn)it)
        .FirstOrDefault();
         //省略部分代码
        if (sugarColumn?.IsOwnsOne==true)
        {
            SetValueObjectColumns(result, property, column);
        }
        if (sugarColumn.IsNullOrEmpty())
        {
            column.DbColumnName = property.Name;
        }
        else
        {
            if (sugarColumn.IsIgnore == false)
            {
                //这里就是对各种属性进行赋值,省略部分代码

                column.IsJson = sugarColumn.IsJson;

               //省略
            }
            else
            {
               //。。。
            }
        }
        result.Columns.Add(column);
    }
}

从上述代码可以看出,这里就是尝试找到resultSugarColumn特性,并且给IsJson等属性赋值。

上述代码在EntityMaintenance类里面,该文件在该文件在 SqlSugar项目的Abstract\EntityMaintenance文件夹内

3.束手无策

事情到这里,就已经结束了,因为我找不到任何办法,可以绕过SugarColumn特性,而将column的IsJson值设置为true。
而这里的代码,应该是属于整个框架里面的核心代码,其外层调用方法的99+的引用次数,更是让我不敢妄动。

我也考虑过修改数据绑定的那块逻辑,看看能否不通过判断columnInfo.IsJson也能实现。但是很可惜,也失败了,因为这里要考量的更多,不光是简单的查询,也要考虑多表join,甚至select时多个 对象属性查询,匿名对象(Select(x=>new{})),sqlFunc实现的子查询等N多种复杂情况。

反思

将查询的对象类包装成EntityInfo似乎是sqlsugar的框架核心实现,这也导致了如果在Select时想要实现 复杂对象 属性的数据绑定,似乎只能依靠SugarColumn

但是我真不敢苟同这样的设计,可是我水平有限,目前确实搞不定这个问题。

朋友们都说,dto上打一个特性标记就能解决了,没必要太上纲上线,框架层次引用的清洁性,真的有那么重要吗?

这个问题,我留给大家回答吧。

最后贴一段代码,调试源码的时候发现的,把我看乐了。

image

标签:OpCodes,dto,generator,public,聊一聊,机制,columnInfo,Emit,sqlsugar
From: https://www.cnblogs.com/diamondhusky/p/18650702

相关文章

  • 数字能力对制造企业可持续发展绩效的作用机制研究
    摘要随着信息技术的飞速发展,数字能力已成为制造企业提升竞争力、实现可持续发展的重要因素。本文旨在探讨数字能力如何影响制造企业的可持续发展绩效,并分析其作用机制。通过文献综述和案例分析,本文构建了数字能力与可持续发展绩效之间的理论框架,并提出了相应的管理建议。引......
  • Redis数据库——内存淘汰机制
    大家好,这里是GoodNote,关注公主号:Goodnote,专栏文章私信限时Free。本文详细介绍Redis的8种内存淘汰机制。文章目录过期键删除策略内存淘汰机制内存限制设置常见策略Redis3.0的淘汰机制——近似LRU算法Redis4.0的新增的淘汰机制——LFU算法历史文章MySQL数......
  • 大白话拆解—多线程(六)— 同步锁机制 和 synchronized
    前言:25年初,这个时候好多小伙伴都在备战期末我们新年第二天照样日更一篇,今天这篇一定会对小白非常有用的!!!因为我们会把案例到用代码实现的全过程思路呈现出来!!!我们一直都是以这样的形式,让新手小白轻松理解复杂晦涩的概念,把Java代码拆解的清清楚楚,每一步都知道他是怎么来的,为......
  • 公域转私域玩法:四大平台推流机制
    一、公域流量的策略1.四大平台的流量分配机制(1)核心原理掌握平台的流量分配机制,对于新手来说至关重要,却常被忽视。流量分配机制,就像高考规则一样,是你必须了解的得分点。想要进入理想的大学,你需要知道哪些是有效的得分点,对吧?同样,如果你想在一个平台上获得更多流量,你也需要......
  • Java反射机制与动态代理
    软件开发中,灵活性与扩展性是非常重要的需求,而Java的反射机制与动态代理正是实现这些特性的强大工具。反射机制让程序在运行时能够检查和操作类的信息,而动态代理则为方法调用提供了一种灵活的拦截机制。本文将深入探讨这两种机制的概念、原理、应用场景,并通过具体示例展示它......
  • 请讲讲Node的缓存机制
    在前端开发中,Node的缓存机制是一个重要的概念,它有助于提高网站或应用的性能,减少对服务器的频繁请求,从而加快网页加载速度和提升用户体验。以下是对Node缓存机制的详细讲解:一、浏览器缓存强缓存:当客户端(浏览器)请求资源时,会先访问缓存数据库看缓存是否存在。如果存在且未过期,......
  • 深入浅出 YOLO 物体检测算法:实战融合注意力机制
    摘要:本文呈上一份超详细的YOLO物体检测算法指南,先深挖其运行与数学原理,助您吃透底层逻辑。接着展开框架实战,从环境搭建、数据集处理,到经典模型的训练、测试,均有实操步骤与代码示例。重点来了,我们还会融入注意力机制,解读原理、设计融合方案并给出完整代码。一文在手,新手能......
  • 静态时序分析:线负载模型的选择机制
    相关阅读静态时序分析https://blog.csdn.net/weixin_45791458/category_12567571.html线负载模型及其选择    线负载模型仅在DesignCompiler线负载模式(非拓扑模式)下时使用,它估算了导线长度和扇出对网线的电阻、电容和面积的影响,DesignCompiler使用这些物理值来计......
  • 内网渗透:网络认证机制
    文章目录一、网络认证概述什么是网络认证?常见的网络认证方式二、NTLM协议挑战响应认证机制基本流程成功认证流程三、NTLM抓包分析实验环境实验步骤抓包分析要点四、Challenge和Response分析Challenge和Response的作用Response的生成过程五、NTLMv1和NTLMv......
  • 无人机双上行链路协调NOMA的自适应解码机制研究(Matlab代码实现)
      ......