c# - Ef-Core - 我可以使用什么正则表达式在 Db 拦截器中用 nolock 替换表名

标签 c# regex entity-framework .net-core entity-framework-core

我一直在尝试将我们的 EF6 项目移植到 EF-Core-2.0。

在 EF6 中,我们使用 DbNolock 拦截器来添加 With (NOLOCK) 提示我们需要哪些查询。您可以在下面找到我之前运行的 Db 拦截器代码。

   public class DbNoLockInterceptor : DbCommandInterceptor
    {
    private static readonly Regex TableAliasRegex = new Regex(@"((?<!\){1,5})AS \[Extent\d+\](?! WITH \(NOLOCK\)))", RegexOptions.Multiline | RegexOptions.IgnoreCase);

    public override void ScalarExecuting(DbCommand command,
        DbCommandInterceptionContext<object> interceptionContext)
    {
        command.CommandText =
            TableAliasRegex.Replace(command.CommandText, mt => mt.Groups[0].Value + " WITH (NOLOCK) ");
    }

    public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        command.CommandText = TableAliasRegex.Replace(command.CommandText,  mt => mt.Groups[0].Value + " WITH (NOLOCK) ");
    }
} 

在 Ef-Core 中,我们可以用几乎相同的方式进行拦截。但是由于更改表的命名约定,我无法为新表编写正则表达式。您可以在下面找到新的 Ef-Core 版本:

public class DbNoLockListener
{
    private static readonly Regex TableAliasRegex = new Regex(@"((?<!\){1,5})AS \[Extent\d+\](?! WITH \(NOLOCK\)))", RegexOptions.Multiline | RegexOptions.IgnoreCase);
    [DiagnosticName("Microsoft.EntityFrameworkCore.Database.Command.CommandExecuting")]
    public void OnCommandExecuting(DbCommand command, DbCommandMethod executeMethod, Guid commandId, Guid connectionId, bool async, DateTimeOffset startTime)
    {
        command.CommandText =
                        TableAliasRegex.Replace(command.CommandText, mt => mt.Groups[0].Value + " WITH (NOLOCK) ");
    }
}

Ef6 生成的 SQL:

SELECT
    [Extent1].[Id] AS [Extent1Id], 
    [Extent2].[Id] AS [Extent2Id]
    FROM [Advert].[Advert]  AS [Extent1]
    INNER JOIN [Membership].[Members] AS [Extent2] ON [Extent1].[MemberId] = [Extent2].[MemberId]

Ef-Core 生成的 SQL:

SELECT 
     [t].[Id]
    ,[t.Member].[Id]
FROM [Advert].[Advert] AS [t]
INNER JOIN [Membership].[Members] AS [t.Member] ON [t].[MemberId] = [t.Member].[MemberId]

你也可以看看this github issue for more detail .

我要换 AS [t]AS [t] WITH (NOLOCK)AS [t.Member]AS [t.Member] WITH (NOLOCK)

我可以使用哪种模式在 Ef-Core 中执行相同的操作?

最佳答案

这种拦截方式我看不太好。 IMO 更好的方法是连接到 EF Core 基础设施以替换 IQuerySqlGenerator SqlServer 的服务实现,自定义实现覆盖 VisitTable 方法,如下所示:

public override Expression VisitTable(TableExpression tableExpression)
{
    // base will append schema, table and alias
    var result = base.VisitTable(tableExpression);
    Sql.Append(" WITH (NOLOCK)");
    return result;
}

Hook 有点复杂,因为我们需要创建和替换“工厂”服务,以便能够替换 sql 生成器。所有这些的完整代码以及辅助扩展方法如下:

EF 核心 7.0:

using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.SqlServer.Query.Sql.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Query.Internal;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage;

namespace Microsoft.EntityFrameworkCore
{
    public static partial class CustomDbContextOptionsBuilderExtensions
    {
        public static DbContextOptionsBuilder UseCustomSqlServerQuerySqlGenerator(this DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.ReplaceService<IQuerySqlGeneratorFactory, CustomSqlServerQuerySqlGeneratorFactory>();
            return optionsBuilder;
        }
    }
}

namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Sql.Internal
{
    class CustomSqlServerQuerySqlGeneratorFactory : IQuerySqlGeneratorFactory
    {
        public CustomSqlServerQuerySqlGeneratorFactory(QuerySqlGeneratorDependencies dependencies, IRelationalTypeMappingSource typeMappingSource)
            => (Dependencies, TypeMappingSource) = (dependencies, typeMappingSource);
        public QuerySqlGeneratorDependencies Dependencies { get; }
        public IRelationalTypeMappingSource TypeMappingSource { get; }
        public QuerySqlGenerator Create() => new CustomSqlServerQuerySqlGenerator(Dependencies, TypeMappingSource);
    }

    public class CustomSqlServerQuerySqlGenerator : SqlServerQuerySqlGenerator
    {
        public CustomSqlServerQuerySqlGenerator(QuerySqlGeneratorDependencies dependencies, IRelationalTypeMappingSource typeMappingSource)
            : base(dependencies, typeMappingSource) { }
        protected override Expression VisitTable(TableExpression tableExpression)
        {
            // base will append schema, table and alias
            var result = base.VisitTable(tableExpression);
            Sql.Append(" WITH (NOLOCK)");
            return result;
        }
    }
}

EF 核心 3.x:

using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.SqlServer.Query.Sql.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Query.Internal;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;

namespace Microsoft.EntityFrameworkCore
{
    public static class CustomDbContextOptionsBuilderExtensions
    {
        public static DbContextOptionsBuilder UseCustomSqlServerQuerySqlGenerator(this DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.ReplaceService<IQuerySqlGeneratorFactory, CustomSqlServerQuerySqlGeneratorFactory>();
            return optionsBuilder;
        }
    }
}

namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Sql.Internal
{
    class CustomSqlServerQuerySqlGeneratorFactory : IQuerySqlGeneratorFactory
    {
        public CustomSqlServerQuerySqlGeneratorFactory(QuerySqlGeneratorDependencies dependencies)
            => Dependencies = dependencies;
        public QuerySqlGeneratorDependencies Dependencies { get; }
        public QuerySqlGenerator Create() => new CustomSqlServerQuerySqlGenerator(Dependencies);
    }

    public class CustomSqlServerQuerySqlGenerator : SqlServerQuerySqlGenerator
    {
        public CustomSqlServerQuerySqlGenerator(QuerySqlGeneratorDependencies dependencies)
            : base(dependencies) { }
        protected override Expression VisitTable(TableExpression tableExpression)
        {
            // base will append schema, table and alias
            var result = base.VisitTable(tableExpression);
            Sql.Append(" WITH (NOLOCK)");
            return result;
        }
    }
}

EF 核心 2.x:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore.Query.Expressions;
using Microsoft.EntityFrameworkCore.Query.Sql;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Query.Sql.Internal;

namespace Microsoft.EntityFrameworkCore
{
    public static class CustomDbContextOptionsBuilderExtensions
    {
        public static DbContextOptionsBuilder UseCustomSqlServerQuerySqlGenerator(this DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.ReplaceService<IQuerySqlGeneratorFactory, CustomSqlServerQuerySqlGeneratorFactory>();
            return optionsBuilder;
        }
    }
}

namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Sql.Internal
{
    class CustomSqlServerQuerySqlGeneratorFactory : SqlServerQuerySqlGeneratorFactory
    {
        private readonly ISqlServerOptions sqlServerOptions;
        public CustomSqlServerQuerySqlGeneratorFactory(QuerySqlGeneratorDependencies dependencies, ISqlServerOptions sqlServerOptions)
            : base(dependencies, sqlServerOptions) => this.sqlServerOptions = sqlServerOptions;
        public override IQuerySqlGenerator CreateDefault(SelectExpression selectExpression) =>
            new CustomSqlServerQuerySqlGenerator(Dependencies, selectExpression, sqlServerOptions.RowNumberPagingEnabled);
    }

    public class CustomSqlServerQuerySqlGenerator : SqlServerQuerySqlGenerator
    {
        public CustomSqlServerQuerySqlGenerator(QuerySqlGeneratorDependencies dependencies, SelectExpression selectExpression, bool rowNumberPagingEnabled)
            : base(dependencies, selectExpression, rowNumberPagingEnabled) { }
        public override Expression VisitTable(TableExpression tableExpression)
        {
            // base will append schema, table and alias
            var result = base.VisitTable(tableExpression);
            Sql.Append(" WITH (NOLOCK)");
            return result;
        }
    }
}

相当多的代码仅用于添加一个有意义的行,但这样做的好处是,如果存在此类查询选项,它的执行方式可能与 EF Core 的执行方式相同。

无论如何,使用上面的代码,您只需要从您的上下文OnConfiguring override 中激活它:

optionsBuilder.UseCustomSqlServerQuerySqlGenerator();

关于c# - Ef-Core - 我可以使用什么正则表达式在 Db 拦截器中用 nolock 替换表名,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53078435/

相关文章:

c# - Entity Framework 和动态模式

mysql - 更正此ERD这些部分的最有效方法是什么?

c# - DataGridView 数据源不更新

c# 检测窗口真正调整大小而不是移动

java - 正则表达式 : Insert space in the string after a matched pattern

python - 如何使用正则表达式创建特定的虚拟变量?

c# - 如何存储数据库密码

c# - 如果服务器不是本地,则无法打开连接

regex - 如何在 shell 中使用正则捕获这个特定的字符串?

c# - 如何解决 "The method ' Skip' 仅支持 LINQ to Entities 中的排序输入。”