Dapper icon indicating copy to clipboard operation
Dapper copied to clipboard

FeatureSupport broken when using a wrapped IDbConnection

Open jhaygood86 opened this issue 6 years ago • 6 comments

Our code uses a "wrapped connection" for APM tracing (inspired by mini profiler), and so this code in FeatureSupport.cs doesn't work:

public static FeatureSupport Get(IDbConnection connection) { string name = connection?.GetType().Name; if (string.Equals(name, "npgsqlconnection", StringComparison.OrdinalIgnoreCase)) return Postgres; return Default; }

Essentially, connection is a "TracingDbConnection" that wraps and proxies an underlying "NpgsqlConnection" while sending traces to DataDog (that part isn't of concern here). There doesn't appear to be a way for code outside of Dapper to "correct" this.

It looks like using MiniProfiler has the same issue: https://github.com/MiniProfiler/dotnet/issues/319 (which makes sense, since our approach was inspired by MiniProfiler's)

jhaygood86 avatar Aug 28 '19 15:08 jhaygood86

Yes, totally agreed and acknowledged; and yes: I feel this pain too (Stack Overflow uses something also related to, but not identical to, mini-profiler as a decorator layer)

ideally IMO what we need here is to move away from internal detection based on the type, and towards some kind of external configuration where the caller can influence this; that's part of the "major API overhaul" changes planned.

Note: I would have said "v2", but we're in the process of deploying v2 currently but with fairly invisible API surface changes, so in reality I think this means "v3" (this change from v2 to v3 has zero effect on timing; the current v2 is purely a semver thing)

mgravell avatar Aug 28 '19 16:08 mgravell

We have exactly the same issue, wrapping the NpgSqlConnection to add telemetry which prevents Dapper from detecting the connection as being 'array supporting'.

Whilst I 100% do not condone doing this, if you are looking to work around this issue and have Dapper treat all connections as supporting arrays GLOBALLY - then we do some awful reflection based operations on application start-up as follows:

public static void DisableArrayExpansion()
{
	var featureSupportType = Type.GetType("Dapper.FeatureSupport, Dapper", throwOnError: true)!;

	var d = featureSupportType.GetField("Default", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null);
	var arraysField = featureSupportType.GetField("<Arrays>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic);

	bool currentArraySupport = (bool)arraysField!.GetValue(d)!;
	if (currentArraySupport)
		throw new InvalidOperationException("Dapper was already configured for use with databases that have direct array support");

	// overwrite the 'default' to be 'true' - database is aware of array types and we do not want dapper expanding them
	arraysField.SetValue(d, true);
}

YMMV - works against Dapper 2.0.78.

kieranbenton avatar Jan 04 '21 12:01 kieranbenton

I have been investigating options for this for V2; it is a known problem area, and isn't being ignored

On Mon, 4 Jan 2021, 12:03 Kieran Benton, [email protected] wrote:

We have exactly the same issue, wrapping the NpgSqlConnection to add telemetry which prevents Dapper from detecting the connection as being 'array supporting'.

Whilst I 100% do not condone doing this, if you are looking to work around this issue and have Dapper treat all connections as supporting arrays GLOBALLY - then we do some awful reflection based operations on application start-up as follows:

public static void DisableArrayExpansion() { var featureSupportType = Type.GetType("Dapper.FeatureSupport, Dapper", throwOnError: true)!;

var d = featureSupportType.GetField("Default", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null); var arraysField = featureSupportType.GetField("<Arrays>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic);

bool currentArraySupport = (bool)arraysField!.GetValue(d)!; if (currentArraySupport) throw new InvalidOperationException("Dapper was already configured for use with databases that have direct array support");

// overwrite the 'default' to be 'true' - database is aware of array types and we do not want dapper expanding them arraysField.SetValue(d, true); }

YMMV - works against Dapper 2.0.78.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/StackExchange/Dapper/issues/1318#issuecomment-753937044, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAEHMGWS2WVN3QMYVZ54ELSYGU7VANCNFSM4IRLDZ3Q .

mgravell avatar Jan 04 '21 18:01 mgravell

We're looking at having an ambient and passable IDapperConfig or similar here to handle things...and not rely on detection since that model is fundamentally broken in these cases. Early on still, but it's being thought of!

NickCraver avatar May 09 '21 01:05 NickCraver

Hello. Are there any updates on this? Can't it be just some

enum ArrayHandling { Detect, Parameter, Expand }
SqlMapper.Settings {
  ArrayHandling ArrayHandling {get;set;}
}

or should it be more flexible like

interface IFeatureSupport { bool ExpandArray(IDbConnection); }
SqlMapper.Settings {
  IFeatureSupport FeatureSupport {get;set;}
}

Not sure I understand clearly the idea of IDapperConfig, but is this possible to introduce some simple solution using Settings for now?

Voronkov-A avatar Mar 05 '23 17:03 Voronkov-A

I believe the simplest workaround here is to name your wrapper class identically to the expected "NpgsqlConnection" that Dapper is looking for in the above snippet. If you are using something from a third party (EG MiniProfiler) then you would have to further wrap that with a custom class with the expected name. This is an ugly hack, but it seems to work at least in my use case. EG:

    public IDbConnection GetDbConnection()
    {
        // wrap the underlying connection in a custom class with the same name
        return new NpgsqlConnection(new Npgsql.NpgsqlConnection(databaseSettings.GetConnectionString());
    }

ian-a-anderson avatar Oct 30 '24 11:10 ian-a-anderson