Почему 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 он должен генерировать кортеж:

Поддержка Dapper List

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

Другие вопросы по тегам