前言
今天看到有園友寫了一篇關於添加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語句時添加對表的查詢提示皆可,到底哪一個好呢?自行判斷吧,其他就沒啥可以進行總結的了,暫時到此為止吧。