@gustavo-rossi-muller的回答很有用,但缺乏线程安全性,因此无法与EF Core提供的async方法一起使用,例如DbContext.SaveChangesAsync()
,因为没有覆盖ScalarExecutingAsync()
和ReaderExecutingAsync()
。
在公共静态字段HintValue
上使用[ThreadStatic]
属性是必要的,以强制每个线程使用自己的HintInterceptor.HintValue
变体值,而不是在所有线程(即全局变量)之间共享相同的值。
拦截器文档已经解决了这个问题:
拦截器通常是无状态的,这意味着单个拦截器实例可用于所有DbContext实例。
如果你想在每个DbContext
的拦截器实例中保留一些状态,你需要:
此拦截器具有状态:它存储最近查询的每日消息的ID和消息文本,以及执行该查询的时间。由于此状态,我们还需要锁定,因为缓存要求相同的拦截器必须由多个上下文实例使用。
但我们需要的是对每个查询命令控制拦截器的状态,因为我们只需要某些特定的SELECT
命令使用FOR UPDATE
后缀进行查询,而不是所有导致许多语法错误的命令。
到目前为止,我们只能通过TagWith()
向某些查询命令提供一些额外信息,然后在DbCommandInterceptor
的覆盖中检测标记添加的注释,为这些查询追加FOR UPDATE
提示。
事实上,文档已经提供了一个示例来完成此操作。
我已经修改了那个示例,使用MySQL语法附加FOR UPDATE
:
private class SelectForUpdateCommandInterceptor : DbCommandInterceptor
{
public override InterceptionResult<object> ScalarExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<object> result)
{
ManipulateCommand(command);
return result;
}
public override ValueTask<InterceptionResult<object>> ScalarExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<object> result, CancellationToken cancellationToken = default)
{
ManipulateCommand(command);
return new(result);
}
public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
{
ManipulateCommand(command);
return result;
}
public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result, CancellationToken cancellationToken = default)
{
ManipulateCommand(command);
return new(result);
}
private static void ManipulateCommand(IDbCommand command)
{
if (command.CommandText.StartsWith("-- ForUpdate", StringComparison.Ordinal))
{
command.CommandText += " FOR UPDATE";
}
}
}
然后在配置您的DbContext时注入此拦截器:
private static readonly SelectForUpdateCommandInterceptor SelectForUpdateCommandInterceptorInstance = new();
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.AddInterceptors(SelectForUpdateCommandInterceptorInstance);
}
最后,我们可以这样做:
var results = (from e in db.Set<SomeEntity>.TagWith("ForUpdate")
where e.SomeField == someValue
select e.SomeField).ToList();
db.Set<SomeEntity>.Add(new SomeEntity {SomeField = 1});
db.SaveChanges();
db.SaveChangesAsync();
直到 EF Core 8,他们仍然没有计划实现这个查询提示后缀:https://github.com/dotnet/efcore/issues/26042,但另一个名为 linq2db
的 linq2sql 表达式转换器已经完成了此操作:
https://github.com/linq2db/linq2db/issues/1276
https://github.com/linq2db/linq2db/pull/3297
https://github.com/linq2db/linq2db/issues/3905
引用
FOR UPDATE
,EFCore提供了FromSql
方法,或者您可以转到ADO.NET并做任何您想要的事情(这是一篇好文章:http://www.elanderson.net/2016/04/execute-raw-sql-in-entity-framework-core/)。 - Shay Rojansky