1

PLINQO を使用して、n 層の分散システムにリポジトリ レイヤーとデータ層を実装する作業を開始しました。

データ層は次の層で構成されています: リポジトリ、データ プロバイダー、データ サービス

データベースからデータを取得し、データベースにデータを設定する役割を担うリポジトリ層。

データ プロバイダー レイヤーは、リポジトリとサービス レイヤーの間のゲートです。

データ サービス層は、すべてのビジネス ロジックとルールを保持します。

トランザクションを使用すると、このアーキテクチャで問題が発生します。「既に存在するエンティティをアタッチできません」というエラー メッセージとともにInvalidOperationExceptionが発生します。よく。

その理由は、リポジトリ層の設計のためです。すべてのリポジトリ メソッドで共通の手順は次のとおりです。

MyDataContext context;
if(InTransaction == true)
    context = GetExistingContext();
else
    context = GetNewContext();

//Do some database operation for example:
var user = context.User.GetByKey("username");

//Detach from context
user.Detach();

if(InTransaction == false)
    context.Dispose();

InTransactionが trueのときにInvalidOperationExceptionが発生し、同じエンティティで動作する 2 つのメソッドを呼び出しています。InTransaction が true であるため、2 つのメソッドは同じデータ コンテキストを使用します。最初のメソッドはエンティティをアタッチし、最後にデタッチし、2 番目のメソッドもアタッチしようとすると、例外が発生します。

私は何を間違っていますか?どうすればこれを防ぐことができますか?

ありがとう、

コビー

4

1 に答える 1

2

問題の解決策を見つけました。

既にデータ コンテキストにアタッチされているエンティティをアタッチしようとしたときに例外が発生しました。これは、メソッドが使用するデータ コンテキストにエンティティが既にアタッチされているかどうかを Update() メソッドに示していなかったために発生しました。

アタッチを削除したときに例外は発生しませんでしたが、エンティティが更新されないことがありました (データコンテキストが新しい場合)。

そこで、これら 2 つの相反する状況を橋渡しする方法を考えました。その解決策が TransactionScope です。

メソッドがスコープに参加および脱退できるようにする TransactionScope クラスを作成しました。

Join() メソッドは、新しいデータ コンテキストを作成し、使用カウンターを初期化するため、このメソッドを連続して呼び出すと、このカウンターが 1 増加します。

Leave() メソッドは使用カウンターをデクリメントし、ゼロに達するとデータコンテキストが破棄されます。

データ コンテキストを返すプロパティもあります (各メソッドが独自のコンテキストを取得しようとする代わりに)。

データ コンテキストを必要とするメソッドを、質問で説明したものから次のように変更しました。

_myTranscationScope.Join();

try
{
  var context = _myTransactionScope.Context;

  //Do some database operation for example:
  context.User.GetByKey("username");

}
finally
{
    _myTranscationScope.Leave();
}

さらに、データ コンテキストの Dispose メソッドをオーバーライドし、各メソッドでこれを行う代わりに、エンティティのデタッチをそこに移動しました。

私が確認する必要があるのは、正しいトランザクションスコープを持っていることと、参加するための各呼び出しにも(例外であっても)去るための呼び出しがあることを確認することだけです

これにより、私のコードはすべてのシナリオ (単一のデータベース操作、複雑なトランザクション、シリアル化されたオブジェクトの操作など) で完璧に機能するようになりました。

これが TransactionScope クラスのコードです (私が取り組んでいるプロジェクトに依存する行コードを削除しましたが、それでも完全に機能するコードです):

using System;
using CSG.Games.Data.SqlRepository.Model;

namespace CSG.Games.Data.SqlRepository
{
    /// <summary>
    /// Defines a transaction scope
    /// </summary>
    public class TransactionScope :IDisposable
    {
        private int _contextUsageCounter; // the number of usages in the context
        private readonly object _contextLocker; // to make access to _context thread safe

        private bool _disposed; // Indicates if the object is disposed

        internal TransactionScope()
        {
            Context = null;
            _contextLocker = new object();
            _contextUsageCounter = 0;
            _disposed = false;
        }

        internal MainDataContext Context { get; private set; }

        internal void Join()
        {
            // block the access to the context
            lock (_contextLocker)
            {
                CheckDisposed();

                // Increment the context usage counter
                _contextUsageCounter++;

                // If there is no context, create new
                if (Context == null)
                    Context = new MainDataContext();
            }
        }

        internal void Leave()
        {
            // block the access to the context
            lock (_contextLocker)
            {
                CheckDisposed();
                // If no one using the context, leave...
                if(_contextUsageCounter == 0)
                     return;

                // Decrement the context usage counter
                _contextUsageCounter--;

                // If the context is in use, leave...
                if (_contextUsageCounter > 0)
                    return;

                // If the context can be disposed, dispose it
                if (Context.Transaction != null)
                    Context.Dispose();

                // Reset the context of this scope becuase the transaction scope ended
                Context = null;
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            lock (_contextLocker)
            {
                if (_disposed) return;

                if (disposing)
                {
                    if (Context != null && Context.Transaction != null)
                        Context.Dispose();

                    _disposed = true;
                }
            }
        }        

        private void CheckDisposed()
        {
            if (_disposed)
                throw new ObjectDisposedException("The TransactionScope is disposed");
        }

    }
 }
于 2011-05-06T15:35:19.123 に答える