今天看到有園友寫了一篇關於添加NOLOCK查詢提示的博文《http://www.javashuo.com/article/p-tivaoyob-ge.html》,這裏呢,我將介紹另一種添加查詢提示的方法,此方式源於我看過源碼後的實現,孰好孰歹,請自行判之,接下來咱們一塊兒來看看。html
在EntityFramework中,如須要添加查詢提示須要自定義實現攔截器,但在EntityFramework Core中除了支持實現自定義攔截器外,還能夠經過繼承自對應類進行復寫,那就是QuerySqlGenerator類,存在於命名空間【Microsoft.EntityFrameworkCore.Query】,在此類經過咱們所寫的表達式實現全部查詢組合,好比咱們須要用到的對錶的設置,以下:sql
protected override Expression VisitTable(TableExpression tableExpression) { _relationalCommandBuilder .Append(_sqlGenerationHelper.DelimitIdentifier(tableExpression.Name, tableExpression.Schema)) .Append(AliasSeparator) .Append(_sqlGenerationHelper.DelimitIdentifier(tableExpression.Alias)); return tableExpression; }
同時咱們能夠看到還有另一個類SqlServerQuerySqlGenerator繼承自上述類,若咱們須要重寫的話繼承自此類便可,好比在此類中進一步重寫了三個表達式,咱們隨便看一個,以下:ide
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工廠類實例化,以下:ui
public class SqlServerQuerySqlGeneratorFactory : IQuerySqlGeneratorFactory { private readonly QuerySqlGeneratorDependencies _dependencies; public SqlServerQuerySqlGeneratorFactory(QuerySqlGeneratorDependencies dependencies) { _dependencies = dependencies; } public virtual QuerySqlGenerator Create() => new SqlServerQuerySqlGenerator(_dependencies); }
那麼上述Sql查詢工廠類到底具體是在何時被註冊的呢,以下已省略其餘註冊類:this
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查詢提示,以下:spa
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中默認的實現,以下:3d
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseLoggerFactory(loggerFactory) .UseSqlServer(@"Server=.;Database=EFCore;Trusted_Connection=True;") .ReplaceService<IQuerySqlGeneratorFactory, CustomSqlServerQuerySqlGeneratorFactory>();
到此就已經實現了添加NOLOCK查詢提示,對於此種實現方式一樣應該也適用於2.x版本,只不過稍微注意下對於自定義類構造函數參數可能略有不一樣,對於自定義實現,仍是寫成擴展方法比較好,這樣也方便統一管理,看我的諾,好比寫成以下:code
public static class CustomDbContextOptionsBuilderExtensions { public static DbContextOptionsBuilder UseCustomSqlServerQuerySqlGenerator(this DbContextOptionsBuilder optionsBuilder) { optionsBuilder.ReplaceService<IQuerySqlGeneratorFactory, CustomSqlServerQuerySqlGeneratorFactory>(); return optionsBuilder; } }
經過攔截器或者本節從源頭生成Sql語句時添加對錶的查詢提示皆可,到底哪個好呢?自行判斷吧,其餘就沒啥能夠進行總結的了,暫時到此爲止吧。