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

Chris F Carroll
  • 11,146
  • 3
  • 53
  • 61
stmax
  • 6,506
  • 4
  • 28
  • 45
  • 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 here – Marc Gravell Mar 21 '14 at 18:04
  • 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 investigate – Marc Gravell Mar 22 '14 at 15:17
  • 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.. – stmax Mar 22 '14 at 19:18
  • 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. – stmax Mar 22 '14 at 19:27
  • @MarcGravell i investigated and found the cause of the problem, but no nice solution. please see my answer below. – stmax Mar 27 '14 at 10:58

1 Answers1

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

stmax
  • 6,506
  • 4
  • 28
  • 45
  • 3
    today I learned; thanks; wow I need to clear out the pull requests and issues! – Marc Gravell Mar 27 '14 at 11:01
  • Thanks! This solved my problem with ERROR: operator does not exist: integer = integer[]. Great tip, didn't know about FeatureSupport. – Diego Jul 04 '14 at 07:23
  • @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? – Krummelz Nov 04 '19 at 10:05
  • @Krummelz 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 today – Marc Gravell Nov 04 '19 at 12:21