记一次ORM模糊查询偶发报错
0x00 前言
前几天生产服务使用的接口报错,本地测试、测试服没问题,预生产和生产报错,重新部署又好了领导那边又说是版本dll没更新上(发布是127,实际已经用上129版本),问题就搁置了(解决了就行)。忙了好几天,今天抽空查一下bug。
0x01 偶发报错的几种可能
1、线程安全报错
2、缓存丢失报错
3、上下文丢失报错
4、环境不同报错(本地调试是Debug,发布是Release)
5、其他报错(狗头保命)
0x02 demo场景复现
/// <summary>
/// 数组like测试
/// </summary>
private static async Task ArrayLikeTest()
{
SqlSugarScope db = DbHelper.GetNewDbScope();
var arrNums = Enumerable.Range(1, 1).Select(x => $"{x}").ToArray();
var pageIndex = 1;
var pageSize = 50;
RefAsync<int> rowCount = 1;
for (int i = 0; i < 100000; i++)
{
var sugarWhereExp = Expressionable.Create<Student03>();
var isRandTrue = DateTime.Now.Ticks % 2 == 1;
if (isRandTrue)
{
sugarWhereExp.And(p => arrNums.Any(x => p.Name.Contains(x)) || arrNums.Any(x => p.Name2.Contains(x)));
}
else
{
sugarWhereExp.And(p => arrNums.Contains(p.Name) || arrNums.Contains(p.Name2));
}
var tempWhereExp = sugarWhereExp.ToExpression();
var r1 = await db.Queryable<Student03>("p").Where(tempWhereExp).ToPageListAsync(pageIndex,pageSize,rowCount);
}
}
因为是偶发性,且部署的第一时间是没有问题,过了几天才有问题,所以demo使用%2取模做随机(生产环境是有模糊和精确两种查询);
可以看到两种sql查询都没问题,在数据库执行也没问题;
0x03 追捕不能重现的BUG
1、复现sql报错
通过预生产的环境报错,能清晰的知道sql是因为int类型转换字符串报错了,而且一线人员也将参数发给我看了,模糊查询的字符串是纯数字(不然你以为我demo直接写数字干嘛呢)
SELECT 1 FROM [Student03] [p] WHERE (([Name] IN ('1')) OR ([Name2] IN ('1')) )
SELECT 1 FROM [Student03] [p] WHERE (( ([p].[Name] like '%'+ '1' +'%') ) OR ( ([p].[Name2] like '%'+ '1' +'%') ) )
--报错sql
SELECT 1 FROM [Student03] [p] WHERE (([Name] IN (1)) OR ([Name2] IN (1)) )
SELECT 1 FROM [Student03] [p] WHERE (( ([p].[Name] like '%'+ 1 +'%') ) OR ( ([p].[Name2] like '%'+ 1 +'%') ) )
因为报错信息里面存在% 所以我们直接锁定模糊查询sql报错;
2、万能的F11步入调试法
因为是表达式目录树生成sql,我们在where时进行单步步入;
因为表达式一般都是递归解析,所以我们就不一步步看了,我们能知道的是like拼接报错,那么就一定是在数组解析时出现问题,我们直接看数组解析部分;
D:\Project\SqlSugar\Src\Asp.NetCore2\SqlSugar\ExpressionsToSql\ResolveItems\MethodCallExpressionResolve.cs
D:\Project\SqlSugar\Src\Asp.NetCore2\SqlSugar\ExpressionsToSql\ResolveItems\MethodCallExpressionResolve_Helper.cs
在众多的case中步入ListAny分支;
实体列的属性信息,这里因为是string,也默认取了其属性列做缓存;
这里有3个类型判断,guid、整型、日期;因为我们的string类型第一个属性是chars,字符串类型 所以进入了else的ToSqlValue方法,我们看看tosql做了什么;
这里也没问题,最终通过正则替换掉参数关键字(原先还以为是正则替换错了值导致的),生成模糊查询的sql;
3、思考问题
因为是一步步进方法的,所以最终的sql没问题 也没有找到可疑点;那么就不是bug?
我们仔细看看,因为缓存了string的属性列,那么有没有可能缓存被覆盖?答案肯定是否定的,orm自身缓存是通过类型的hashcode拼接的key;
那么有没有可能是列的问题?我们上面能够知道string缓存了两个列chars和length;看看列是怎么来的,通过反射获取属性;
(IndexerName会指定索引器名称,否则默认是Item)
我们通过il源码能够知道chars是字符串类型,length是整型;还记得我们看数组拼接的时候有个if判断吗?没错,我们都知道int数据类型查询是可以用单引号拼接,也可以不用;如:
SELECT 1 ?FROM [Student03] [p] ?WHERE p.Id='1' OR p.Id=1,结果是等效的;
那么如果此处是int类型会如何拼接?直接tostring!!!
也就是说 如果是int类型就会报错!
但是这里有两个列 第一列是chars【字符串类型】已经把结果的标识字符串给替换了,那么他可能会是因为这个原因报错吗?
难道就无解了吗?
0x04 峰回路转,杀回string,不要默认不可能!
两个列?string int?如果他的顺序是错误的,那么不就报错了?
typeof(string).GetProperties()反射获取属性的顺序可能会变?
我们试试!
var taskList=Enumerable.Range(1,1000).Select(e => Task.Run(TaskRun)).ToArray();
Task.WaitAll(taskList);
void TaskRun(){
foreach (var element in Enumerable.Range(1, 1_000_000_000).AsParallel())
{
var s = typeof(string).GetProperties();
var ar = s.Select(x => x.Name).ToArray();
var ars = string.Join(",", ar);
if (ars.StartsWith("Length"))
{
("首个属性是length" + ars).Dump();
break;
}
}
}
结果是。。。。。。。。。。。。。。
没有出现奇迹。
真的无解了!!
问问我广大的群友
!!!!
上F12
因为项目是6.0版本,所以可能是中奖了
0x05 后续BUG复现及问题修复
1、因为BUG是偶发且不易复现,先写demo,不行再找个官方文档看看,确定一下
2、如果可能是此bug引起的问题,优先联系orm,提issue
3、顺序获取属性:
//1、根据token排序
PropertyInfo[] properties = typeof(string).GetProperties().OrderBy(x=>x.MetadataToken)
//2、标记属性
public class Student03
{
[你的自定义属性(orderIndex=-1)]
public int Id { get; set; }
[你的自定义属性(orderIndex=2)]
public string Name { get; set; }
[你的自定义属性(orderIndex=3)]
public string Name2 { get; set; }
}
4、升级框架至7.0及以上
2024/01/09 9:48 复现截图
参考链接
允许 GetProperties 按声明顺序对结果进行排序 ·期刊 #46272 ·dotnet/运行时 (github.com)
typeof(可查询)。GetMethods() 以不同的顺序返回 .NET 5 和 .NET 6 的结果 ·期刊 #71422 ·dotnet/运行时 (github.com)
bug 给我死!(不确定的先F12,优先相信自己的怀疑)
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!