11

誰かに簡単な実例を見せてもらうのがとても難しいとは信じられません。誰もがやり方を知っているかのようにしか話せず、実際にはそうではないと私は信じています。

投稿を短くして、例でやりたいことだけにします。たぶん、投稿は長くて怖がっている人々に遠ざかっていました。

この報奨金を得るために、VS 2010 でコピーして実行できる実例を探しています。

例が行う必要があること。

  1. mssql 2008のタイムスタンプとして、バージョンのドメインで必要なデータ型を表示します
  2. 「StaleObjectException」を自動的にスローする nhibernate を表示
  3. これら 3 つのシナリオの実例を示してください

シナリオ 1

ユーザー A がサイトにアクセスし、Row1 を編集します。ユーザー B が来て (Row1 が表示されることに注意してください)、クリックして Row1 を編集します。UserB は、ユーザー A が終了するまで行の編集を拒否する必要があります。

シナリオ 2

ユーザー A がサイトにアクセスし、Row1 を編集します。ユーザー B は 30 分後に来て、クリックして Row1 を編集します。ユーザー B は、この行を編集して保存できる必要があります。これは、ユーザー A が行の編集に時間がかかりすぎて、編集する権利を失ったためです。

シナリオ 3

ユーザー A が不在から戻ってきました。行の更新ボタンをクリックすると、StaleObjectException が表示されます。

私はasp.net mvcと流暢なnhibernateを使用しています。これらで行われる例を探しています。


私が試したこと

独自のビルドを試みましたが、StaleObjectException をスローすることも、バージョン番号をインクリメントすることもできません。2 つの別々のブラウザを開くのに疲れて、インデックス ページをロードしました。どちらのブラウザも同じバージョン番号を示しました。

public class Default1Controller : Controller
{
    //
    // GET: /Default1/

    public ActionResult Index()
    {
        var sessionFactory = CreateSessionFactory();

        using (var session = sessionFactory.OpenSession())
        {
            using (var transaction = session.BeginTransaction())
            {
                var firstRecord = session.Query<TableA>().FirstOrDefault();
                transaction.Commit();
                return View(firstRecord);
            }

        }

    }

    public ActionResult Save()
    {
        var sessionFactory = CreateSessionFactory();
        using (var session = sessionFactory.OpenSession())
        {
            using (var transaction = session.BeginTransaction())
            {
                var firstRecord = session.Query<TableA>().FirstOrDefault();
                firstRecord.Name = "test2";
                transaction.Commit();
                return View();
            }
        }
    }

    private static ISessionFactory CreateSessionFactory()
    {
        return Fluently.Configure()
            .Database(MsSqlConfiguration.MsSql2008
                .ConnectionString(c => c.FromConnectionStringWithKey("Test")))
            .Mappings(m => m.FluentMappings.AddFromAssemblyOf<TableA>())
                             //  .ExposeConfiguration(BuidSchema)
            .BuildSessionFactory(); 
    }


    private static void BuidSchema(NHibernate.Cfg.Configuration config)
    {
        new NHibernate.Tool.hbm2ddl.SchemaExport(config).Create(false, true);
    }

}


public class TableA
{
    public virtual Guid Id { get; set; }
    public virtual string Name { get; set; }

    // Not sure what data type this should be for timestamp.
    // To eliminate changing to much started with int version
    // but want in the end timestamp.
    public virtual int Version { get; set; } 
}

public class TableAMapping : ClassMap<TableA>
{
    public TableAMapping()
    {
        Id(x => x.Id);
        Map(x => x.Name);
        Version(x => x.Version);
    }
}
4

7 に答える 7

6

nhibernate は行の取得を停止しますか?

いいえ。ロックはトランザクションの範囲に対してのみ配置され、Web アプリケーションではリクエストが終了すると終了します。また、トランザクション分離モードのデフォルトのタイプはコミット済み読み取りです。これは、選択ステートメントが終了するとすぐに読み取りロックが解放されることを意味します。同じリクエストとトランザクションで読み取りと編集を行っている場合は、他のトランザクションがその行に書き込んだり読み取ったりするのを防ぐために、その行に読み取りと書き込みのロックを設定できます。ただし、このタイプの同時実行制御は Web アプリケーションではうまく機能しません。

または、ユーザー B は引き続き行を表示できますが、保存しようとするとクラッシュしますか?

これは、[オプティミスティック コンカレンシー] が使用されている場合に発生します。NHibernate では、バージョン フィールドを追加することで、オプティミスティック コンカレンシーが機能します。保存/更新コマンドは、更新のベースとなったバージョンで発行されます。それがデータベース テーブルのバージョンと異なる場合、行は更新されず、NHibernate はスローします。

ユーザー A がキャンセルして編集しないと言うとどうなりますか。自分でロックを解除する必要がありますか、それともロックを解除するためにタイムアウトを設定できますか?

いいえ、ロックはリクエストの最後に解放されます。

全体として、最善の策は、NHibernate によって管理されるバージョン フィールドで楽観的同時実行を選択することです。

于 2012-09-04T23:51:09.553 に答える
2

コードではどのように見えますか? 流暢な nhibernate でタイムスタンプを生成するようにセットアップしますか (データ型をタイムスパンするかどうかはわかりません)。

バージョン列を使用することをお勧めします。自動マッピングで FluentNhibernate を使用している場合、型 int/long の Version という列を作成すると、デフォルトでそれがバージョンに使用されます。代わりに、マッピングで Version() メソッドを使用してそうすることができます (同様です)。タイムスタンプ用)。

だから今、私はどういうわけかタイムスタンプを生成し、ユーザーは(GUIを介して)行を編集しています。タイムスタンプをメモリなどに保存する必要がありますか? 次に、ユーザーがメモリから呼び出しを送信すると、タイムスタンプと行の ID とチェックが行われますか?

ユーザーが行の編集を開始すると、それを取得して現在のバージョン (version プロパティの値) を保存します。現在のバージョンをフォームの隠しフィールドに入れることをお勧めします。ユーザーが変更を保存すると、データベース内のバージョンに対して手動でチェックする (非表示フィールドのバージョンと同じであることをチェックする) か、バージョン プロパティを非表示フィールドの値に設定することができます (データバインディングを使用している場合は、これを自動的に行うことができます)。version プロパティを設定すると、エンティティを保存しようとすると、NHibernate は保存しているバージョンがデータベースのバージョンと一致するかどうかを確認し、一致しない場合は例外をスローします。

NHibernate は次のような更新クエリを発行します。

UPDATE xyz SET 、バージョン = 16 WHERE Id = 1234 AND バージョン = 15

(バージョンが 15 であると仮定) - その過程で、バージョン フィールドもインクリメントされます。

その場合、ビジネス ロジックが「行のロック」を追跡していることを意味しますが、理論的には、誰かが Where(x => x.Id == id) に移動してその行を取得し、自由に更新することができます。

他の誰かが NHibernate を介して行を更新すると、バージョンが自動的にインクリメントされるため、ユーザーが間違ったバージョンで保存しようとすると例外が発生し、処理方法を決定する必要があります (つまり、いくつかのマージ画面を表示してみてください。または、ユーザーに新しいデータで再試行するよう伝えます)

行が更新されるとどうなりますか? タイムスタンプに null を設定しますか?

バージョンまたはタイムスタンプを自動的に更新します (タイムスタンプは現在の時刻に更新されます)。

ユーザーが実際に更新を完了せずに離れた場合はどうなりますか。どのようにして行が再びロック解除されるのですか?

行自体はロックされていません。代わりに楽観的同時実行性を使用しています。同じ行を同時に変更する人はいないと想定し、誰かが変更した場合は、更新を再試行する必要があります。

競合状態が発生することはまだありますか、それともほぼ不可能ですか? 2 人の ppl が同じ行を編集しようとすると、両方とも編集用の GUI で表示されますが、競合状態に負けたため、実際には 1 つが最終的に拒否されることを心配しています。

オプティミスティック コンカレンシーを使用している場合、2 人が同時に同じ行を編集しようとすると、そのうちの 1 人が負けます。利点は、変更が失われて更新されたと考えたり、知らないうちに他の人の変更を上書きしたりするのではなく、競合があったことを知ることができることです。

だから私はこのようなことをしました

var test = session.Query.Where(x => x.Id == id).FirstOrDefault(); // 編集のためにユーザーに送信します。バージョン管理があります。ユーザーが編集して 30 分後にデータを返送します。

コードは

test.Id = vm.Id; test.ColumnA = vm.ColumnA; test.Version = vm.Version;

session.Update(テスト); session.Commit(); それで、上記は正しく機能しますか?

他の誰かが入って行を変更した場合、上記は例外をスローします。それがポイントなので、並行性の問題が発生したことがわかります。通常、「誰かがこの行を変更しました」というメッセージをユーザーに表示し、そこに新しい行を表示し、場合によってはその変更も表示するため、ユーザーはどの変更が勝つかを選択する必要があります。

しかし、私がこれを行うと

test.Id = vm.Id; test.ColumnA = vm.ColumnA;

session.Update(test);
session.Commit(); it would not commit right?

行のタイムスタンプが一致しないため、テストをリロードしていない限り(つまり、 test = session.Load() ではなく test = new Xyz() を実行した場合)、修正してください。

他の誰かが NHibernate を介して行を更新すると、バージョンが自動的にインクリメントされるため、ユーザーが間違ったバージョンで保存しようとすると例外が発生し、処理方法を決定する必要があります (つまり、いくつかのマージ画面を表示してみてください。または、ユーザーに新しいデータで再試行するよう伝えます)

レコードを取得するときに、このチェックをオンにできますか。一度に 1 人だけが編集できるように、最初はシンプルに保ちたいと思います。何かがレコードを編集している間、他の人はレコードにアクセスして編集することさえできません。

それは楽観的並行性ではありません。簡単な答えとして、誰かが編集を開始したときに設定する CheckOutDate プロパティを追加し、終了時に null に設定することができます。次に、編集を開始するとき、または編集する行を表示するときに、CheckOutDate が過去 10 分よりも新しいすべての行を除外できます (定期的にリセットするスケジュールされたタスクは必要ありません)。

行自体はロックされていません。代わりに楽観的同時実行性を使用しています。同じ行を同時に変更する人はいないと想定し、誰かが変更した場合は、更新を再試行する必要があります。

あなたの言うことは、これが私にできることを意味するのかわかりません

session.query.Where(x => x.id == id).FirstOrDefault(); 一日中、記録を取得し続けます(バージョンを増やし続けると思っていました)。

クエリはバージョンをインクリメントしません。それへの更新のみがバージョンをインクリメントします。

于 2012-09-13T17:33:33.627 に答える
0

はい、nhibernateで行をロックすることは可能ですが、私がよく理解している場合、シナリオはWebコンテキストにあり、ベストプラクティスではありません。

最善の方法は、前述のように自動バージョン管理で楽観的ロックを使用することです。ページが開いているときに行をロックし、ページがアンロードされているときに行を解放すると、すぐに行のデッドロックが発生します(JavaScriptの問題、ページが適切に強制終了されない...)。オプティミスティックロックにより、別のセッションによって変更されたオブジェクトを含むトランザクションをフラッシュするときに、NHibernateが例外をスローします。同じ情報を真に同時に変更したい場合は、同じドキュメント内で入力された多くのユーザーをマージするシステムを考えてみてください。ただし、それ自体はシステムであり、ORMによって管理されていません。

Web環境でセッションを処理する方法を選択する必要があります。 http://nhibernate.info/doc/nh/en/index.html#transactions-optimistic

高い同時実行性と高いスケーラビリティと一致する唯一のアプローチは、バージョン管理による楽観的同時実行制御です。NHibernateは、楽観的同時実行性を使用するアプリケーションコードを作成するための3つの可能なアプローチを提供します。

于 2012-09-13T09:58:03.480 に答える
0

あなたの質問に対する簡単な答えは、nhibernates 楽観的 (バージョン) および悲観的 (行ロック) ロックを使用する単純な Web アプリケーションでは、これを行うことはできない/行うべきではないということです。あなたのトランザクションがリクエストの期間だけであるという事実は、あなたの制限要因です.

できることは、別のテーブルとエンティティ クラス、およびこれらの「ロック」を管理するマッピングを作成することです。最下位レベルでは、編集中のオブジェクトの ID と、編集を実行しているユーザーの ID、およびロックが取得された日時が必要です。排他的にしたいので、編集中のオブジェクトのIDを主キーにします...

ユーザーが編集する行をクリックすると、ロックの取得を試みることができます (ID と現在の日時でそのテーブルに新しいレコードを作成します)。別のユーザーのロックが既に存在する場合は、主キーの制約に違反しようとしているため、ロックは失敗します。

ロックが取得された場合、ユーザーが保存をクリックしたときに、実際の保存を実行する前に、有効な「ロック」がまだ残っていることを確認する必要があります。次に、実際の保存を実行し、ロック レコードを削除します。

また、これらのロックを定期的にスイープし、期限切れまたは制限時間より古いロックを削除するバックグラウンド サービス/プロセスをお勧めします。

これは、Web 環境で「ロック」を処理するための私の規定の方法です。幸運を!

于 2012-10-04T14:54:59.227 に答える
0

ISaveOrUpdateEventListener インターフェイスを見ましたか?

public class SaveListener : NHibernate.Event.ISaveOrUpdateEventListener
{

    public void OnSaveOrUpdate(NHibernate.Event.SaveOrUpdateEvent e)
    {
        NHibernate.Persister.Entity.IEntityPersister p = e.Session.GetEntityPersister(null, e.Entity);
        if (p.IsVersioned)
        {
            //TODO: check types etc...
            MyEntity m = (MyEntity) e.Entity;
            DateTime oldversion = (DateTime) p.GetVersion(m, e.Session.EntityMode);
            DateTime currversion = (DateTime) p.GetCurrentVersion(m.ID, e.Session);

            if (oldversion < currversion.AddMinutes(-30))
                throw new StaleObjectStateException("MyEntity", m.ID);
        }
    }

}

次に、構成で登録します。

    private static void Configure(NHibernate.Cfg.Configuration cfg)
    {
        cfg.EventListeners.SaveOrUpdateEventListeners = new NHibernate.Event.ISaveOrUpdateEventListener[] {new SaveListener()};

    }



    public static ISessionFactory CreateSessionFactory()
    {
        return Fluently.Configure().Database(...).
                    .Mappings(...)
                    .ExposeConfiguration(Configure)                       
                    .BuildSessionFactory();
    }

そして、Mapping クラスでバージョン管理するプロパティをバージョン管理します。

public class MyEntityMap: ClassMap<MyENtity>
{
    public MyEntityMap()
    {
        Table("MyTable");

        Id(x => x.ID);
        Version(x => x.Timestamp);
        Map(x => x.PropA);
        Map(x => x.PropB);

    }
}
于 2012-10-03T14:22:59.923 に答える
0

nHibernate 自体についてはあまり知りませんが、データベースにいくつかのストアド プロシージャを作成する準備ができている場合は、>ある程度<実行できます。

各行に対する情報を格納するには、オブジェクト モデルに 1 つの追加のデータ列と 2 つのフィールドが必要です。

  • ハッシュ フィールド自体と EditTimestamp フィールド以外のすべてのフィールド値の「ハッシュ」 (SQL Server CHECKSUM 2008 以降または以前のエディションの HASHBYTES を使用)。これは、必要に応じて INSERT/UPDATE トリガーを使用してテーブルに永続化できます。
  • 日時型の「編集タイムスタンプ」。

手順を次のように変更します。

  • 「select」手順には、「edit-timestamp < (Now - 30 minutes)」のような where 句を含める必要があり、「edit-timestamp」を現在の時刻に更新する必要があります。行を更新する前に、適切なロックを使用して選択を実行します。ここでは、このような保持ロックを 使用するストアド プロシージャを考えています。GETDATE() のようなものではなく、永続的な日付/時刻を使用します。

例 (固定値を使用):

BEGIN TRAN

DECLARE @now DATETIME 
SET @now = '2012-09-28 14:00:00'

SELECT *, @now AS NewEditTimestamp, CHECKSUM(ID, [Description]) AS RowChecksum
FROM TestLocks
WITH (HOLDLOCK, ROWLOCK)
WHERE ID = 3 AND EditTimestamp < DATEADD(mi, -30, @now)

/* Do all your stuff here while the record is locked */
UPDATE TestLocks
SET EditTimestamp = @now
WHERE ID = 3 AND EditTimestamp < DATEADD(mi, -30, @now)

COMMIT TRAN

このプロシージャから行が返された場合、「ロック」が「取得」されています。それ以外の場合、行は返されず、編集するものはありません。

  • 「更新」手順では、「ハッシュ = 以前に返されたハッシュ」のような where 句を追加する必要があります。

例 (固定値を使用):

BEGIN TRAN

    DECLARE @RowChecksum INT
    SET @RowChecksum = -845335138

    UPDATE TestLocks
    SET [Description] = 'New Description'
    WHERE ID = 3 AND CHECKSUM(ID, [Description]) = @RowChecksum

    SELECT @@ROWCOUNT AS RowsUpdated

COMMIT TRAN

あなたのシナリオでは:

  1. ユーザー A が行を編集します。データベースからこのレコードを返すと、'edit-timestamp' が現在の時刻に更新され、行が作成されているため、編集できることがわかります。タイムスタンプがまだ新しすぎるため、ユーザー B は行を取得しません。

  2. ユーザー B は 30 分後に行を編集します。タイムスタンプが 30 分以上前に経過しているため、行が返されます。更新が書き込まれていないため、フィールドのハッシュは 30 分前のユーザー A と同じになります。

  3. ここで、ユーザー B が更新されます。以前に取得したハッシュは、行内のフィールドのハッシュと引き続き一致するため、更新ステートメントは成功し、行カウントを返して、行が更新されたことを示します。ただし、ユーザー A は次に更新を試みます。説明フィールドの値が変更されたため、ハッシュ値が変更されたため、UPDATE ステートメントによって何も更新されません。「ゼロ行が更新されました」という結果が得られるため、行が変更されたか、行が削除されたかがわかります。

これらすべてのロックが進行中のスケーラビリティに関しておそらくいくつかの問題があり、上記のコードは最適化される可能性があります (たとえば、UTC を使用するなど、時計の前後移動で問題が発生する可能性があります)。

それ以外では、select トランザクション内でデータベース レベルの行ロックを利用せずにこれを行う方法がわかりません。これらのロックは nHibernate 経由でリクエストできるかもしれませんが、残念ながらそれは nHibernate に関する私の知識を超えています。

于 2012-09-28T14:19:26.377 に答える
-1

ねえ、あなたはこれらのサイトを試すことができます

http://thesenilecoder.blogspot.ca/2012/02/nhibernate-samples-row-versioning-with.html

http://stackingcode.com/blog/2010/12/09/optimistic-concurrency-and-nhibernate

于 2012-10-05T15:30:50.200 に答える