簡體   English   中英

linqtodb 在 asp.net 內核中使用 connection.BeginTransactionAsync

[英]linqtodb use connection.BeginTransactionAsync inside asp.net core

我在 asp.net 6.0 api 中使用 linqtodb 取得了巨大成功。但現在我正處於一個看起來我需要使用事務的地步,看起來我誤解了那里的一些事情。 我將 _connection object 作為服務中的注入 object

我得到的錯誤:

at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action\u00601 wrapCloseInAction)\r\n   at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action\u00601 wrapCloseInAction)\r\n   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock
...
...
"message":"Transaction (Process ID 56) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction."

有問題的代碼是:

...
    await _connection.BeginTransactionAsync(System.Data.IsolationLevel.Serializable);
                    sql = "SELECT ISNULL(MAX(CAST(Code AS INT)),0) FROM [@COR_DIA_AKM_LOTAT]";
                        var maxCode = await _connection.ExecuteAsync<int>(sql) + 1;
                        string newCode = maxCode.ToString("00000000");
                        sql = "INSERT INTO [@COR_DIA_AKM_LOTAT] (Code, Name, U_DocEntry, U_LineNum, U_LotNum, U_LotQty, U_ItemCode, U_Typ, U_DeletedF, U_LotCode) VALUES (@newCode, @newCode, @docEntry, @lineNum, @lot, @qty, @itemCode, 'F', 'N', '')";
                    await Task.Delay(10000);
                    await _connection.ExecuteAsync(sql,
                            new DataParameter { Name = "@newCode", Value = newCode },
                            new DataParameter { Name = "@qty", Value = l.qty },
                            new DataParameter { Name = "@docEntry", Value = updatePos.docEntry },
                            new DataParameter { Name = "@lineNum", Value = updatePos.lineNum },
                            new DataParameter { Name = "@itemCode", Value = updatePos.itemCode },
                            new DataParameter { Name = "@lot", Value = l.lot }
                            );
                    await _connection.CommitTransactionAsync();
...

因此,您可能會看到,需要先創建一個增量字母數字 ID(結構已給出,我無法更改),然后我將在插入中使用它。

所以我需要確保以上部分的並發使用會等待彼此完成

await Task.Delay(...) 就在那里,所以我可以測試並發使用

當我現在從 2 個單獨的客戶端執行此代碼時,來自第二個客戶端的第二次調用失敗並顯示上述消息

我考慮過但不適用的事情:

  • 使用存儲過程
  • 使用一個 sql 語句並從子查詢中獲取新的 id
  • 在應用程序中使用互斥鎖(我猜是個壞主意)

我對代碼的期望:

  • 第二個客戶端等待直到它可以獲得鎖。 這種等待將在我的 await...BeginTransaction() 中自動完成,對嗎?

這里是有問題的表的 model:

    [Table(Schema = "dbo", Name = "@COR_DIA_AKM_LOTAT")]
    public partial class @COR_DIA_AKM_LOTAT : IUDT
    {
        [PrimaryKey, NotNull] public string Code { get; set; }
        [Column, NotNull] public string Name { get; set; }

        [Column, NotNull] public int U_DocEntry { get; set; }

        [Column, NotNull] public int U_LineNum { get; set; }
        [Column] public decimal U_LotQty { get; set; }


        [Column] public string U_DeletedF { get; set; }
        [Column] public string U_LotNum { get; set; }

        [Column] public decimal U_PkgMandatoryQty { get; set; }
    }

任何啟示都受到高度贊賞

更新

我的誤解是我在想

...BeginTransaction(..)

已經以某種方式鎖定了表。 但是現在,在@SvyatoslavDanyliv 的更多閱讀和指示之后,我發現鎖定發生在查詢級別。 所以要獲得我想要的行為,我需要:

// read committed is fine because i just would like to lock -> execute queries -> commit 
 using var trans = await _connection.BeginTransactionAsync(System.Data.IsolationLevel.ReadCommitted);
// UPDLOCK is important here because it will start the lock
sql = @"SELECT ISNULL(MAX(CAST(Code AS INT)),0) FROM [@COR_DIA_AKM_LOTAT] WITH (UPDLOCK)";
var maxCode = await _connection.ExecuteAsync<int>(sql) + 1;
string newCode = maxCode.ToString("00000000");
sql = "INSERT INTO [@COR_DIA_AKM_LOTAT] (Code, Name, U_DocEntry, U_LineNum, U_LotNum, U_LotQty, U_ItemCode, U_Typ, U_DeletedF, U_LotCode) VALUES (@newCode, @newCode, @docEntry, @lineNum, @lot, @qty, @itemCode, 'F', 'N', '')";
await Task.Delay(4000);
await _connection.ExecuteAsync(sql, ...)
...more queries
await _connection.CommitTransactionAsync();

由於創建linq2db是為了最大程度地避免使用 Raw SQL,因此有一種方法可以在沒有事務的情況下插入此類記錄。

// tricky part, creating LINQ query which returns result set with calculated 'newCode'
var maxQuery = db.SelectQuery<string>(() =>
        Sql.ConvertTo<string>.From(
            db.GetTable<COR_DIA_AKM_LOTAT>().Max(x => Sql.ConvertTo<int?>.From(x.Code)) ?? 0 + 1)
    )
    .AsSubQuery()  // introducing subquery to help better PadLeft calculation
    .Select(x => Sql.PadLeft(x, 8, '0'))
    .AsSubQuery(); // additional subquery because value will be used twice

// inserting prepared value into destination table
maxQuery.Insert(db.GetTable<COR_DIA_AKM_LOTAT>(),
    newCode =>
        new COR_DIA_AKM_LOTAT
        {
            Code = newCode, 
            Name = newCode,
            U_DocEntry = updatePos.docEntry,
            U_LineNum = updatePos.lineNum,
            U_LotNum = l.lot,
            U_LotQty = l.qty,
            U_ItemCode = updatePos.itemCode,
            U_Typ = "F",
            U_DeletedF = "N",
            U_LotCode = ""
        });

查詢應生成以下 SQL(為成功執行測試而更改了一些值):

INSERT INTO [dbo].[@COR_DIA_AKM_LOTAT]
(
    [Code],
    [Name],
    [U_DocEntry],
    [U_LineNum],
    [U_LotNum],
    [U_LotQty],
    [U_DeletedF],
    [U_PkgMandatoryQty]
)
SELECT
    [t1].[c1],
    [t1].[c1],
    1,
    1,
    N'1',
    1,
    N'N',
    1
FROM
    (
        SELECT
            IIF(Len([x_1].[c1]) > 8, [x_1].[c1], Replicate(N'0', 8 - Len([x_1].[c1])) + [x_1].[c1]) as [c1]
        FROM
            (
                SELECT
                    Convert(NVarChar(11), Coalesce((
                        SELECT
                            Max(Convert(Int, [x].[Code]))
                        FROM
                            [dbo].[@COR_DIA_AKM_LOTAT] [x]
                    ), 1)) as [c1]
            ) [x_1]
    ) [t1]

無論如何最好考慮包含最后增量值的特殊表。 因為通過字符串字段計算 Max 不是性能的好選擇。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM