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