Why does Dapper generate different SQL for Postgres with/without a mini-profiler connection
Asked Answered
W

1

7

Dapper (1.13 Noobget Package) creates different SQL statements depending on if it's used with a plain ADO.NET database connection or with a decorated mini-profiler database connection.

Example code (tested with Postgresql)

Usings:

using System.Linq;
using Dapper;
using Npgsql;
using NUnit.Framework;
using StackExchange.Profiling;
using StackExchange.Profiling.Data;

Test1 uses a plain ADO.NET connection and fails:

[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 uses a mini-profiler connection wrapped around the ADO.NET connection and succeeds:

  [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);
    }
  }
}

Looking at the generated SQL it becomes clear why Test1 fails:

  • SQL of Test1: SELECT 1 WHERE 42 IN ((array[41,42,43])::int4[])
  • SQL of Test2: SELECT 1 WHERE 42 IN (((41)),((42)),((43)))

Arrays don't support IN.

Why does dapper generate different SQL when it's used with/without a profiled connection?

Why does it generate an array[...] with a plain connection? Due to dapper's docs it should generate a tuple:

Dapper List Support

Waterless answered 21/3, 2014 at 14:16 Comment(5)
Where exactly are you getting the sql from in the two cases? I wonder if the difference is largely in how it is presented. Dapper does the same thing regardless of the provider hereEventide
this was reported a second time related to postgres; I have a suspicion that it is actually the postgres provider making a mess here, because that simply isn't what dapper emits; I will have to investigateEventide
I get the sql straight from the server logs (run it in a console with sql logging enabled). What's causing problems is that the sql is different depending on if a profiled connection is/is not used..Waterless
PS: I noticed that mini-profiler actually includes a copy of dapper (it's public in StackExchange.Profiling.Helpers.Dapper). Can this be safely used in projects that use mini profiler anyway or is there a chance it will become internal later? I think that this version of dapper includes some (useful) changes/bugfixes that are not included in the official dapper.Waterless
@MarcGravell i investigated and found the cause of the problem, but no nice solution. please see my answer below.Waterless
W
9

There is a class "FeatureSupport" in Dapper that contains settings for special treatment of arrays. Postgresql connections are marked to support arrays, while other connection types (that includes MiniProfiler ProfiledDbConnections) are marked to not support arrays.

If the connection does not support arrays, Dapper manually creates one parameter for each item in the array (like explained in the docs) - it becomes a tuple in SQL, like: SELECT 1 WHERE 42 IN (41,42,43)

If the connection supports arrays (like Postgres' NpgsqlConnection), array parameters are passed straight to the connection, resulting in something ugly like: SELECT 1 WHERE 42 IN ('{41,42,43}'::int4[]) - which actually fails because IN doesn't support arrays.

Relevant code is in the SqlMapper.PackListParameters method.

Therefore switching between ProfiledDbConnections and NpgsqlConnections causes problems because the generated SQL will be different.

To get rid of the array syntax in Postgres connections, the following code can be used (though it only works at a global level...):

using Dapper;
using Npgsql;

using (var cnn = new NpgsqlConnection())
  FeatureSupport.Get(cnn).Arrays = false;

There does not seem to be a way to enable/disable the array syntax on a per-query or per-parameter level.

PS.: I found an issue for this problem at https://code.google.com/p/dapper-dot-net/issues/detail?id=107&q=postgres

Waterless answered 27/3, 2014 at 10:54 Comment(4)
today I learned; thanks; wow I need to clear out the pull requests and issues!Eventide
Thanks! This solved my problem with ERROR: operator does not exist: integer = integer[]. Great tip, didn't know about FeatureSupport.Snazzy
@MarcGravell 2019 saying hello... :) This still seems to be an problem, and with the current version of Dapper(2.0.30), the FeatureSupport class is marked as internal - so I can't use this anymore. Any other way of doing this?Eponymy
@Eponymy right now; no; this falls into the bucket of provider knowledge things that we want to improve in the next major (which will require an API overhaul), but: it isn't there todayEventide

© 2022 - 2024 — McMap. All rights reserved.