SU2.EntityFrameworkCore.SqlServer.Hints 2.0.2

dotnet add package SU2.EntityFrameworkCore.SqlServer.Hints --version 2.0.2
                    
NuGet\Install-Package SU2.EntityFrameworkCore.SqlServer.Hints -Version 2.0.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="SU2.EntityFrameworkCore.SqlServer.Hints" Version="2.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SU2.EntityFrameworkCore.SqlServer.Hints" Version="2.0.2" />
                    
Directory.Packages.props
<PackageReference Include="SU2.EntityFrameworkCore.SqlServer.Hints" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add SU2.EntityFrameworkCore.SqlServer.Hints --version 2.0.2
                    
#r "nuget: SU2.EntityFrameworkCore.SqlServer.Hints, 2.0.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package SU2.EntityFrameworkCore.SqlServer.Hints@2.0.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=SU2.EntityFrameworkCore.SqlServer.Hints&version=2.0.2
                    
Install as a Cake Addin
#tool nuget:?package=SU2.EntityFrameworkCore.SqlServer.Hints&version=2.0.2
                    
Install as a Cake Tool

SU2.EntityFrameworkCore.SqlServer.Hints

Thư viện hỗ trợ thêm các SQL Server table hint cho truy vấn Entity Framework Core. Phiên bản 2.0.2 hiện cung cấp hint NOLOCK, lấy cảm hứng từ EFCore.NoLock.

Infographic luồng hoạt động của NOLOCK

Cài đặt

Cài đặt từ NuGet:

dotnet add package SU2.EntityFrameworkCore.SqlServer.Hints --version 2.0.2

Hoặc tạo gói NuGet cục bộ:

dotnet pack src/SU2.EntityFrameworkCore.SqlServer.Hints/SU2.EntityFrameworkCore.SqlServer.Hints.csproj -c Release -o artifacts

Sử dụng

Đăng ký interceptor khi cấu hình DbContext:

using Microsoft.EntityFrameworkCore;
using SU2.EntityFrameworkCore.SqlServer.Hints;

services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(connectionString).UseNoLock());

Sau đó chọn các truy vấn cần NOLOCK:

var customers = await db.Customers
    .AsNoLock() // WithNoLock() có tác dụng tương đương.
    .Where(x => x.IsActive)
    .ToListAsync();

Mặc định UseNoLock() chỉ đăng ký interceptor, không bật hint cho toàn bộ DbContext. AsNoLock()WithNoLock() chỉ đánh dấu truy vấn. Nếu chưa gọi UseNoLock(), marker này không làm thay đổi SQL khi thực thi.

Bật NOLOCK toàn cục

Nếu muốn mọi câu SELECT phù hợp trong DbContext tự động nhận WITH (NOLOCK), không cần gọi .AsNoLock() hoặc .WithNoLock() cho từng query:

services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(connectionString)
           .UseNoLock(hints =>
           {
               hints.ApplyGlobally = true;
           }));

ApplyGlobally = true chỉ áp dụng cho command SQL Server dạng text gồm một câu SELECT (có thể có các DECLARE biến scalar ở trước). INSERT, UPDATE, DELETE, stored procedure, nhiều batch, table variable và các nguồn SQL chưa được hỗ trợ sẽ được giữ nguyên; trường hợp SELECT không hỗ trợ sẽ ghi Warning và giữ SQL gốc. Query đã gắn -- SU2_NOLOCK vẫn dùng đường kiểm tra nghiêm ngặt như trước.

ApplyGlobally mặc định là false. Đây là tùy chọn theo từng DbContext, không phải thay đổi isolation level của database. Vì nó áp dụng cả các query không dự kiến, chỉ nên bật khi toàn bộ workload chấp nhận dirty read và các giới hạn của NOLOCK. Có thể bật đồng thời ApplyGlobally = true và vẫn dùng .AsNoLock(); marker tường minh sẽ được ưu tiên kiểm tra nghiêm ngặt.

Chọn cơ chế rewrite và cache

Mặc định UseNoLock() sử dụng ScriptGenerator, parser SQL Server 2019 và bật cache SQL:

options.UseSqlServer(connectionString).UseNoLock(hints =>
{
    hints.ParserVersion = SqlServerParserVersion.SqlServer2019;
    hints.RewriteMode = NoLockRewriteMode.ScriptGenerator;
    hints.EnableCache = true;
    hints.CacheExpiration = TimeSpan.FromMinutes(10);
});

Để giữ nguyên định dạng và comment bằng OffsetInsertion:

options.UseSqlServer(connectionString).UseNoLock(hints =>
{
    hints.RewriteMode = NoLockRewriteMode.OffsetInsertion;
    hints.EnableCache = false; // Tùy chọn; cả hai chế độ đều hỗ trợ cache.
});
  • ScriptGenerator: thêm hint trên AST, sinh lại SQL bằng Sql150ScriptGenerator, Sql160ScriptGenerator hoặc Sql170ScriptGenerator tương ứng parser, rồi parse lại trước khi thực thi. SQL có thể đổi định dạng và mất comment/tag khác; marker SU2_NOLOCK được khôi phục. Không dùng chế độ này nếu hệ thống bên ngoài phụ thuộc vào comment truy vấn.
  • OffsetInsertion: dùng AST xác định vị trí chèn WITH (NOLOCK), không sinh lại toàn bộ SQL.
  • Hai chế độ dùng chung kiểm tra SELECT, CTE, hint xung đột và nguồn bảng được hỗ trợ. Sinh lại SQL không có nghĩa là bảo đảm an toàn ngữ nghĩa tuyệt đối, cũng không loại bỏ rủi ro dirty read của NOLOCK.
  • Cache dùng MemoryCache riêng, chia sẻ trong tiến trình, có ngân sách 8 MiB tính theo chuỗi SQL đầu vào + đầu ra UTF-16 và phụ phí quy ước mỗi entry; đây không phải giới hạn chính xác tổng heap. Entry vượt ngân sách không được lưu.
  • Key gồm toàn bộ SQL, phiên bản parser, chế độ rewrite và thời hạn cache. Thời hạn tuyệt đối mặc định 10 phút, cho phép lớn hơn 0 đến tối đa 1 ngày; đọc cache không gia hạn.
  • Chỉ lưu chuỗi rewrite thành công; không cache exception, kết quả truy vấn, DbCommand hay giá trị parameter. Literal đã nhúng vào SQL vẫn thuộc chuỗi cache; có thể tắt cache nếu cần tránh lưu chúng. Truy vấn không có marker không được cache.
  • Cache thread-safe; các lệnh đồng thời cùng cache miss có thể rewrite trùng trước khi lưu. Cache không bảo đảm single-flight. Cấu hình được chụp tại lúc đăng ký, sửa object sau đó không ảnh hưởng interceptor.

Chọn phiên bản parser

Mặc định thư viện dùng TSql150Parser (SQL Server 2019). Có thể chọn parser phù hợp với phiên bản SQL Server:

// SQL Server 2019
options.UseSqlServer(connectionString)
       .UseNoLock(SqlServerParserVersion.SqlServer2019);

// SQL Server 2022
options.UseSqlServer(connectionString)
       .UseNoLock(SqlServerParserVersion.SqlServer2022);

// SQL Server 2025 / T-SQL 170
options.UseSqlServer(connectionString)
       .UseNoLock(SqlServerParserVersion.SqlServer2025);

Các giá trị trên lần lượt sử dụng TSql150Parser, TSql160ParserTSql170Parser. Parser chỉ dùng để xác thực và tìm table reference; nó không thay đổi phiên bản SQL Server của database.

Khi chạy truy vấn, thư viện chuyển table reference như sau:

-- Trước
FROM [Customers] AS [c]

-- Sau
FROM [Customers] AS [c] WITH (NOLOCK)

Hint được thêm tại thời điểm thực thi. Vì vậy ToQueryString() có thể chưa hiển thị phần WITH (NOLOCK).

Phạm vi và an toàn

  • Tag chính xác -- SU2_NOLOCK sẽ bật hint cho toàn bộ câu lệnh SQL, bao gồm bảng trong JOIN và subquery.
  • Chuỗi dữ liệu, tên bảng hoặc block comment có chứa SU2_NOLOCK không kích hoạt hint ngoài ý muốn.
  • SQL được phân tích bằng Microsoft.SqlServer.TransactSql.ScriptDom phiên bản 180.107.0. Chế độ OffsetInsertion giữ nguyên literal, comment, parameter và định dạng; ScriptGenerator sinh lại SQL nên không bảo toàn comment/định dạng.
  • Hỗ trợ bảng có hoặc không có alias, tên nhiều phần, tên định danh escape, temporal table, join và derived subquery.
  • Nếu bảng đã có duy nhất hint NOLOCK, thư viện giữ nguyên và không chèn trùng.
  • OPENJSON (bao gồm truy vấn Contains của EF Core 8/9) và table-valued function được giữ nguyên, không gắn WITH (NOLOCK) vào lời gọi hàm. Các bảng được tham chiếu trực tiếp trong cùng truy vấn vẫn được thêm hint; hint không được truyền vào các bảng bên trong định nghĩa hàm.
  • Hỗ trợ nguồn inline VALUES (bao gồm LINQ trên danh sách inline), OPENXML, XML @xml.nodes(...), CONTAINSTABLE/FREETEXTTABLE và table variable: giữ nguyên nguồn này, không thêm hint vào nguồn hay đối số tên bảng của hàm full-text.
  • Hỗ trợ PIVOT/UNPIVOT: duyệt xuống và thêm hint vào bảng nguồn, không thêm hint vào alias của kết quả pivot/unpivot.
  • OPENQUERY, OPENROWSET (bao gồm BULK) được giữ nguyên. Thư viện không sửa SQL trong chuỗi truy vấn remote, không áp dụng NOLOCK cho nguồn bên ngoài và không bảo đảm các nguồn này tránh lock. Nếu truy vấn chỉ chứa các nguồn được giữ nguyên thì SQL không được thêm hint nào.
  • Các hint khác, TABLESAMPLE, CTE và các dạng table source chưa hỗ trợ khác vẫn ném NotSupportedException thay vì âm thầm bỏ qua.
  • Hỗ trợ một câu SELECT dạng text, có thể có các khai báo biến scalar DECLARE phía trước trong cùng batch. Lệnh ghi, stored procedure, nhiều SELECT, DECLARE table/cursor và nhiều batch phân cách bằng GO bị từ chối.
  • Query không gọi AsNoLock() hoặc WithNoLock() không bị thay đổi.
  • Khi ApplyGlobally = true, query không gọi hai extension trên vẫn được xử lý nếu là SELECT hợp lệ; khi ApplyGlobally = false, chúng luôn được giữ nguyên.

NOLOCK tương đương với mức cô lập READ UNCOMMITTED. Nó có thể đọc dữ liệu chưa commit, dữ liệu không nhất quán, thiếu dòng hoặc trùng dòng. Chỉ nên sử dụng khi ứng dụng chấp nhận các rủi ro này. NOLOCK không có nghĩa là hoàn toàn không dùng lock và không đảm bảo truy vấn luôn không bị block.

Tương thích

Phiên bản 2.0.2 hỗ trợ:

  • net6.0
  • net7.0
  • net8.0
  • net9.0
  • net10.0

Dependency tối thiểu là Microsoft.EntityFrameworkCore.Relational 6.0.30. Ứng dụng nên sử dụng provider EF Core cùng major version với các package EF Core còn lại.

net11.0 tạm thời chưa được phát hành trong phiên bản 2.0.2.

Kiểm thử

Chạy từ thư mục project:

dotnet test src/SU2.EntityFrameworkCore.SqlServer.Hints.slnx

Test project mặc định dùng net9.0 và EF Core 9.0.0. Cần cài runtime .NET 9 để chạy test; có thể thay đổi runtime và phiên bản EF Core:

dotnet test src/SU2.EntityFrameworkCore.SqlServer.Hints.slnx -p:TestTargetFramework=net8.0 -p:EfCoreVersion=6.0.30
dotnet test src/SU2.EntityFrameworkCore.SqlServer.Hints.slnx -p:TestTargetFramework=net8.0 -p:EfCoreVersion=7.0.20
dotnet test src/SU2.EntityFrameworkCore.SqlServer.Hints.slnx -p:TestTargetFramework=net8.0 -p:EfCoreVersion=8.0.11
dotnet test src/SU2.EntityFrameworkCore.SqlServer.Hints.slnx -p:TestTargetFramework=net9.0 -p:EfCoreVersion=9.0.0
dotnet test src/SU2.EntityFrameworkCore.SqlServer.Hints.slnx -p:TestTargetFramework=net10.0 -p:EfCoreVersion=10.0.5

Các unit test kiểm tra SQL do EF Core sinh ra bằng ScriptDom mà không cần kết nối database. Integration test với SQL Server ưu tiên biến môi trường SQLSERVER_TEST_CONNECTION; nếu biến này thiếu hoặc rỗng thì dùng SU2_HINTS_SQLSERVER để tương thích cấu hình cũ. Nếu cả hai đều thiếu hoặc rỗng, test được bỏ qua. Khi đã export biến trong môi trường user mà tiến trình chạy test kế thừa, chỉ cần chạy:

dotnet test src/SU2.EntityFrameworkCore.SqlServer.Hints.slnx

Không lưu thông tin đăng nhập trong source control.

Khi parse SQL thất bại

Nếu parser không đọc được SQL đầu vào hoặc không parse lại được SQL do generator sinh ra, thư viện trả về đúng SQL đầu vào, không thêm hint và không ném NotSupportedException tại nhánh parse này. Nếu không truyền logger, thư viện ghi [Warning] System.NotSupportedException qua Console.Error (stderr), kèm phiên bản parser, mã lỗi và vị trí lỗi; không ghi nội dung SQL, literal hay thông báo parser có thể chứa dữ liệu nhạy cảm.

Kết quả fallback không được cache, nên lần thực thi sau vẫn parse lại và ghi cảnh báo nếu còn lỗi. SQL Server vẫn có thể báo lỗi nếu SQL đầu vào không hợp lệ; truy vấn không được bổ sung NOLOCK vẫn có thể bị block. Lỗi hint xung đột, CTE, nguồn bảng chưa hỗ trợ hoặc lệnh ghi vẫn bị từ chối như trước; đây không phải cơ chế bỏ qua mọi NotSupportedException.

Ghi cảnh báo qua ILogger hoặc Console

Không cần cấu hình thêm: UseNoLock() ghi cảnh báo parse vào Console.Error. Để dùng logging của ứng dụng, truyền logger từ DI:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

builder.Services.AddDbContext<AppDbContext>((serviceProvider, options) =>
{
    options.UseSqlServer(connectionString).UseNoLock(hints =>
    {
        hints.Logger = serviceProvider
            .GetRequiredService<ILogger<NoLockCommandInterceptor>>();
    });
});

Khi Logger khác null, thư viện gọi LogWarning kèm NotSupportedException (không throw) và không ghi thêm trực tiếp ra Console. Provider và bộ lọc log của ứng dụng quyết định đầu ra. Truyền NullLogger hoặc lọc bỏ Warning sẽ không kích hoạt fallback Console. Logger được giữ theo interceptor, không lưu trong cache SQL; ứng dụng quản lý vòng đời logger.

Thay đổi trong 2.0.2

  • Mặc định chuyển từ OffsetInsertion sang ScriptGenerator. Chọn rõ OffsetInsertion nếu cần giữ nguyên comment hoặc định dạng SQL.
  • Nhận marker chính xác -- SU2_NOLOCK trong token comment kể cả sau DECLARE; literal, identifier và block comment không kích hoạt hint. Marker áp dụng cho toàn command, không chỉ đoạn SQL sau marker.
  • Hỗ trợ batch khai báo biến scalar rồi một SELECT cuối cùng. Các bảng trong subquery của khai báo cũng đi qua kiểm tra/hint như bảng trong SELECT.
  • ToQueryString() có thể thêm DECLARE phục vụ debug; command EF thực thi thông thường truyền giá trị riêng qua parameters. Không cần chuyển sang raw SQL hay tự di chuyển tag để sử dụng paging/Contains.
  • Parse lỗi vẫn trả SQL gốc và ghi Warning qua ILogger hoặc Console; fallback không được cache.
Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.2 0 9/17/2026
2.0.1 42 9/16/2026

Add ApplyGlobally to apply NOLOCK to supported read-only SELECT commands without per-query AsNoLock or WithNoLock calls.