Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions pakefile.ps1
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
$msbuild = "C:\Program Files\Microsoft Visual Studio\2022\Community\\MSBuild\Current\Bin\amd64\MSBuild.exe"
$msbuild = "C:\Program Files\Microsoft Visual Studio\2022\Preview\\MSBuild\Current\Bin\amd64\MSBuild.exe"
$msbuild = "C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\amd64\MSBuild.exe"
$msbuild = "C:\Program Files\Microsoft Visual Studio\2022\Preview\MSBuild\Current\Bin\amd64\MSBuild.exe"

function Init {
# dotnet tool install -g nbgv
Expand Down Expand Up @@ -42,7 +42,6 @@ function Patch-Version {

function Build {


. "${msbuild}" /bl `
.\src\NanoDbProfiler.AspNetCore\NanoDbProfiler.AspNetCore.csproj `
-p:Configuration=Release `
Expand Down
1 change: 1 addition & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Deterministic>true</Deterministic>
<EfCoreRelationPackage>8.0.11</EfCoreRelationPackage>
<GitVersionBaseDirectory>$(MSBuildThisFileDirectory)</GitVersionBaseDirectory>
</PropertyGroup>
<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/Example/Example.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.*" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="$(EfCoreRelationPackage)" />
<PackageReference Include="System.Text.Json" Version="8.0.*" />
</ItemGroup>

Expand Down
9 changes: 0 additions & 9 deletions src/Example/NanoDbProfilerEfCoreQueryInterceptor.cs

This file was deleted.

170 changes: 83 additions & 87 deletions src/NanoDbProfiler.AspNetCore/AspnetCoreExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,116 +1,112 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace Microsoft.Extensions.DependencyInjection;

namespace Microsoft.Extensions.DependencyInjection
public static class AspnetCoreExtensions
{
public static class AspnetCoreExtensions
public static IServiceCollection AddNanoDbProfiler(this IServiceCollection _)
{
public static IServiceCollection AddNanoDbProfiler(this IServiceCollection _)
{
const string EfCoreRelationalAssemblyString = "Microsoft.EntityFrameworkCore.Relational";
const string DiagnosticLoggerFullname = "Microsoft.EntityFrameworkCore.Diagnostics.Internal.RelationalCommandDiagnosticsLogger";
const string DiagnosticLoggerInterfaceName = "IRelationalCommandDiagnosticsLogger";
const string EfCoreRelationalAssemblyString = "Microsoft.EntityFrameworkCore.Relational";
const string DiagnosticLoggerFullname = "Microsoft.EntityFrameworkCore.Diagnostics.Internal.RelationalCommandDiagnosticsLogger";
const string DiagnosticLoggerInterfaceName = "IRelationalCommandDiagnosticsLogger";

var efCoreRelationalAsm = Assembly.Load(EfCoreRelationalAssemblyString);
ArgumentNullException.ThrowIfNull(efCoreRelationalAsm);
var efCoreRelationalAsm = Assembly.Load(EfCoreRelationalAssemblyString);
ArgumentNullException.ThrowIfNull(efCoreRelationalAsm);

var h = new Harmony("id");
var h = new Harmony("id");

Assembly [] assemblies = AppDomain.CurrentDomain.GetAssemblies();
Assembly [] assemblies = AppDomain.CurrentDomain.GetAssemblies();

var diagnosticsLoggerTypes = (
from t in assemblies.SelectMany(t => t.GetTypes())
where t.GetInterfaces().Any(t => t.Name.Contains(DiagnosticLoggerInterfaceName))
select t);
var diagnosticsLoggerTypes = (
from t in assemblies.SelectMany(t => t.GetTypes())
where t.GetInterfaces().Any(t => t.Name.Contains(DiagnosticLoggerInterfaceName))
select t);

var efCoreRelationAsm = (
from asm in assemblies
where asm.GetName().Name == EfCoreRelationalAssemblyString
select asm).Single();
var efCoreRelationAsm = (
from asm in assemblies
where asm.GetName().Name == EfCoreRelationalAssemblyString
select asm).Single();

Type [] efCoreRelationalTypes = efCoreRelationAsm.GetTypes();
Type [] efCoreRelationalTypes = efCoreRelationAsm.GetTypes();

var diagnosticsLoggerType = (
from t in efCoreRelationalTypes
where t.FullName == DiagnosticLoggerFullname
select t).Single();
var diagnosticsLoggerType = (
from t in efCoreRelationalTypes
where t.FullName == DiagnosticLoggerFullname
select t).Single();

MethodInfo [] diagnosticsLoggerMethods = diagnosticsLoggerType.GetMethods(AccessTools.all);
MethodInfo [] diagnosticsLoggerMethods = diagnosticsLoggerType.GetMethods(AccessTools.all);

var diagLoggerMethods = (
from m in diagnosticsLoggerMethods
where m.Name.Contains("Executed")
select m);
var diagLoggerMethods = (
from m in diagnosticsLoggerMethods
where m.Name.Contains("Executed")
select m);

patch("CommandReaderExecuted", nameof(Hooks.CommandReaderExecuted), diagLoggerMethods, h);
patch("CommandScalarExecuted", nameof(Hooks.CommandScalarExecuted), diagLoggerMethods, h);
patch("CommandNonQueryExecuted", nameof(Hooks.CommandNonQueryExecuted), diagLoggerMethods, h);
patch("CommandReaderExecutedAsync", nameof(Hooks.CommandReaderExecutedAsync), diagLoggerMethods, h);
patch("CommandScalarExecutedAsync", nameof(Hooks.CommandScalarExecutedAsync), diagLoggerMethods, h);
patch("CommandNonQueryExecutedAsync", nameof(Hooks.CommandNonQueryExecutedAsync), diagLoggerMethods, h);
patch("CommandReaderExecuted", nameof(Hooks.CommandReaderExecuted), diagLoggerMethods, h);
patch("CommandScalarExecuted", nameof(Hooks.CommandScalarExecuted), diagLoggerMethods, h);
patch("CommandNonQueryExecuted", nameof(Hooks.CommandNonQueryExecuted), diagLoggerMethods, h);
patch("CommandReaderExecutedAsync", nameof(Hooks.CommandReaderExecutedAsync), diagLoggerMethods, h);
patch("CommandScalarExecutedAsync", nameof(Hooks.CommandScalarExecutedAsync), diagLoggerMethods, h);
patch("CommandNonQueryExecutedAsync", nameof(Hooks.CommandNonQueryExecutedAsync), diagLoggerMethods, h);

var relationCommandType = efCoreRelationalTypes.Single(x => x.Name == "RelationalCommand");
var methods = relationCommandType.GetMethods(AccessTools.all);
var relationCommandType = efCoreRelationalTypes.Single(x => x.Name == "RelationalCommand");
var methods = relationCommandType.GetMethods(AccessTools.all);

patch("ExecuteReader", methods, h,
prefix: new HarmonyMethod(typeof(Hooks).GetMethod(nameof(Hooks.ExecuteReaderPrefix))),
postfix: new HarmonyMethod(typeof(Hooks).GetMethod(nameof(Hooks.ExecuteReaderPostfix))));
patch("ExecuteReader", methods, h,
prefix: new HarmonyMethod(typeof(Hooks).GetMethod(nameof(Hooks.ExecuteReaderPrefix))),
postfix: new HarmonyMethod(typeof(Hooks).GetMethod(nameof(Hooks.ExecuteReaderPostfix))));

return _;
}
return _;
}

private static void patch(string name, IEnumerable<MethodInfo> methods, Harmony harmony, HarmonyMethod prefix, HarmonyMethod postfix)
{
var method = (
from m in methods
where m.Name == name
select m).Single();
private static void patch(string name, IEnumerable<MethodInfo> methods, Harmony harmony, HarmonyMethod prefix, HarmonyMethod postfix)
{
var method = (
from m in methods
where m.Name == name
select m).Single();

var replacement = harmony.Patch(method, prefix: prefix, postfix: postfix);
ArgumentNullException.ThrowIfNull(replacement);
}
var replacement = harmony.Patch(method, prefix: prefix, postfix: postfix);
ArgumentNullException.ThrowIfNull(replacement);
}

private static void patch(string name, string hookName, IEnumerable<MethodInfo> methods, Harmony harmony)
{
var replacement = harmony.Patch(methods.Single(x => x.Name == name), new HarmonyMethod(typeof(Hooks).GetMethod(name)));
ArgumentNullException.ThrowIfNull(replacement);
}
private static void patch(string name, string hookName, IEnumerable<MethodInfo> methods, Harmony harmony)
{
var replacement = harmony.Patch(methods.Single(x => x.Name == name), new HarmonyMethod(typeof(Hooks).GetMethod(name)));
ArgumentNullException.ThrowIfNull(replacement);
}

public static WebApplication UseNanoDbProfilerPage(this WebApplication app, string route = "query-log")
{
QueryLogMiddleware.QUERY_LOG_ROUTE = "/" + route;
public static WebApplication UseNanoDbProfilerPage(this WebApplication app, string route = "query-log")
{
QueryLogMiddleware.QUERY_LOG_ROUTE = "/" + route;

EfQueryLog.ServiceScopeFactory = app.Services.GetRequiredService<IServiceScopeFactory>();
EfQueryLog.ServiceScopeFactory = app.Services.GetRequiredService<IServiceScopeFactory>();

app.MapGet(route, (HttpRequest h) =>
{
var metrics = EfCoreMetrics.GetInstance();
MediaTypeHeaderValue.TryParseList(h.Headers ["Accept"], out var accept);

IResult resp = accept switch
{
null => EfQueryLog.TextResult(metrics),
var a when a.Any(x => x.MatchesMediaType("text/html")) => EfQueryLog.HtmlResult(metrics),
var a when a.Any(x => x.MatchesMediaType("text/plain")) => EfQueryLog.TextResult(metrics),
var a when a.Any(x => x.MatchesMediaType("application/json")) => EfQueryLog.JsonResult(metrics),
_ => EfQueryLog.TextResult(metrics)
};

return resp;
});

app.MapDelete(route, (HttpRequest h) =>
app.MapGet(route, (HttpRequest h) =>
{
var metrics = EfCoreMetrics.GetInstance();
MediaTypeHeaderValue.TryParseList(h.Headers ["Accept"], out var accept);

IResult resp = accept switch
{
var metrics = EfCoreMetrics.GetInstance();
null => EfQueryLog.TextResult(metrics),
var a when a.Any(x => x.MatchesMediaType("text/html")) => EfQueryLog.HtmlResult(metrics),
var a when a.Any(x => x.MatchesMediaType("text/plain")) => EfQueryLog.TextResult(metrics),
var a when a.Any(x => x.MatchesMediaType("application/json")) => EfQueryLog.JsonResult(metrics),
_ => EfQueryLog.TextResult(metrics)
};

return resp;
});

metrics.Clear();
return Results.NoContent();
});
app.MapDelete(route, (HttpRequest h) =>
{
var metrics = EfCoreMetrics.GetInstance();

metrics.Clear();
return Results.NoContent();
});

return app;
}

public static void UseNanoDbProfilerToolbar(this WebApplication app) => app.UseMiddleware<QueryLogMiddleware>();
return app;
}

public static void UseNanoDbProfilerToolbar(this WebApplication app) => app.UseMiddleware<QueryLogMiddleware>();
}
55 changes: 1 addition & 54 deletions src/NanoDbProfiler.AspNetCore/DashboardData.cs
Original file line number Diff line number Diff line change
@@ -1,57 +1,4 @@

using System.Data.Common;

using Microsoft.EntityFrameworkCore.Diagnostics;

namespace NanoDbProfiler.AspNetCore;

internal class NanoDbProfilerEfCoreQueryInterceptor : DbCommandInterceptor
{
private static Metric metricFactory(CommandExecutedEventData eventData)
{
return new Metric()
{
Duration = eventData.Duration.TotalMilliseconds,
Query = eventData.Command.CommandText
};
}

public override int NonQueryExecuted(DbCommand command, CommandExecutedEventData eventData, int result)
{
EfCoreMetrics.GetInstance().Add(metricFactory(eventData));
return base.NonQueryExecuted(command, eventData, result);
}

public override ValueTask<int> NonQueryExecutedAsync(DbCommand command, CommandExecutedEventData eventData, int result, CancellationToken cancellationToken = default)
{
EfCoreMetrics.GetInstance().Add(metricFactory(eventData));
return base.NonQueryExecutedAsync(command, eventData, result, cancellationToken);
}

public override DbDataReader ReaderExecuted(DbCommand command, CommandExecutedEventData eventData, DbDataReader result)
{
EfCoreMetrics.GetInstance().Add(metricFactory(eventData));
return base.ReaderExecuted(command, eventData, result);
}

public override ValueTask<object?> ScalarExecutedAsync(DbCommand command, CommandExecutedEventData eventData, object? result, CancellationToken cancellationToken = default)
{
EfCoreMetrics.GetInstance().Add(metricFactory(eventData));
return base.ScalarExecutedAsync(command, eventData, result, cancellationToken);
}

public override object? ScalarExecuted(DbCommand command, CommandExecutedEventData eventData, object? result)
{
EfCoreMetrics.GetInstance().Add(metricFactory(eventData));
return base.ScalarExecuted(command, eventData, result);
}

public override ValueTask<DbDataReader> ReaderExecutedAsync(DbCommand command, CommandExecutedEventData eventData, DbDataReader result, CancellationToken cancellationToken = default)
{
EfCoreMetrics.GetInstance().Add(metricFactory(eventData));
return base.ReaderExecutedAsync(command, eventData, result, cancellationToken);
}
}
namespace NanoDbProfiler.AspNetCore;

public struct DashboardData
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,42 @@

public static class EfCoreDbContextOptionsBuilderExtensions
{
public static DbContextOptionsBuilder AddNanoDbProfilerEfCoreInterceptor(this DbContextOptionsBuilder o)
public static object AddNanoDbProfilerEfCoreInterceptor(object dbContextOptionsBuilder)
{
return o.AddInterceptors(new NanoDbProfilerEfCoreQueryInterceptor());
// Load the DbContextOptionsBuilder type dynamically
var dbContextOptionsBuilderType = dbContextOptionsBuilder.GetType();

// Load the DbCommandInterceptor type dynamically
var dbCommandInterceptorType = Type.GetType("Microsoft.EntityFrameworkCore.Diagnostics.DbCommandInterceptor");

if (dbCommandInterceptorType == null)
{
throw new InvalidOperationException("Unable to find DbCommandInterceptor type.");
}

// Check if your class NanoDbProfilerEfCoreQueryInterceptor inherits from DbCommandInterceptor
var nanoDbProfilerEfCoreQueryInterceptorType = typeof(NanoDbProfilerEfCoreQueryInterceptor);

if (!dbCommandInterceptorType.IsAssignableFrom(nanoDbProfilerEfCoreQueryInterceptorType))
{
throw new InvalidOperationException($"{nanoDbProfilerEfCoreQueryInterceptorType.Name} does not inherit from DbCommandInterceptor.");
}

// Create an instance of NanoDbProfilerEfCoreQueryInterceptor dynamically
var interceptorInstance = Activator.CreateInstance(nanoDbProfilerEfCoreQueryInterceptorType);

// Get the AddInterceptors method of DbContextOptionsBuilder dynamically
var addInterceptorsMethod = dbContextOptionsBuilderType
.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.FirstOrDefault(m => m.Name == "AddInterceptors" && m.GetParameters().Length == 1);

if (addInterceptorsMethod == null)
{
throw new InvalidOperationException("Unable to find the AddInterceptors method.");
}

// Invoke the AddInterceptors method dynamically
return addInterceptorsMethod.Invoke(dbContextOptionsBuilder, new [] { interceptorInstance });
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@

<ItemGroup>
<PackageReference Include="Lib.Harmony" Version="2.3.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.*" />
<!--<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="$(EfCoreRelationPackage)" />-->
<PackageReference Include="System.Text.Encodings.Web" Version="8.0.*" />
<PackageReference Include="System.Text.Json" Version="8.0.*" />
</ItemGroup>
Expand Down
Loading