Skip to content

Instantly share code, notes, and snippets.

@danielcrenna
Last active June 21, 2017 15:54
Show Gist options
  • Save danielcrenna/5b07b8497db0a2bb96a3e2078fc76575 to your computer and use it in GitHub Desktop.
Save danielcrenna/5b07b8497db0a2bb96a3e2078fc76575 to your computer and use it in GitHub Desktop.
IDbConnection profiling in .NET Core
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace DatabaseProfiling
{
public class ProfilingDbCommand : DbCommand
{
private readonly DbCommand _inner;
private readonly IHttpContextAccessor _http;
private readonly ILogger _logger;
public ProfilingDbCommand(DbCommand inner, ILoggerFactory loggerFactory, IHttpContextAccessor http)
{
_inner = inner;
_http = http;
_logger = loggerFactory.CreateLogger(nameof(ProfilingDbCommand));
}
public override void Cancel()
{
_inner.Cancel();
}
public override int ExecuteNonQuery()
{
_logger.Log(LogLevel.Trace, 0, BuildSqlStatement(), null, (s, e) => $"SQL: {s}");
var sw = Stopwatch.StartNew();
var result = _inner.ExecuteNonQuery();
sw.Stop();
HttpResponse response = _http.HttpContext.Response;
response.OnStarting(() =>
{
AddOrUpdateSamplingTime(sw, response);
return Task.CompletedTask;
});
return result;
}
public override object ExecuteScalar()
{
_logger.Log(LogLevel.Trace, 0, default(string), null, (s, e) => $"SQL: {BuildSqlStatement()}");
var sw = Stopwatch.StartNew();
var result = _inner.ExecuteScalar();
sw.Stop();
HttpResponse response = _http.HttpContext.Response;
response.OnStarting(() =>
{
AddOrUpdateSamplingTime(sw, response);
return Task.CompletedTask;
});
return result;
}
protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
{
_logger.Log(LogLevel.Trace, 0, default(string), null, (s, e) => $"SQL: {BuildSqlStatement()}");
var sw = Stopwatch.StartNew();
var result = _inner.ExecuteReader(behavior);
sw.Stop();
HttpResponse response = _http.HttpContext.Response;
response.OnStarting(() =>
{
AddOrUpdateSamplingTime(sw, response);
return Task.CompletedTask;
});
return result;
}
private static void AddOrUpdateSamplingTime(Stopwatch sw, HttpResponse response)
{
double sample = sw.Elapsed.TotalMilliseconds;
const string header = "X-DatabaseExecutionTime-Milliseconds";
if (response.Headers.ContainsKey(header))
{
double cumulating = double.Parse(response.Headers[header]);
response.Headers[header] = $"{sample + cumulating}";
}
else
{
response.Headers.Add(header, $"{sample}");
}
}
private string BuildSqlStatement()
{
string query = _inner.CommandText;
foreach (SqlParameter p in _inner.Parameters)
{
string value = p.Value is string ? $"'{p.Value}'" : $"{p.Value}";
query = query.Replace($"@{p.ParameterName}", value);
}
return query;
}
#region Passthrough
public override void Prepare()
{
_inner.Prepare();
}
public override string CommandText
{
get => _inner.CommandText;
set => _inner.CommandText = value;
}
public override int CommandTimeout
{
get => _inner.CommandTimeout;
set => _inner.CommandTimeout = value;
}
public override CommandType CommandType
{
get => _inner.CommandType;
set => _inner.CommandType = value;
}
public override UpdateRowSource UpdatedRowSource
{
get => _inner.UpdatedRowSource;
set => _inner.UpdatedRowSource = value;
}
protected override DbConnection DbConnection
{
get => _inner.Connection;
set => _inner.Connection = value;
}
protected override DbParameterCollection DbParameterCollection => _inner.Parameters;
protected override DbTransaction DbTransaction
{
get => _inner.Transaction;
set => _inner.Transaction = value;
}
public override bool DesignTimeVisible
{
get => _inner.DesignTimeVisible;
set => _inner.DesignTimeVisible = value;
}
protected override DbParameter CreateDbParameter()
{
return _inner.CreateParameter();
}
#endregion
}
}
using System.Data;
using System.Data.Common;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace DatabaseProfiling
{
public class ProfilingDbConnection : DbConnection
{
private readonly ILoggerFactory _loggerFactory;
private readonly IHttpContextAccessor _http;
public DbConnection Inner { get; }
public ProfilingDbConnection(DbConnection inner, ILoggerFactory loggerFactory, IHttpContextAccessor http)
{
Inner = inner;
_loggerFactory = loggerFactory;
_http = http;
}
protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel)
{
return Inner.BeginTransaction(isolationLevel);
}
public override void Close()
{
Inner.Close();
}
public override void Open()
{
Inner.Open();
}
public override string ConnectionString
{
get => Inner.ConnectionString;
set => Inner.ConnectionString = value;
}
public override string Database => Inner.Database;
public override ConnectionState State => Inner.State;
public override string DataSource => Inner.DataSource;
public override string ServerVersion => Inner.ServerVersion;
protected override DbCommand CreateDbCommand()
{
return new ProfilingDbCommand(Inner.CreateCommand(), _loggerFactory, _http);
}
public override void ChangeDatabase(string databaseName)
{
Inner.ChangeDatabase(databaseName);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment