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