sqlserver执行sql的时候是带有lock的,这样可能会造成死锁现象
在使用ef/efcore时,生成的sql都是带有lock的,所以当使用ef组件时,需要单独设置一下with nolock
1. net fx集成
我的项目基于net fx4.6.1,ef版本是6.1.3版本
首先在项目中增加WithNoLockInterceptor
/// <summary> /// ef实现withnolock,给表名后面添加with(nolock),不适用.net core /// </summary> public class WithNoLockInterceptor : DbCommandInterceptor { private static readonly Regex TableAliasRegex = new Regex(@"(?<tableAlias>[dbo].[w+] AS [Extentd+](?! WITH(NOLOCK)))", RegexOptions.Multiline | RegexOptions.IgnoreCase); /// <summary> /// https://www.bbsmax.com/A/8Bz8V6V65x/ /// 建议不要为标记为 ThreadStaticAttribute 的字段指定初始值,因为这样的初始化只会发生一次,因此在类构造函数执行时只会影响一个线程。 /// 在不指定初始值的情况下,如果它是值类型,可依赖初始化为其默认值的字段,如果它是引用类型,则可依赖初始化为空引用的字段。 /// </summary> [ThreadStatic] public static bool Uselocking; [ThreadStatic] public static string CommandText; public override void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) { if (!Uselocking) { command.CommandText = TableAliasRegex.Replace(command.CommandText, "${tableAlias} WITH(NOLOCK) "); CommandText = command.CommandText; } } public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { if (!Uselocking) { command.CommandText = TableAliasRegex.Replace(command.CommandText, "${tableAlias} WITH(NOLOCK) "); CommandText = command.CommandText; } } }
然后在Global.asax.cs里面添加引用
DbInterception.Add(new WithNoLockInterceptor());
这样全局设置,可能有些sql想带lock,所以新建一个扩展类:
WithNoLockUtility
public static class WithNoLockUtility { /// <summary> /// 部分查询需要使用锁查询的,可以调用此扩展(默认全局查询使用with(nolock)) /// 参考:https://github.com/aspnetboilerplate/aspnetboilerplate/issues/1637/ /// 示例: /// 1、query.OrderByCustom(filters.orderFields).Select({...}).UseLocking(querable => querable.PagingAsync(filters.page, filters.rows)); /// 2、repository.EntitiesAsNoTracking.Select(...).UseLocking(item=>item.FirstOrDefaultAsync()); /// </summary> /// <typeparam name="T"></typeparam> /// <typeparam name="TResult"></typeparam> /// <param name="query"></param> /// <param name="queryAction"></param> /// <returns></returns> public static TResult UseLocking<T, TResult>(this IQueryable<T> query, Func<IQueryable<T>, TResult> queryAction) { WithNoLockInterceptor.Uselocking = true; TResult queryableResult = default(TResult); try { queryableResult = queryAction(query); } finally { WithNoLockInterceptor.Uselocking = false; } return queryableResult; } }
使用方式参考示例,不过这个方法我没有测试使用
2. netcore集成
这个直接参考这一篇文章:https://www.cnblogs.com/weihanli/p/12623934.html
使用拦截器:
public class QueryWithNoLockDbCommandInterceptor : DbCommandInterceptor { private static readonly Regex TableAliasRegex = new Regex(@"(?<tableAlias>AS [[a-zA-Z]w*](?! WITH (NOLOCK)))", RegexOptions.Multiline | RegexOptions.Compiled | RegexOptions.IgnoreCase); public override InterceptionResult<object> ScalarExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<object> result) { command.CommandText = TableAliasRegex.Replace( command.CommandText, "${tableAlias} WITH (NOLOCK)" ); return base.ScalarExecuting(command, eventData, result); } public override Task<InterceptionResult<object>> ScalarExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<object> result, CancellationToken cancellationToken = new CancellationToken()) { command.CommandText = TableAliasRegex.Replace( command.CommandText, "${tableAlias} WITH (NOLOCK)" ); return base.ScalarExecutingAsync(command, eventData, result, cancellationToken); } public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result) { command.CommandText = TableAliasRegex.Replace( command.CommandText, "${tableAlias} WITH (NOLOCK)" ); return result; } public override Task<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result, CancellationToken cancellationToken = new CancellationToken()) { command.CommandText = TableAliasRegex.Replace( command.CommandText, "${tableAlias} WITH (NOLOCK)" ); return base.ReaderExecutingAsync(command, eventData, result, cancellationToken); } }
然后在configservice里面添加
var services = new ServiceCollection(); services.AddDbContext<TestDbContext>(options => { options .UseLoggerFactory(loggerFactory) .UseSqlServer(DbConnectionString) .AddInterceptors(new QueryWithNoLockDbCommandInterceptor()) ; });
这样就可以全局开启with nolock了
以上均是参考网上资料写的,mark一下防止丢失
参考:
1.https://www.cnblogs.com/weihanli/p/12623934.html
2.https://www.cnblogs.com/slyzly/articles/11452341.html