Created
January 23, 2024 10:16
-
-
Save cobysy/3dd87dc9bf1ea5a4018c650e987d283c to your computer and use it in GitHub Desktop.
QueryInterceptor for Ef Core to apply SQL hints (Sql Server)
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
public class QueryInterceptor : DbCommandInterceptor | |
{ | |
public const string TagPrefix = "QueryInterceptor:"; | |
public override InterceptionResult<DbDataReader> ReaderExecuting( | |
DbCommand command, | |
CommandEventData eventData, | |
InterceptionResult<DbDataReader> result) | |
{ | |
ManipulateCommand(command: command); | |
return result; | |
} | |
public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync( | |
DbCommand command, | |
CommandEventData eventData, | |
InterceptionResult<DbDataReader> result, | |
CancellationToken cancellationToken = default) | |
{ | |
ManipulateCommand(command: command); | |
return new ValueTask<InterceptionResult<DbDataReader>>(result: result); | |
} | |
internal static void ManipulateCommand( | |
IDbCommand command) | |
{ | |
if (command.Connection is not SqlConnection) | |
{ | |
return; | |
} | |
var commandText = command.CommandText; | |
if (!TryGetQueryInterceptorHintArgs( | |
commandText: commandText, | |
args: out var hint)) | |
{ | |
return; | |
} | |
commandText = ApplyHints( | |
commandText: commandText, | |
args: hint!); | |
command.CommandText = commandText; | |
} | |
private static bool TryGetQueryInterceptorHintArgs( | |
string commandText, | |
out QueryInterceptorHintsArgs? args) | |
{ | |
var sqlLines = commandText.ReplaceLineEndings().Split(separator: Environment.NewLine); | |
// search for the line starting with: -- QueryInterceptor: | |
var prefix = "-- " + TagPrefix; | |
foreach (var sqlLine in sqlLines) | |
{ | |
if (sqlLine.StartsWith( | |
value: prefix, | |
comparisonType: StringComparison.Ordinal)) | |
{ | |
var json = sqlLine[prefix.Length..]; | |
args = Json.Json.Deserialize<QueryInterceptorHintsArgs>(json: json)!; | |
return true; | |
} | |
} | |
args = null; | |
return false; | |
} | |
private static string ApplyHints( | |
string commandText, | |
QueryInterceptorHintsArgs args) | |
{ | |
var fromPartRegex = new Regex( | |
pattern: $@"^(.*?FROM.*?{args.Entity}.*?)(\s.*?AS.*?)?$", | |
options: RegexOptions.Multiline); | |
var match = fromPartRegex.Match(input: commandText); | |
if (match.Success) | |
{ | |
var oldValue = match.Groups[groupnum: 0].Value; | |
var newValue = match.Groups[groupnum: 0].Value + $" WITH ({args.Hints})"; | |
commandText = commandText | |
.Replace( | |
oldValue: oldValue, | |
newValue: newValue, | |
comparisonType: StringComparison.Ordinal); | |
} | |
return commandText; | |
} | |
} |
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
public static class QueryInterceptorHelper | |
{ | |
public static IQueryable<T> InterceptWith<T>( | |
this IQueryable<T> source, | |
QueryInterceptorHintsArgs hintsArgs) | |
{ | |
return source | |
.TagWith(tag: QueryInterceptor.TagPrefix + Json.Json.Serialize(value: hintsArgs)); | |
} | |
} |
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
public record QueryInterceptorHintsArgs( | |
string Entity, | |
string Hints); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment