Ho cercato di trasferire il nostro progetto EF6 su EF-Core-2.0.
In EF6, stavamo usando l' intercettatore di DbNolock per aggiungere il suggerimento con (NOLOCK) a quali query vogliamo. È possibile trovare il mio codice intercettore Db ex-running qui sotto.
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) ");
}
}
In Ef-Core, possiamo fare l'intercettazione quasi allo stesso modo. Ma a causa della modifica della convenzione di denominazione delle tabelle , non potevo scrivere un Regex per il nuovo. Puoi trovare la nuova versione di Ef-Core qui sotto:
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 generato:
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 Generated 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]
Puoi anche dare un'occhiata a questo problema github per maggiori dettagli .
Voglio sostituire AS [t] con AS [t] WITH (NOLOCK) e AS [t.Member] con AS [t.Member] WITH (NOLOCK)
Quale modello posso usare per fare lo stesso in Ef-Core?
Questo metodo di intercettazione non mi sembra buono. Un modo migliore in cui l'IMO è collegarsi all'infrastruttura EF Core per sostituire l' implementazione del servizio IQuerySqlGenerator per SqlServer con l'implementazione personalizzata che ha la VisitTable
metodo VisitTable
questo modo:
public override Expression VisitTable(TableExpression tableExpression)
{
// base will append schema, table and alias
var result = base.VisitTable(tableExpression);
Sql.Append(" WITH (NOLOCK)");
return result;
}
L'aggancio è un po 'complicato perché dobbiamo creare e sostituire il servizio "factory" per poter sostituire il generatore sql. Il codice completo per tutto ciò, insieme al metodo di estensione dell'helper è il seguente:
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;
}
}
}
Abbastanza un po 'di codice per l'aggiunta di una sola riga significativa, ma il vantaggio è che lo fa nel modo in cui EF Core lo farebbe probabilmente nel caso in cui ci fosse tale opzione di query.
Ad ogni modo, con il codice sopra è sufficiente attivarlo dal proprio contesto OnConfiguring
override:
optionsBuilder.UseCustomSqlServerQuerySqlGenerator();
L'equivalente dell'intercettore può essere eseguito agganciando l'infrastruttura DiagnosticSource
.
Per prima cosa crea un intercettore:
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
}
}
}
Quindi, crea un listener globale per la diagnostica EF. Qualcosa di simile a:
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);
}
}
}
E registralo come parte dell'avvio dell'applicazione
DiagnosticListener.AllListeners.Subscribe(new EfGlobalListener());