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
<PackageReference Include="SU2.EntityFrameworkCore.SqlServer.Hints" Version="2.0.2" />
<PackageVersion Include="SU2.EntityFrameworkCore.SqlServer.Hints" Version="2.0.2" />
<PackageReference Include="SU2.EntityFrameworkCore.SqlServer.Hints" />
paket add SU2.EntityFrameworkCore.SqlServer.Hints --version 2.0.2
#r "nuget: SU2.EntityFrameworkCore.SqlServer.Hints, 2.0.2"
#:package SU2.EntityFrameworkCore.SqlServer.Hints@2.0.2
#addin nuget:?package=SU2.EntityFrameworkCore.SqlServer.Hints&version=2.0.2
#tool nuget:?package=SU2.EntityFrameworkCore.SqlServer.Hints&version=2.0.2
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.
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() và 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ằngSql150ScriptGenerator,Sql160ScriptGeneratorhoặcSql170ScriptGeneratortươ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; markerSU2_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ènWITH (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
MemoryCacheriê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,
DbCommandhay 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, TSql160Parser và TSql170Parser. 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_NOLOCKsẽ bật hint cho toàn bộ câu lệnh SQL, bao gồm bảng trongJOINvà subquery. - Chuỗi dữ liệu, tên bảng hoặc block comment có chứa
SU2_NOLOCKkhông kích hoạt hint ngoài ý muốn. - SQL được phân tích bằng
Microsoft.SqlServer.TransactSql.ScriptDomphiên bản180.107.0. Chế độOffsetInsertiongiữ nguyên literal, comment, parameter và định dạng;ScriptGeneratorsinh 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ấnContainscủa EF Core 8/9) và table-valued function được giữ nguyên, không gắnWITH (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/FREETEXTTABLEvà 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ồmBULK) được giữ nguyên. Thư viện không sửa SQL trong chuỗi truy vấn remote, không áp dụngNOLOCKcho 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émNotSupportedExceptionthay vì âm thầm bỏ qua. - Hỗ trợ một câu
SELECTdạng text, có thể có các khai báo biến scalarDECLAREphí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ặcWithNoLock()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ệ; khiApplyGlobally = 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.0net7.0net8.0net9.0net10.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ừ
OffsetInsertionsangScriptGenerator. Chọn rõOffsetInsertionnếu cần giữ nguyên comment hoặc định dạng SQL. - Nhận marker chính xác
-- SU2_NOLOCKtrong token comment kể cả sauDECLARE; 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 | Versions 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. |
-
net10.0
- Microsoft.EntityFrameworkCore.Relational (>= 6.0.30)
- Microsoft.Extensions.Caching.Memory (>= 6.0.3)
- Microsoft.SqlServer.TransactSql.ScriptDom (>= 180.107.0)
-
net6.0
- Microsoft.EntityFrameworkCore.Relational (>= 6.0.30)
- Microsoft.Extensions.Caching.Memory (>= 6.0.3)
- Microsoft.SqlServer.TransactSql.ScriptDom (>= 180.107.0)
-
net7.0
- Microsoft.EntityFrameworkCore.Relational (>= 6.0.30)
- Microsoft.Extensions.Caching.Memory (>= 6.0.3)
- Microsoft.SqlServer.TransactSql.ScriptDom (>= 180.107.0)
-
net8.0
- Microsoft.EntityFrameworkCore.Relational (>= 6.0.30)
- Microsoft.Extensions.Caching.Memory (>= 6.0.3)
- Microsoft.SqlServer.TransactSql.ScriptDom (>= 180.107.0)
-
net9.0
- Microsoft.EntityFrameworkCore.Relational (>= 6.0.30)
- Microsoft.Extensions.Caching.Memory (>= 6.0.3)
- Microsoft.SqlServer.TransactSql.ScriptDom (>= 180.107.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Add ApplyGlobally to apply NOLOCK to supported read-only SELECT commands without per-query AsNoLock or WithNoLock calls.