0

I have a custom class LoggingDbConnection that implements IDbConnection. This LoggingDbConnection class has its own public instance method, for example, QueryAsync<T>(...), which includes custom logging logic.

In my service, I have a field:

private readonly IDbConnection _dbConnection;

This field is injected (e.g., via Autofac) with an instance of LoggingDbConnection.

When I call:

await _dbConnection.QueryAsync<T>(...);

I expect my LoggingDbConnection.QueryAsync<T>() instance method (with the logging) to be executed because the actual runtime object is a LoggingDbConnection.

However, what actually happens is that Dapper's QueryAsync<T>() extension method (for IDbConnection) gets called instead, and my custom logging is bypassed.

Why does the C# compiler choose Dapper's extension method for IDbConnection instead of the instance method on my concrete LoggingDbConnection class, even though the object instance at runtime is a LoggingDbConnection?

8
  • 1
    "When I call" - you are calling extension method yourself. To call instance method you need the instance of that type first, not some interface it implements. Commented May 12 at 16:01
  • As a side note: maybe MiniProfiler does what you want here. It was written in tandem with Dapper and works very well with it. Commented May 12 at 21:15
  • 1
    TLDR when you have a variable of type IDbConnection, you can only call methods of this interface or extension methods. The compiler doesn't know that an IDbConnection is actually a LoggingDbConnection with different functions. Commented May 13 at 1:48
  • @MarcGravell installing MiniProfiler when I just want to simple logs seems like an overkill, no? Commented May 13 at 7:33
  • @Shad miniprofiler is a simple log, hence the "mini" Commented May 13 at 10:23

2 Answers 2

7

So you have:

public class LoggingDbConnection : IDbConnection
{
    public Task<T> QueryAsync<T>(...) { ... }
}

// In Dapper
public static class SqlMapper
{
    public static Task<T> QueryAsync<T>(this IDbConnection connection, ...) { ... }
}

And you do:

IDbConnection connection = new LoggingDbConnection(...);
connection.QueryAsync<T>(...);

That will not call LoggingDbConnection.QueryAsync<T>, no.

When you call connection.QueryAsync<T>(...) the compiler goes through a long process to resolve that to a method to call, but for our purposes here the process looks like:

  1. I've got a variable connection of type IDbConnection. Is there a method QueryAsync<T> on IDbConnection? No.
  2. Are there any extension methods on IDbConnection called QueryAsync<T>? Yes.

In other words, the compiler works out at compile-time whether you're calling an instance method or an extension method using the information available to it at compile-time, and that information is that you have a receiver of type IDbConnection.

Put another way, yes instance methods win out over extension methods if both are applicable, but if the QueryAsync<T> extension method didn't exist, then you wouldn't be able to call connection.QueryAsync<T> anyway. So it was never a candidate here. That's just not how interfaces work.

In other other words, extension methods are not virtual, and cannot participate in method overriding.

Sign up to request clarification or add additional context in comments.

Comments

2

@canton7's response answers the original question.

Dapper doesn't have interceptors, so to solve your real problem (add logging) you have two options:

1. Make own extension methods (bad option):

Make methods like .LoggingQueryAsync(...).
It looks simple at first, but have way too many downsides...

2. Implement IDbConnection method that Dapper calls (good option):

public class LoggingDbConnection : IDbConnection
{
    ...

    public IDbCommand CreateCommand()
    {
        return new LoggingDbCommand(this);
    }

    ...
}

Dapper have to call IDbConnection.CreateCommand() to do anything.

In LoggingDbCommand implement IDbCommand.ExecuteNonQuery(), IDbCommand.ExecuteReader(), IDbCommand.ExecuteReader(CommandBehavior) and IDbCommand.ExecuteScalar() to add logging.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.