我一直试图将我们的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中,我们可以采用几乎相同的方式进行拦截。但是由于更改表的命名约定,我无法为新表编写正则表达式。您可以在下面找到新的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中执行相同的操作?
答案 0 :(得分:4)
这种拦截方法对我来说并不好。 IMO的一种更好的方法是连接到EF Core基础结构,以使用覆盖VisitTable
方法的自定义实现替换SqlServer的IQuerySqlGenerator服务实现,如下所示:
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();
答案 1 :(得分:0)
等效的拦截器可以通过连接到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());