1. 程式人生 > >EntityFramework Core 3.x新增查詢提示(NOLOCK)

EntityFramework Core 3.x新增查詢提示(NOLOCK)

前言

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