Почему Dapper генерирует разные SQL с / без подключения мини-профилировщика
Dapper (1.13 Noobget Package) создает различные операторы SQL в зависимости от того, используется ли он с простым соединением с базой данных ADO.NET или с украшенным соединением с базой данных мини-профилировщика.
Пример кода (протестирован с Postgresql)
Usings:
using System.Linq;
using Dapper;
using Npgsql;
using NUnit.Framework;
using StackExchange.Profiling;
using StackExchange.Profiling.Data;
Test1 использует простое соединение ADO.NET и завершается ошибкой:
[TestFixture]
public class DapperTests {
private const string cnnstr = "HOST=...;DATABASE=...;USER ID=...;PASSWORD=...;";
[Test]
public void Test1() {
using (var cnn = new NpgsqlConnection(cnnstr)) {
cnn.Open();
// The following line fails:
cnn.Query<int>("SELECT 1 WHERE 42 IN @Items", new {Items = new[] {41, 42, 43}}).Single();
// Npgsql.NpgsqlException : ERROR: 42883: operator does not exist: integer = integer[]
}
}
Test2 использует соединение с мини-профилировщиком, обернутое вокруг соединения ADO.NET, и успешно:
[Test]
public void Test2() {
using (var cnn = new NpgsqlConnection(cnnstr))
using (var profiled = new ProfiledDbConnection(cnn, MiniProfiler.Start())) {
profiled.Open();
int result = profiled.Query<int>("SELECT 1 WHERE 42 IN @Items", new {Items = new[] {41, 42, 43}}).Single();
Assert.AreEqual(1, result);
}
}
}
Глядя на сгенерированный SQL, становится понятно, почему Test1 не работает:
- SQL Test1: ВЫБЕРИТЕ 1 ГДЕ 42 В ((массив [41,42,43]):: int4 [])
- SQL теста 2: ВЫБЕРИТЕ 1 ГДЕ 42 В (((41)), ((42)), ((43)))
Массивы не поддерживают IN.
Почему dapper генерирует другой SQL, когда он используется с / без профилированного соединения?
Почему он генерирует массив [...] с простым соединением? Из-за документов dapper он должен генерировать кортеж:
1 ответ
В Dapper есть класс FeatureSupport, который содержит настройки для специальной обработки массивов. Соединения Postgresql помечены для поддержки массивов, в то время как другие типы соединений (включая MiniProfiler ProfiledDbConnections) помечены как не поддерживающие массивы.
Если соединение не поддерживает массивы, Dapper вручную создает один параметр для каждого элемента в массиве (как объяснено в документации) - он становится кортежем в SQL, например: SELECT 1 WHERE 42 IN (41,42,43)
Если соединение поддерживает массивы (например, Postgres' NpgsqlConnection), параметры массива передаются прямо в соединение, что приводит к чему-то безобразному: SELECT 1 WHERE 42 IN (' {41,42,43} ':: int4 []) - что фактически терпит неудачу, потому что IN не поддерживает массивы.
Соответствующий код находится в методе SqlMapper.PackListParameters.
Поэтому переключение между ProfiledDbConnections и NpgsqlConnections вызывает проблемы, поскольку сгенерированный SQL будет другим.
Чтобы избавиться от синтаксиса массива в соединениях Postgres, можно использовать следующий код (хотя он работает только на глобальном уровне...):
using Dapper;
using Npgsql;
using (var cnn = new NpgsqlConnection())
FeatureSupport.Get(cnn).Arrays = false;
Похоже, нет способа включить / отключить синтаксис массива на уровне запроса или параметра.
PS: я нашел проблему для этой проблемы на https://code.google.com/p/dapper-dot-net/issues/detail?id=107&q=postgres