Предложение IN массива Dapper Guid MySql - PullRequest
0 голосов
/ 26 мая 2020

Я пытаюсь создать предложение IN для массива Guid s для запроса MySql. Столбцы Guid представлены в БД как 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 в базе данных, переставляя часть отметки времени значения GUID. Я решил не изменять это поведение, оно отлично работает для операций чтения / записи и таких условий, как WHERE SomeGuidField = @SomeGuid, но для оператора IN в вопросе оно соответствует 0 результатам. Вместо этого мне удалось написать этот хак

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

, в котором я конвертирую каждый идентификатор в строку, а затем 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

1 Ответ

0 голосов
/ 26 мая 2020

Параметризация предложения IN требует одного параметра на каждое значение IN'd; вы не передаете строку 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" })

Первая форма найдет только кого-то, чье имя буквально «Джон, Джейн, Мэри»

Возможно, вы сможете использовать FIND IN SET:

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

Но это немного неубедительно ..

...