Yesterday I blogged about the usage of DiagnosticSource to capture database specific events. The code we ended up with looked like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public sealed class SqlClientObserver : IObserver<DiagnosticListener>, IObserver<KeyValuePair<string, object>> | |
{ | |
private readonly IList<IDisposable> _listeners = new List<IDisposable>(); | |
void IObserver<DiagnosticListener>.OnNext(DiagnosticListener diagnosticListener) | |
{ | |
lock (_listeners) | |
{ | |
if (diagnosticListener.Name == "SqlClientDiagnosticListener") | |
{ | |
var subscription = diagnosticListener.Subscribe(this); | |
_listeners.Add(subscription); | |
} | |
} | |
} | |
void IObserver<DiagnosticListener>.OnError(Exception error) | |
{ } | |
void IObserver<DiagnosticListener>.OnCompleted() | |
{ | |
lock(_listeners) | |
{ | |
_listeners.ForEach(x => x.Dispose()); | |
_listeners.Clear(); | |
} | |
} | |
void IObserver<KeyValuePair<string, object>>.OnNext(KeyValuePair<string, object> pair) | |
{ | |
Console.WriteLine(pair.Key); | |
Console.WriteLine(pair.Value) | |
} | |
void IObserver<KeyValuePair<string, object>>.OnError(Exception error) | |
{ } | |
void IObserver<KeyValuePair<string, object>>.OnCompleted() | |
{ } | |
} |
What is rather unfortunate is that you are working with generic events and that you need to use magic strings and reflection to capture the object data:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void IObserver<KeyValuePair<string, object>>.OnNext(KeyValuePair<string, object> pair) | |
{ | |
switch (pair.Key) | |
{ | |
case"System.Data.SqlClient.WriteCommandAfter": | |
{ | |
var command = GetProperty<SqlCommand>(pair.Value, "Command"); | |
Console.WriteLine($"CommandText: {command.CommandText}"); | |
Console.WriteLine($"Elapsed: {stopwatch.Elapsed}"); | |
Console.WriteLine(); | |
break; | |
} | |
} | |
} | |
private static T GetProperty<T>(objectvalue, string name) | |
{ | |
return (T) value.GetType() | |
.GetProperty(name) | |
.GetValue(value); | |
} |
A solution exists through the usage of the Microsoft.Extensions.DiagnosticAdapter package. This Nuget package will use code generation to avoid the cost of reflection.
- Add a reference to the Microsoft.Extensions.DiagnosticAdapter package:
dotnet add package Microsoft.Extensions.DiagnosticAdapter
- Update the implementation to use the SubscribeWithAdapter extension method:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void IObserver<DiagnosticListener>.OnNext(DiagnosticListener diagnosticListener) | |
{ | |
lock (_listeners) | |
{ | |
if (diagnosticListener.Name == "SqlClientDiagnosticListener") | |
{ | |
var subscription = diagnosticListener.SubscribeWithAdapter(this); | |
_listeners.Add(subscription); | |
} | |
} | |
} |
- Now we no longer need to implement the
IObserver<KeyValuePair<string, object>>
interface. Instead, for each event that we want to handle, we need to declare a separate method, marking it with an attributeDiagnosticNameAttribute.
The parameters of these methods are the parameters of the event being processed:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[DiagnosticName("System.Data.SqlClient.WriteCommandAfter")] | |
public void OnCommandAfter(DbCommand command) | |
{ | |
Console.WriteLine($"CommandText: {command.CommandText}"); | |
} |