2

LINQ to SQL を使用する ASP.NET MVC 2 サイトを構築しています。私のサイトが DB にアクセスする場所の 1 つで、競合状態が発生する可能性があると思います。


DB アーキテクチャ

という名前の関連する DB テーブルの列の一部を次に示しますRevisions

  • RevisionID - bigint、IDENTITY、PK
  • PostID - bigint、Postsテーブルの FK から PK
  • EditNumber - 整数
  • RevisionText - nvarchar(最大)

私のサイトでは、ユーザーは投稿を送信し、後で投稿を編集できます。元の投稿者以外のユーザーが投稿を編集できるため、1 つの投稿で複数の編集を同時に行うことができます。

投稿を送信すると、Postsテーブル内のレコードが作成され、PostIDがレコードの ID に設定され、 RevisionTextが投稿テキストに設定され、EditNumberRevisionsが 1 に設定されたテーブル内のレコードが作成されます 。Posts

投稿を編集すると、Revisionsレコードのみが作成され、EditNumber最新の編集番号よりも 1 大きい値に設定されます

したがって、EditNumber列は、投稿が編集された回数を示します。


EditNumber のインクリメント

これらの関数を実装する上での課題は、EditNumber列をインクリメントすることです。その列をIDENTITYにすることはできないため、その値を手動で操作する必要があります。

新しいリビジョンに必要なEditNumberを決定するための LINQ クエリを次に示します。

using(var db = new DBDataContext())
{
    var rev = new Revision();
    rev.EditNumber = db.Revisions.Where(r => r.PostID == postID).Max(r => r.EditNumber) + 1;

    // ... (fill other properties)

    db.Revisions.InsertOnSubmit(rev);
    db.SubmitChanges();
}

最大値を計算して増加させると、競合状態が発生する可能性があります

その機能を実装するより良い方法はありますか?

4

5 に答える 5

6

データベースで直接更新し、新しいリビジョンを返します。

update Revisions
set EditNumber += 1
output INSERTED.EditNumber
where PostID = @postId;

残念ながら、これは LINQ では不可能です。実際、ペシミスティック・ロックを行うことを除けば、使用するテクノロジーに関係なく、クライアントはまったく不可能です。

更新しました:

新しいリビジョン (最初のリビジョンを含む) を挿入する方法は次のとおりです。

create procedure usp_insertPostRevision
  @postId int,
  @text nvarchar(max),
  @revisionId bigint output

as 
begin
  set nocount on;
  declare @nextEditNumber (EditNumber int not null);
  declare @rc int = 0;

  begin transaction;
  begin try
    update Posts
    set LastRevision += 1
    output INSERTED.LastRevision
       into @nextEditNumber (EditNumber)
    where PostId = @postId;

    set @rc = @@rowcount;

    if (@rc <> 1)
      raiserror (N'Expected exactly one post with Id:%i. Found:%i', 
        16, 1 , @postId, @rc);

    insert into Revisions
      (PostId, Text, EditNumber)
    select @postID, @text, EditNumber
    from @nextEditNumber;

    set @revisionId = scope_identity();

    commit;    
  end try
  begin catch
   ... // Error handling omitted
  end catch
end

エラー処理は省略しました。エラーとネストされたトランザクションを適切に処理するテンプレート プロシージャについては、例外処理とネストされたトランザクションを参照してください。

Posts テーブルには、投稿のリビジョンの増分として使用される LastRevision フィールドがあります。これは、リビジョンの (範囲) スキャンを回避するため、リビジョンを追加するたびに MAX を計算するよりもはるかに優れています。また、同時実行保護としても機能します。一度に 1 つのトランザクションのみが更新でき、そのトランザクションのみが新しいリビジョンの挿入を続行します。並行トランザクションはブロックされ、最初のトランザクションがコミットされるまで待機します。その後、ブロックが解除された次のトランザクションはリビジョン番号を +1 に正しく更新します。

于 2010-08-01T00:28:34.267 に答える
2

複数のユーザーが同じ投稿を同時に編集できますか? そうでない場合は、1 人のユーザーが複数の編集を同時に送信できない限り、競合状態にはなりません。

于 2010-08-01T00:22:49.893 に答える
1

Posts テーブルには Post ごとに 1 つのレコードしかないため、ロックを使用します。

Posts テーブルのレコードを読み取り、テーブル ヒント [WITH (ROWLOCK, XLOCKX)] を使用して排他ロックを取得します。ロック タイムアウトを数ミリ秒待機するように設定します。

プロセスがロックを取得すると、リビジョン レコードを追加できます。プロセスがロックを取得できない場合は、プロセスに再試行させます。プロセスがロックを取得できない場合は、数回再試行した後、エラーを返します。

于 2010-08-01T04:16:01.367 に答える
1

コメントを送信したユーザーのみが変更を許可する場合は、上記で問題ありません。複数のユーザーが 1 つのコメントを変更できる場合は、問題が発生する可能性があります。

于 2010-08-01T00:23:27.723 に答える
0

EditNumber はコレクションのメンバーシップによって決定されるプロパティであるため、コレクションに提供させます。

EditNumber を計算列にする - 同じ投稿の COUNT 件のレコードで、より少ない RevisionID を持つ。

于 2010-08-01T16:13:39.840 に答える