3

車に関する情報を保持するテーブルがあります (tbl_incoming_car と呼びましょう)。そのテーブルには、'customer_number' という名前の一意ではない列があり、これまでにシステムに入った車の数を示しています。同じ車は何回も出入りできますが、これは一度しか登録されません。

そのため、新しい車が入ってきたら、最後の車の番号を取得してインクリメントし、それを新しい車の「customer_number」として保存する必要があります。

最も簡単な方法は、車用に別のテーブルを用意し、そこに「customer_number」を用意し、インとアウトを別のテーブルに登録することですが、これは状況を明らかにするためのばかげた例にすぎません。したがって、アプローチが間違っていることを議論する意味はありません。私はすでにそれを知っています:)

前述したように、新しい車がシステムに入るたびに、最後に追加された行を取得し、'customer_number' を取得してインクリメントし、アトミック操作として保存する必要があります。他のアプリケーション インスタンスが同じことを試みる可能性があり、DB は「作成タスク」中に最後に追加された行の要求を保持する必要があります。

分離レベルをシリアライズ可能に設定することでできると思いましたが、最後の行の読み取りは妨げられず、新しい行の挿入は妨げられないと思います。したがって、ロックが解決策のようです。コードで静的オブジェクトを Monitor として使用しようとしましたが、正常に動作しますが、もちろん同じアプリケーション ドメインに限定されており、DB レベルで何かが必要です。

DBにロックを設定するEFには何もないと思うので、テーブルにロックを設定して後で解放する最良の方法はどれですか?

ありがとう。

4

6 に答える 6

3

これまでのところ、これは私が思いついた最良の方法です:

    public void SetTransactionLock(String resourceName)
    {
        Ensure.IsNotNull(resourceName, "resourceName");

        String command = String.Format(
        @"declare @result int;
          EXEC @result = sp_getapplock '{0}', 'Exclusive', 'Transaction', 10000 
          IF @result < 0
            RAISERROR('ERROR: cannot get the lock [{0}] in less than 10 seconds.', 16, 1);",resourceName);

        base.Database.ExecuteSqlCommand(command);
    }

    public void ReleaseTransactionLock(String resourceName)
    {
        Ensure.IsNotNull(resourceName, "resourceName");
        String command = String.Format("EXEC sp_releaseapplock '{0}';",resourceName);
        base.Database.ExecuteSqlCommand(command);
    }

EF には組み込みの方法がないため、これら 2 つのメソッドをデータ層に追加し、それらを使用して、1 つの同時操作のみが許可される「クリティカル セクション」を宣言します。

try finally ブロックで使用できます。

于 2013-02-24T22:49:01.853 に答える
1

REPEATABLE READ分離レベルを使用すると、SELECTコマンドを実行した時点でロックを取得できます。最後の行を取得するためのSELECTコマンドがわからないのですが、おそらく機能しません。SELECTを実行する方法は、SQLが行/インデックス/テーブルをロックする方法を変更します。

最善のアプローチは、ストアドプロシージャを実行することです。EFは、データベースとアプリケーションの間でいくつかのラウンドトリップを行います。これにより、ロック時間が長くなり、アプリケーションのパフォーマンスに影響を与えます。

おそらく、挿入後にトリガーを使用して更新を行うことができます。

于 2013-03-03T00:17:29.613 に答える
1

シリアライズ可能は実際にこれを解決します。シリアル化可能とは、トランザクションがすべてグローバルデータベースのXロックを取得したかのように動作することを意味します。同時に1つのトランザクションのみが実行されたかのように。

これはインサートにも当てはまります。ロックは、間違った場所への挿入を防ぐなどの方法で行われます。

ただし、デッドロックの自由を達成できない可能性があります。まだ試してみる価値があります。

于 2013-02-24T23:13:52.107 に答える
1

EF は C# で動作しTransactionScopeます。

過去に、私はこのような同様の問題を解決してきました:

 using (var ts = new TransactionScope())
        {
            using (var db = new DbContext())
            {
                db.Set<Car>().Add(newCar);
                db.SaveChanges();
                // newCar.Id has value here, but record is locked for read until ts.Complete()

                newCar.CustomerNumber = db.Set<Car>().Max(car => car.CustomerNumber) + 1;
                db.SaveChanges();
            }
            ts.Complete();
        }

db.Set<Car>().Max(car => car.CustomerNumber)すべてのレコードにアクセスする必要があるため、同時タスクの行は待機する必要があります。ts.Complete() の前にブレークポイントを追加Select max(CustomerNumber) from dbname.dbo.Carsし、その行でコードが一時停止している間に実行を試みることでこれを確認できます。コードを再開して ts が完了すると、クエリが完了します。

もちろん、これは、例が実際のシナリオを十分に説明している場合にのみ機能しますが、ニーズに簡単に適応できるはずです。

詳細については、MSDN の ef のトランザクションを参照してください。

于 2013-03-03T17:20:20.047 に答える
1

EF は SQL 操作の前に実行されるため、このスレッドで前述したソリューションは普遍的に適用できない可能性がありsp_resetconnectionます。そのため、DbContext で SQL を呼び出す何かを実行すると、保持していたはずのロックが本質的に解放されます。

私が思いついたのは、ロックを取得する前に SqlConnection のクローンを作成し、解放する準備ができるまでこの接続を保持する必要があるということです。

public class SqlAppLock {
    DbConnection connection;
    public SqlAppLock(DbContext context, string resourceName)
    {
        ... (test arguments for null, etc)
        connection = (DbConnection)(context.Database.Connection as ICloneable).Clone();
        connection.Open();

        var cmd = connection.CreateCommand();
        cmd.CommandText = "sp_getapplock";
        ... (set up parameters)
        cmd.ExecuteNonQuery();

        int result = (int)cmd.Parameters["@result"].Value;
        if (result < 0)
        {
            throw new ApplicationException("Could not acquire lock on resource");
        }
    }

そして解放するには、 を使用してロックを解放するsp_releaseapplockか、単に接続を Dispose() することができます

于 2014-06-19T18:02:11.010 に答える
0

EF は、rowversion データ型とも呼ばれる SQL timeStamp をサポートしています。

これにより、楽観的ロックを使用して更新を実行できます。フィールドが適切に宣言されていれば、フレームワークがそれを行います。本質的に:

UPDATE where ID=ID SET Customer_number to X+1Update where ID=ID and Rowversion = rowversion Set Customer_number =X+1

そのため、行がスレッド X にロードされてから変更された場合、エラーが発生します。リロードして再試行するか、シナリオに適したアクションを実行できます。

詳細については、http://msdn.microsoft.com/en-us/data/jj592904を参照してください。

http://msdn.microsoft.com/en-us/library/aa0416cz.aspx

そしてこのSO投稿

データベースでエンティティ データがいつ変更されたかを判断するための適切な方法は何ですか?

于 2013-02-21T14:37:50.727 に答える