如何在EF Core中實現Select For Update

entity-framework entity-framework-core npgsql postgresql

據我所知,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();
}

這樣,其他開發人員可以通過Linq使用EF和所有其他過程一樣,而不是運行.ToList()他們運行.ForUpdate() 。 (對於Update,故意執行查詢以使實現更容易,並且因為FOR UPDATE是postgres支持的最後一個選項,之後不再有任何其他選項)

熱門答案

這項工作適合我使用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);
    }
}

所以進入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>

現在我創建一個名為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");
        }
    }

這就是全部,我可以在數據庫事務中使用,如:

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
}

對不起我的英語,我希望我的例子會有所幫助。



Related

許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因
許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因