Browse Source

优化WithNoLockInterceptor

懒得勤快 9 months ago
parent
commit
a5d90448b3
3 changed files with 45 additions and 30 deletions
  1. 1 1
      Directory.Build.props
  2. 37 26
      Masuit.Tools.AspNetCore/Extensions/WithNoLockInterceptor.cs
  3. 7 3
      README.md

+ 1 - 1
Directory.Build.props

@@ -1,6 +1,6 @@
 <Project>
  <PropertyGroup>
-   <Version>2025.1.5</Version>
+   <Version>2025.1.6</Version>
    <Deterministic>true</Deterministic>
  </PropertyGroup>
 </Project>

+ 37 - 26
Masuit.Tools.AspNetCore/Extensions/QueryWithNoLockDbCommandInterceptor.cs → Masuit.Tools.AspNetCore/Extensions/WithNoLockInterceptor.cs

@@ -1,19 +1,21 @@
 using Microsoft.EntityFrameworkCore.Diagnostics;
 using System.Data.Common;
 using System.Text.RegularExpressions;
+using Microsoft.EntityFrameworkCore;
 
 namespace Masuit.Tools.Core;
 
-public class QueryWithNoLockDbCommandInterceptor : DbCommandInterceptor
+/// <summary>
+/// WITH (NOLOCK)全局拦截器,仅限SQL Server使用
+/// </summary>
+/// <param name="enableGlobalNolock">全局启用,无需手动调用WithNolock扩展</param>
+public class WithNoLockInterceptor(bool enableGlobalNolock = false) : DbCommandInterceptor
 {
-    private static readonly Regex TableAliasRegex = new Regex(@"(?<tableAlias>AS \[[a-zA-Z]\w*\](?! WITH \(NOLOCK\)))", RegexOptions.Multiline | RegexOptions.Compiled | RegexOptions.IgnoreCase);
+    private static readonly Regex TableRegex = new Regex(@"(?<table>\[\w+\] 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)"
-        );
+        AddWithNoLock(command);
         return base.ScalarExecuting(command, eventData, result);
     }
 
@@ -22,31 +24,24 @@ public class QueryWithNoLockDbCommandInterceptor : DbCommandInterceptor
     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)"
-        );
+        AddWithNoLock(command);
         return base.ScalarExecutingAsync(command, eventData, result, cancellationToken);
     }
 
 #else
+
     public override ValueTask<InterceptionResult<object>> ScalarExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<object> result,
 CancellationToken cancellationToken = new CancellationToken())
     {
-        command.CommandText = TableAliasRegex.Replace(
-            command.CommandText,
-            "${tableAlias} WITH (NOLOCK)"
-        );
+        AddWithNoLock(command);
         return base.ScalarExecutingAsync(command, eventData, result, cancellationToken);
     }
+
 #endif
 
     public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
     {
-        command.CommandText = TableAliasRegex.Replace(
-            command.CommandText,
-            "${tableAlias} WITH (NOLOCK)"
-        );
+        AddWithNoLock(command);
         return result;
     }
 
@@ -55,22 +50,38 @@ CancellationToken cancellationToken = new CancellationToken())
     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)"
-        );
+        AddWithNoLock(command);
         return base.ReaderExecutingAsync(command, eventData, result, cancellationToken);
     }
 
 #else
+
     public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result,
 CancellationToken cancellationToken = new CancellationToken())
     {
-        command.CommandText = TableAliasRegex.Replace(
-            command.CommandText,
-            "${tableAlias} WITH (NOLOCK)"
-        );
+        AddWithNoLock(command);
         return base.ReaderExecutingAsync(command, eventData, result, cancellationToken);
     }
 #endif
+
+    private void AddWithNoLock(DbCommand command)
+    {
+        // 检查查询是否有标记
+        if (enableGlobalNolock || command.CommandText.StartsWith("-- NOLOCK"))
+        {
+            command.CommandText = TableRegex.Replace(command.CommandText, "${table} WITH (NOLOCK)");
+        }
+    }
 }
+
+public static class WithNoLockExt
+{
+    public static IQueryable<T> WithNolock<T>(this IQueryable<T> queryable)
+    {
+        return queryable.TagWith("-- NOLOCK");
+    }
+}
+
+[Obsolete("请使用WithNoLockInterceptor替代")]
+public class QueryWithNoLockDbCommandInterceptor : WithNoLockInterceptor
+{ }

+ 7 - 3
README.md

@@ -1181,15 +1181,19 @@ var allchanges=dbContext.GetAllChanges();//获取增删改的实体字段信息
 
 #### nolock查询
 
-sqlserver:
+SQL Server:
 
 上下文注入Interceptor即可在任何查询时使用nolock查询
 
 ```csharp
-services.AddDbContext<TContext>(opt => opt.UseSqlserver("ConnString", builder => builder.AddInterceptors(new QueryWithNoLockDbCommandInterceptor()));
+services.AddDbContext<TContext>(opt => opt.UseSqlserver("ConnString", builder => builder.AddInterceptors(new WithNoLockInterceptor(true))); // 启用全局nolock查询
+services.AddDbContext<TContext>(opt => opt.UseSqlserver("ConnString", builder => builder.AddInterceptors(new WithNoLockInterceptor())); // 按需启用全局nolock查询
+
+// 按需启用全局nolock查询,执行单个nolock查询
+await dbContext.Users.Where(x=>x.Name=="aaa").WithNolock().ToListAsync();
 ```
 
-通用数据库:
+其他通用数据库:
 
 nolock本质是开启一个 `读未提交`级别的事务,此时的查询性能最好,但有可能会读取到脏数据。