Entity Framework Core, SQL Server, and Deadlock victims
The application I am currently working on loads text files, transforms them, and then saves them as relational data.
I use Entity Framework Core with SQL Server for my persistence. Unfortunately, when inserting so much data I keep experiencing exceptions because SQL Server is throwing deadlock exceptions and preventing my data from being saved.
The insert involves data for 4 related tables, every access to the DB is an insert, which is why I was so surprised to see SQL Server couldn't cope with it - especially as Firebird SQL didn't struggle (nor did PostgreSQL).
It seems that adding OPTION (LOOP JOIN) to the end of SQL Statements prevents this problem, so I wrote an extension to ensure it is appended to Entity Framework Core generated SQL.
It is used like so:
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
base.OnConfiguring(options);
options.UseLoopJoinQueries();
}
and the extension code you need is:
public static class UseLoopJoinQueriesExtension
{
public static DbContextOptionsBuilder UseLoopJoinQueries(this DbContextOptionsBuilder builder)
{
if (builder is null)
throw new ArgumentNullException(nameof(builder));
builder.AddInterceptors(new OptionLoopJoinCommandInterceptor());
return builder;
}
}
internal class OptionLoopJoinCommandInterceptor : DbCommandInterceptor
{
public override Task<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result, CancellationToken cancellationToken = default)
{
AppendOptionToSql(command);
return Task.FromResult(result);
}
public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
{
AppendOptionToSql(command);
return result;
}
private static void AppendOptionToSql(DbCommand command)
{
const string OPTION_TEXT = " OPTION (LOOP JOIN)";
string[] commands = command.CommandText.Split(";");
for (int index = 0; index < commands.Length; index++)
{
string sql = commands[index].Trim();
if (sql.StartsWith("insert into ", StringComparison.InvariantCultureIgnoreCase)
|| sql.StartsWith("select ", StringComparison.InvariantCultureIgnoreCase)
|| sql.StartsWith("delete ", StringComparison.InvariantCultureIgnoreCase)
|| sql.StartsWith("merge ", StringComparison.InvariantCultureIgnoreCase))
{
commands[index] += OPTION_TEXT;
}
}
#pragma warning disable CA2100 // Review SQL queries for security vulnerabilities
command.CommandText = string.Join(";\r\n", commands);
#pragma warning restore CA2100 // Review SQL queries for security vulnerabilities
}
}
Comments