如何在 EF Core 中实现 Select For Update

pet*_*ter 6 postgresql entity-framework npgsql entity-framework-core

据我所知,EF(和 EF Core)中没有选项可以显式锁定我正在查询的资源,但我会经常需要这个功能,并且真的不想回到写作每次我需要它时都选择语句。

因为我只需要它用于 postgres 并且根据规范 FOR UPDATE是查询中的最后一项,所以我想到实现它的最简单的方法是获取如下所述的 select 语句:在 Linq to Entities 中,您能否将 IQueryable 转换为字符串SQL?并附加FOR UPDATE并直接执行它。然而,这要么给我一个带有参数占位符的查询,要么不是一个准备好的查询,这意味着执行计划的缓存不会真正在 postgres 上工作,所以无论哪种方式都行不通。

Linq to SQL 有这个方法,DataContext.GetCommand但在 EF 和特别是 EF Core 中似乎没有任何等效的东西。我还查看了 EntityFramework.Extended 及其批量更新/删除,但由于他们必须将 select 语句转换为不同的语句,因此他们需要处理比我复杂得多的问题,因此我希望有一个更简单的解决方案。

更新:

如果描述不清楚,我想创建一个这样的扩展方法:

public static IList<T> ForUpdate (this IQueryable<T> me)
{
    // this line is obviously what is missing for me :)
    var theUnderlyingCommand = me.GetTheUnderlyingDbCommandOrSimilar();

    theUnderlyingCommand.Text += "FOR UPDATE";
    return me.ToList();
}
Run Code Online (Sandbox Code Playgroud)

这样,其他开发人员可以像所有其他过程一样通过 Linq 使用 EF,而不是运行.ToList()他们会运行.ForUpdate(). (为了更新故意执行查询以使实现更容易,也因为FOR UPDATE是 postgres 支持的最后一个选项,之后不应该再有其他任何东西了)

Gus*_*ler 7

这项工作适合我使用 SQLServer(没有经过测试的异步方法):

首先,创建一个 DbCommandInterceptor(我叫 HintInterceptor.cs)

using System;
using System.Data.Common;
using System.Data.Entity.Infrastructure.Interception;
using System.Text.RegularExpressions;

public class HintInterceptor : DbCommandInterceptor
{
    private static readonly Regex _tableAliasRegex = new Regex(@"(?<tableAlias>FROM +(\[.*\]\.)?(\[.*\]) AS (\[.*\])(?! WITH \(*HINT*\)))", RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.Compiled);

    [ThreadStatic]
    public static string HintValue;

    private static string Replace(string input)
    {
        if (!String.IsNullOrWhiteSpace(HintValue))
        {
            if (!_tableAliasRegex.IsMatch(input))
            {
                throw new InvalidProgramException("Não foi possível identificar uma tabela para ser marcada para atualização(forupdate)!", new Exception(input));
            }
            input = _tableAliasRegex.Replace(input, "${tableAlias} WITH (*HINT*)");
            input = input.Replace("*HINT*", HintValue);
        }
        HintValue = String.Empty;
        return input;
    }

    public override void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
    {
        command.CommandText = Replace(command.CommandText);
    }

    public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        command.CommandText = Replace(command.CommandText);
    }
}
Run Code Online (Sandbox Code Playgroud)

所以进入 Web.config 注册你的拦截器类

<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
<providers>
  <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
</providers>
<interceptors> 
  <interceptor type="Full.Path.Of.Class.HintInterceptor, Dll.Name" />
</interceptors>
</entityFramework>
Run Code Online (Sandbox Code Playgroud)

现在我创建一个名为 HintExtension 的静态类

public static class HintExtension
{
    public static IQueryable<T> WithHint<T>(this IQueryable<T> set, string hint) where T : class
    {
        HintInterceptor.HintValue = hint;
        return set;
    }
    public static IQueryable<T> ForUpdate<T>(this IQueryable<T> set) where T : class
    {
        return set.WithHint("UPDLOCK");
    }
}
Run Code Online (Sandbox Code Playgroud)

这就是全部,我可以在数据库事务中使用,例如:

using(var trans = context.Database.BeginTransaction())
{
        var query = context.mydbset.Where(a => a.name == "asd").ForUpdate();
        // not locked yet
        var mylist = query.ToList();
        // now are locked for update
        // update the props, call saveChanges() and finally call commit ( or rollback)
        trans.Commit();
        // now are unlocked
}
Run Code Online (Sandbox Code Playgroud)

对不起我的英语,我希望我的例子会有所帮助。