We are using DiagnosticListeners
in order to modify the SQL command text that EF Core produces. The problem is that our listeners need to be modifying the SQL command based on some user specific data that comes into our Api via HttpRequests. Our current solution is extremely hacky and likely to cause issues in the future. We register a new listener each time DbContext
is created:
public class MyContext : DbContext
{
private readonly HReCommandAdapter _adapter;
public MyContext(DbContextOptions options) : base(options)
{
_adapter = new DbCommandAdapter();
var listener = this.GetService<DiagnosticSource>();
(listener as DiagnosticListener).SubscribeWithAdapter(_adapter);
}
public override void Dispose()
{
_adapter.Dispose();
base.Dispose();
}
//DbSets and stuff
}
The simplified listener code looks like below:
public class DbCommandAdapter : IDisposable
{
private bool _hasExecuted = false;
private Guid? _lastExecId = null;
[DiagnosticName("Microsoft.EntityFrameworkCore.Database.Command.CommandExecuting")]
public void OnCommandExecuting(DbCommand command, DbCommandMethod executeMethod, Guid commandId, Guid connectionId, bool async, DateTimeOffset startTime)
{
if (!_lastExecId.HasValue)
_lastExecId = connectionId;
if (_lastExecId != connectionId)
return;
//We are modifying command text here
}
[DiagnosticName("Microsoft.EntityFrameworkCore.Database.Command.CommandExecuted")]
public void OnCommandExecuted(object result, bool async)
{
}
[DiagnosticName("Microsoft.EntityFrameworkCore.Database.Command.CommandError")]
public void OnCommandError(Exception exception, bool async)
{
}
public void Dispose() { //No code in here }
}
As you can see, our current approach is to use the connectionId
which will be different each time DbContext
is created. The reason for this hacky approach is because the listener instances are not disposed even though the DbContext.Dispose()
is called every time the HttpRequest
is processed. So connectionId
allows to basically have an illusion of 1:1 mapping between a listener and a given DbContext
instance.
What happens though is that the amount of listener instances keep piling up throughout the lifetime of the api and the only time the instances go away is when the application pools stop or recycle.
Is it possible to somehow dispose of these listener instances and how? I'm also open to a different approach in order to modify SQL commands (the diagnostic listeners was the only viable one we found for EF Core).
EDIT:
I am modifying only SELECT
commands. I omitted the details, but the DbCommandAdapter
gets created with a user specific prefix that is different based on the user trying to access the API.
So for example if the query is:
SELECT FIELD1, FIELD2 FROM EMPLOYEES
and the user specific prefix is USER_SOMENUMBER
, then the modified query ends up:
SELECT FIELD1, FIELD2 FROM USER_SOMENUMBER_EMPLOYEES
I understand that this is fragile but we guarantee that the schema of the tablename that we change is identical and it is not a concern.