【问题标题】:Dapper Guid array IN clause MySqlDapper Guid 数组 IN 子句 MySql
【发布时间】:2020-05-26 14:19:27
【问题描述】:

我正在尝试为 MySql 查询的 Guids 数组构建一个 IN 子句。 Guid 列在 DB 中表示为 binary(16)。根据这里的文档和答案,我应该能够做类似

var arrayOfGuidsFromDb = ...;

await dbconn.ExecuteAsync<T>("UPDATE ...
SET ...
WHERE SomeGuidField IN @Ids",
new { Ids = arrayOfGuidsFromDb }

我也在用这个 Guid 转换器

class MySqlGuidTypeHandler : SqlMapper.TypeHandler<Guid>
{
    public override void SetValue(IDbDataParameter parameter, Guid guid) => parameter.Value = guid.ToByteArray();

    public override Guid Parse(object value) => new Guid((byte[])value);
}

不过,MySql 的问题是它尝试(默认情况下)通过重新排列 GUID 值的时间戳部分来优化 DB 中的 GUID 布局。我决定不改变这种行为,它适用于读/写和WHERE SomeGuidField = @SomeGuid 等条件,但对于问题中的IN 语句,它匹配 0 个结果。我可以改写这个 hack

guids.Select(guid => $"uuid_to_bin('{RotateToMatchInDbGuid(guid).ToString()}')")

我将每个 guid 转换为字符串,然后将 string.Join(','... 转换为 IN 子句,即辅助方法:

static Guid RotateToMatchInDbGuid(Guid source)
    {
        Span<byte> result = stackalloc byte[16];
        source.TryWriteBytes(result);
        Swap(result, 0, 3);
        Swap(result, 1, 2);
        Swap(result, 4, 5);
        Swap(result, 6, 7);
        return new Guid(result);
    }

这显然看起来和感觉都不对。我是做错了什么,还是缺少一些设置,我应该启用以使 Dapper 行为在 =IN GUID 条件下保持一致?

完整代码:

Guid[] guids = await dbConn.QueryAsync("SELECT Id FROM SomeTable"); //returns 1 row

// query using IN clause and array param:
var usingIn = await dbConn.QueryAsync("SELECT * From SomeTable WHERE Id IN @Ids", new { Ids = guids}); // returns 0 rows, should be 1

// now query using the `=` operator and same param but as a single value
var usingEquals = await dbConn.QueryAsync("SELECT * From SomeTable WHERE Id = @Id", new { Id = guids.First() }); // returns 1 row as expected

// query using array as CSV and no params
var usingCSV = await dbConn.QueryAsync($"SELECT * From SomeTable WHERE Id IN ({BuildCsv(guids)})"); // also returns 1 row as expected

【问题讨论】:

  • 我建议切换到nuget.org/packages/MySqlConnector 并使用其GuidFormat=TimeSwapBinary16 连接字符串选项(mysqlconnector.net/connection-options)。这样,您不需要自定义MySqlGuidTypeHandler,但只要您使用Guid 作为MySqlCommand 参数值(直接或由小巧玲珑)。
  • 谢谢,但我已经有很多使用 Dapper 编写的代码,这很好,我唯一遇到的问题是当我开始添加 IN 子句时的这个问题
  • Dapper 与 MySqlConnector 配合得很好;无需更改任何现有代码。只需换掉 MySQL ADO.NET 库,让它处理网络上Guid 对象的序列化。
  • 注意:guid 作为字节在数据库中非常尴尬,因为有多个互斥布局;没有人同意它应该是什么
  • @MarcGravell 我明白了,我担心的是,如果我在 IN(未旋转)或 =(旋转)子句中使用相同的 guid 参数,Dapper 会以不同方式处理它我没记错

标签: c# mysql dapper


【解决方案1】:

IN 子句的参数化需要每个 IN 值一个参数;您不会将 CSV 字符串传递给单个参数 IN 并希望它能够解决

//no
Query("SELECT * FROM person WHERE name IN @names", new { names = "John,Jane,Mary" })

//yes
Query("SELECT * FROM person WHERE name IN (@name1,@name2,@name3)", new { name1 = "John", name2="Jane", name3="Mary" })

第一种形式只会找到名字字面意思是“John,Jane,Mary”的人

您也许可以使用 FIND IN SET:

Query("SELECT * FROM person WHERE FIND_IN_SET(name, @names) > 0", new { names = "John,Jane,Mary" })

但是有点蹩脚..

【讨论】:

  • 我的问题中的 arrayOfGuidsFromDb 变量实际上是使用 Dapper 从 DB 获取的 Guids Guid[] 数组,它应该(?)工作 stackoverflow.com/questions/17150542/… ,添加了 CSV hack,因为它没有
  • 我认为您可能会将 SQL IN 与 dapper 迭代数组并重复运行相同的查询(每个数组值一次)混淆?也就是说,如果您使用数组中的 5 个元素进行查询 ("SELECT * FROM t WHERE x IN (@p)", arrayOfX),Dapper 将运行该查询 5 次,每次执行一个元素(并且 IN 使用 1 的列表,即这是有效的 SQL:@987654329 @ 与 SELECT * FROM Person WHERE Name = 'Jon' 相同)- Dapper 不会做一些时髦的事情来将查询放入 x IN (@p1,@p2,@p3,@p4,@p5) 并将一个元素放入每个 @pX
  • 哦,很不幸,我确信它会执行 IN (@p1,@p2,@p3,@p4,@p5) 所以这意味着如果我想针对 @987654334 执行单个查询而不是 N ,我的 CSV hack 实际上是一个最佳解决方案@元素?
  • 实际上,我放弃了最后一条评论:stackoverflow.com/questions/8388093/… 似乎表明如果您使用 IN 不带括号,那么 Dapper 转换 Query("... id IN @someList", myArrayOf3Ids)等价于Query("... id IN (@p1,@p2,@p3)", new { p1 = myArrayOf3Ids[0], p2 = myArrayOf3Ids[1], p3 = myArrayOf3Ids[2] } ),生成的@pX的个数等于数组中的元素个数
  • 请注意,您提出的任何 CSV hack 都不会将 values 连接到 SQL 字符串中。总是连接 parameters 然后给参数赋值。我目前无法解释为什么stackoverflow.com/questions/8388093/… 中给出的建议表明您的第二个查询应该有效,但没有。它还提供了使用表值参数的建议 - 也许试一试,看看它是否有效?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-07
  • 2015-04-02
  • 1970-01-01
相关资源
最近更新 更多