使用Dapper TypeHandler<string[]>时IN条件SQL语法错误求助
问题背景
使用Dapper连接MySQL,自定义StringArrayHandler用于将数据库字符串解析为string[](因TypeHandler要求必须同时重写Parse和SetValue方法)。但执行包含IN(...)条件的查询时触发SQL语法错误,仅将参数数组转为List<string>可正常运行,但该方式易因遗忘转换引发问题。
示例代码
业务查询代码
using (var db = ADA_BaseDBManager.CreateADA_BaseDBConnectionDapper()) { SqlMapper.AddTypeHandler(new StringArrayHandler()); var superOdps = new string[]{"0200258416_70","0200258438_20","0200258649_10","0100942638_30","0200258768_10"}; var sql = "select * from odps_v2 ov where ov.ID_SuperODP in @superOdps"; var data = db.Query<DTOODP>(sql, new { superOdps}); return data; }
自定义TypeHandler代码
public class StringArrayHandler : SqlMapper.TypeHandler<string[]> { public override string[] Parse(object value) { if (value == null || value == DBNull.Value) { return null; } return Utils.SerializedToArray(value.ToString()); } public override void SetValue(IDbDataParameter parameter, string[] value) { parameter.Value = $"('{Utils.ArrayToSerialized(value, "','")}')"; } }
异常信息
{"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''(\'0200258416_70\',\'0200258438_20\',\'0200258649_10\',\'0100942638_30\',\'0200' at line 1"}
问题原因
全局注册的StringArrayHandler会将传入的string[]参数转换为带括号的字符串,Dapper会把这个字符串作为单个参数包裹在引号中,最终生成的SQL为:
select * from odps_v2 ov where ov.ID_SuperODP in '(\'0200258416_70\',\'0200258438_20\',...)'
这不符合SQL语法规范,IN后应紧跟括号包裹的多个值,而非带引号的单个字符串。
而转为List<string>能正常运行,是因为Dapper对IEnumerable类型参数有原生处理逻辑,会自动拆分为多个独立参数,不会被自定义TypeHandler干扰。
解决方案
方案1:仅在DTO属性上指定TypeHandler(推荐)
如果StringArrayHandler仅用于读取数据库字段并转换为DTO的string[]属性,无需全局注册,只需在DTO的对应属性上标记即可:
public class DTOODP { // 其他属性... [TypeHandler(typeof(StringArrayHandler))] public string[] TargetArrayProperty { get; set; } }
修改业务代码,移除全局注册的TypeHandler,直接传入string[]参数:
using (var db = ADA_BaseDBManager.CreateADA_BaseDBConnectionDapper()) { var superOdps = new string[]{"0200258416_70","0200258438_20","0200258649_10","0100942638_30","0200258768_10"}; var sql = "select * from odps_v2 ov where ov.ID_SuperODP in @superOdps"; var data = db.Query<DTOODP>(sql, new { superOdps}); return data; }
此时Dapper会自动将string[]拆分为多个参数,生成正确的SQL:
select * from odps_v2 ov where ov.ID_SuperODP in (@superOdps0, @superOdps1, @superOdps2, @superOdps3, @superOdps4)
既避免语法错误,又能防止SQL注入。
方案2:避免用TypeHandler处理IN查询参数
自定义TypeHandler的设计目标是处理单个参数与数据库字段的双向转换,不适合处理IN查询所需的多参数场景。因此,不要用TypeHandler来处理作为IN条件的数组参数,直接依赖Dapper的原生IEnumerable参数支持即可。
内容的提问来源于stack exchange,提问作者Brakes

