I came across this:
https://entityframework.codeplex.com/wikipage?title=Interception
And it appears that you can do something like this:
public class HintInterceptor : DbCommandInterceptor
{
public override void ReaderExecuting(System.Data.Common.DbCommand command, DbCommandInterceptionContext<System.Data.Common.DbDataReader> interceptionContext)
{
command.CommandText += " option (recompile)";
base.ReaderExecuting(command, interceptionContext);
}
}
And register it like this (I did it in Application_Start
of global.asax.cs
):
DbInterception.Add(new HintInterceptor());
And it will let you alter the CommandText
. The only problem is that it's now attached for every reader query which might be a problem as some of them might be negatively impacted by that hint. I'm guessing I can do something with the context to figure out if the hint is appropriate or not, or worse case I could examine the CommandText
itself.
Doesn't quite seem the most elegant or fine-grained solution.
Edit: From the interceptorContext
, you can get the DbContexts
, so I defined an interface that looks like this:
public interface IQueryHintContext
{
string QueryHint { get; set; }
bool ApplyHint { get; set; }
}
And then created a class that derives from my original DbContext (generated by EF) and implements the above interface. Then I changed my interceptor to look like this:
public class HintInterceptor : DbCommandInterceptor
{
public override void ReaderExecuting(System.Data.Common.DbCommand command, DbCommandInterceptionContext<System.Data.Common.DbDataReader> interceptionContext)
{
if (interceptionContext.DbContexts.Any(db => db is Dal.IQueryHintContext))
{
var ctx = interceptionContext.DbContexts.First(db => db is Dal.IQueryHintContext) as Dal.IQueryHintContext;
if (ctx.ApplyHint)
{
command.CommandText += string.Format(" option ({0})", ctx.QueryHint);
}
}
base.ReaderExecuting(command, interceptionContext);
}
}
Now to use it, I create a context using my derived class instead of the original, set QueryHint
to whatever I want it to be (recompile
in this case) and set ApplyHint
right before I execute the command and set it back to false afterwards.
To make this all a little more self-contained, I ended up defining an interface like this:
public interface IQueryHintContext
{
string QueryHint { get; set; }
bool ApplyHint { get; set; }
}
And extended my db context like this (you could, of course, just use a partial class to extend the EF generated class as well):
public class MyEntities_Ext : MyEntities, IQueryHintContext
{
public string QueryHint { get; set; }
public bool ApplyHint { get; set; }
}
And then, to make the turn-on, turn-off part a little easier to handle, I defined this:
public class HintScope : IDisposable
{
public IQueryHintContext Context { get; private set; }
public void Dispose()
{
Context.ApplyHint = false;
}
public HintScope(IQueryHintContext context, string hint)
{
Context = context;
Context.ApplyHint = true;
Context.QueryHint = hint;
}
}
Now to use it, I can do just this:
using (var ctx = new MyEntities_Ext())
{
// any code that didn't need the query hint
// ....
// Now we want the query hint
using (var qh = new HintScope(ctx, "recompile"))
{
// query that needs the recompile hint
}
// back to non-hint code
}
This maybe slightly overkill and could be developed further (for example, using an enum for available hints instead of a string - or subclassing a recompile
query hint so you don't need to specify the string recompile
every time and risk a typo), but it solved my immediate problem.