Last active
June 21, 2017 15:54
-
-
Save danielcrenna/5b07b8497db0a2bb96a3e2078fc76575 to your computer and use it in GitHub Desktop.
IDbConnection profiling in .NET Core
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
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 | |
} | |
} |
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
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