Ef-Core - какое регулярное выражение я могу использовать для замены имен таблиц на nolock в Db Interceptor - PullRequest
0 голосов
/ 31 октября 2018

Я пытался перенести наш проект EF6 на EF-Core-2.0.

В EF6 мы использовали DbNolock перехватчик для добавления с (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 мы можем сделать перехват почти таким же образом. Но из-за изменения соглашения об именах таблиц я не смог написать Regex для новой. Вы можете найти новую версию 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]

SQL, сгенерированный Ef-Core:

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

Вы также можете посмотреть эту проблему с github для более подробной информации .

хочу заменить AS [t] с AS [t] С (NOLOCK) и AS [t.Member] с AS [t.Member] С (NOLOCK)

Какой шаблон я могу использовать, чтобы сделать то же самое в Ef-Core?

Ответы [ 2 ]

0 голосов
/ 31 октября 2018

Этот метод перехвата мне не нравится. Более эффективные способы 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;
}

Подключение немного сложнее, потому что нам нужно создать и заменить "фабричный" сервис, чтобы иметь возможность заменить генератор sql. Полный код для всего этого, вместе с вспомогательным методом расширения, выглядит следующим образом:

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 переопределить:

optionsBuilder.UseCustomSqlServerQuerySqlGenerator();
0 голосов
/ 31 октября 2018

Эквивалент перехватчика можно сделать, подключившись к инфраструктуре DiagnosticSource.

Сначала создайте перехватчик:

public class NoLockInterceptor : IObserver<KeyValuePair<string, object>>
{
    public void OnCompleted()
    {
    }

    public void OnError(Exception error)
    {
    }

    public void OnNext(KeyValuePair<string, object> value)
    {
        if (value.Key == RelationalEventId.CommandExecuting.Name)
        {
            var command = ((CommandEventData)value.Value).Command;

            // Do command.CommandText manipulation here
        }
    }
}

Затем создайте глобальный прослушиватель для диагностики EF. Что-то вроде:

public class EfGlobalListener : IObserver<DiagnosticListener>
{
    private readonly NoLockInterceptor _noLockInterceptor = new NoLockInterceptor();

    public void OnCompleted()
    {
    }

    public void OnError(Exception error)
    {
    }

    public void OnNext(DiagnosticListener listener)
    {    
        if (listener.Name == DbLoggerCategory.Name)
        {
            listener.Subscribe(_noLockInterceptor);
        }
    }
}

И зарегистрируйте это как часть запуска приложения

DiagnosticListener.AllListeners.Subscribe(new EfGlobalListener());
...