前言
今天看到有園友寫了一篇關于添加NOLOCK查詢提示的博文《https://www.cnblogs.com/weihanli/p/12623934.html》,這裡呢,我将介紹另外一種添加查詢提示的方法,此方式源于我看過源碼後的實作,孰好孰歹,請自行判之,接下來我們一起來看看。
查詢提示(NOLOCK)
在EntityFramework中,如需要添加查詢提示需要自定義實作攔截器,但在EntityFramework Core中除了支援實作自定義攔截器外,還可以通過繼承自對應類進行複寫,那就是QuerySqlGenerator類,存在于命名空間【Microsoft.EntityFrameworkCore.Query】,在此類通過我們所寫的表達式實作所有查詢組合,比如我們需要用到的對表的設定,如下:
protected override Expression VisitTable(TableExpression tableExpression)
{
_relationalCommandBuilder
.Append(_sqlGenerationHelper.DelimitIdentifier(tableExpression.Name, tableExpression.Schema))
.Append(AliasSeparator)
.Append(_sqlGenerationHelper.DelimitIdentifier(tableExpression.Alias));
return tableExpression;
}
同時我們可以看到還有另外一個類SqlServerQuerySqlGenerator繼承自上述類,若我們需要重寫的話繼承自此類即可,比如在此類中進一步重寫了三個表達式,我們随便看一個,如下:
protected override void GenerateTop(SelectExpression selectExpression)
{
if (selectExpression.Limit != null
&& selectExpression.Offset == null)
{
Sql.Append("TOP(");
Visit(selectExpression.Limit);
Sql.Append(") ");
}
}
上述意在表明:當我們進行在記憶體中通過Skip和Take進行分頁時,因為Skip會翻譯成Offset,而Take會翻譯成Limit,若我們直接跳過Skip而寫Take,此時在生成的Sql語句中添加TOP,很顯然這是合情合理而且合法的。舉個栗子,如下:
var context = new EFCoreDbContext();
context.Database.EnsureCreated();
var blogs = context.Blogs.Take(3).ToList();

那麼此類是何時進行執行個體化的呢?通過SqlServerQuerySqlGeneratorFactory工廠類執行個體化,如下:
public class SqlServerQuerySqlGeneratorFactory : IQuerySqlGeneratorFactory
{
private readonly QuerySqlGeneratorDependencies _dependencies;
public SqlServerQuerySqlGeneratorFactory(QuerySqlGeneratorDependencies dependencies)
{
_dependencies = dependencies;
}
public virtual QuerySqlGenerator Create()
=> new SqlServerQuerySqlGenerator(_dependencies);
}
那麼上述Sql查詢工廠類到底具體是在什麼時候被注冊的呢,如下已省略其他注冊類:
public static IServiceCollection AddEntityFrameworkSqlServer([NotNull] this IServiceCollection serviceCollection)
{
Check.NotNull(serviceCollection, nameof(serviceCollection));
var builder = new EntityFrameworkRelationalServicesBuilder(serviceCollection)
// New Query Pipeline
.TryAdd<IQuerySqlGeneratorFactory, SqlServerQuerySqlGeneratorFactory>()
builder.TryAddCoreServices();
return serviceCollection;
}
通過上述AddEntityFrameworkSqlServer名稱可猜測該方法肯定是在執行個體化上下文時注冊所有需要用到的接口具體實作,有了這個就好辦了,為了不破壞原有的實作,我們自定義Sql查詢生成類并繼承自SqlServerQuerySqlGenerator并重寫對表的設定并添加NOLOCK查詢提示,如下:
public class CustomSqlServerQuerySqlGenerator : SqlServerQuerySqlGenerator
{
public CustomSqlServerQuerySqlGenerator(QuerySqlGeneratorDependencies dependencies)
: base(dependencies) { }
protected override Expression VisitTable(TableExpression tableExpression)
{
var result = base.VisitTable(tableExpression);
Sql.Append(" WITH (NOLOCK)");
return result;
}
}
接下來我們則需要實作自定義查詢工廠并繼承自預設提供的查詢工廠類進而執行個體化上述自定義的查詢類,如下:
public class CustomSqlServerQuerySqlGeneratorFactory : SqlServerQuerySqlGeneratorFactory
{
private readonly QuerySqlGeneratorDependencies _dependencies;
public CustomSqlServerQuerySqlGeneratorFactory(QuerySqlGeneratorDependencies dependencies)
: base(dependencies)
{
_dependencies = dependencies;
}
public override QuerySqlGenerator Create() =>
new CustomSqlServerQuerySqlGenerator(_dependencies);
}
那我們如何将預設提供的查詢工廠類替換為上述自定義查詢工廠類呢?稍微對DbContextOptionsBuilder類有所了解的童鞋應該知道,在該類中提供了ReplaceService方法來給我們替換EF Core中預設的實作,如下:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseLoggerFactory(loggerFactory)
.UseSqlServer(@"Server=.;Database=EFCore;Trusted_Connection=True;")
.ReplaceService<IQuerySqlGeneratorFactory, CustomSqlServerQuerySqlGeneratorFactory>();
到此就已經實作了添加NOLOCK查詢提示,對于此種實作方式同樣應該也适用于2.x版本,隻不過稍微注意下對于自定義類構造函數參數可能略有不同,對于自定義實作,還是寫成擴充方法比較好,這樣也友善統一管理,看個人諾,比如寫成如下:
public static class CustomDbContextOptionsBuilderExtensions
{
public static DbContextOptionsBuilder UseCustomSqlServerQuerySqlGenerator(this DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.ReplaceService<IQuerySqlGeneratorFactory, CustomSqlServerQuerySqlGeneratorFactory>();
return optionsBuilder;
}
}
總結
通過攔截器或者本節從源頭生成Sql語句時添加對表的查詢提示皆可,到底哪一個好呢?自行判斷吧,其他就沒啥可以進行總結的了,暫時到此為止吧。
你所看到的并非事物本身,而是經過诠釋後所賦予的意義