今天看到有园友写了一篇关于添加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语句时添加对表的查询提示皆可,到底哪个好呢?自行判断吧,其余就没啥能够进行总结的了,暂时到此为止吧。