コンシューマータイプのアプリケーションでDataContextインスタンスをキャッシュする方法は?


8

プロバイダーが提供するSDKを使用して、簡単に統合できるアプリケーションがあります。このSDKはAMQPエンドポイントに接続し、メッセージをコンシューマーに配信、キャッシュ、および変換するだけです。以前、この統合はデータソースとしてXMLを使用するHTTP経由で行われ、古い統合には、WebリクエストごととマネージスレッドIDごとの2つのDataContextキャッシュ方法がありました。(1)

ただし、SDKがすべての接続ロジックを実行し、コンシューマーを定義するだけなので、HTTP経由ではなく、AMQPが透過的になり、「Webリクエストごとに」DataContextをキャッシュするオプションがないため、管理スレッドごとにのみidが残されます。責任パターンのチェーンを実装したので、更新が来ると、DataContextを使用して新しい更新に従ってデータベースを更新するハンドラーの1つのパイプラインに配置されます。パイプラインの呼び出し方法は次のようになります。

public Task Invoke(TInput entity)
{
    object currentInputArgument = entity;

    for (var i = 0; i < _pipeline.Count; ++i)
    {
        var action = _pipeline[i];
        if (action.Method.ReturnType.IsSubclassOf(typeof(Task)))
        {
            if (action.Method.ReturnType.IsConstructedGenericType)
            {
                dynamic tmp = action.DynamicInvoke(currentInputArgument);
                currentInputArgument = tmp.GetAwaiter().GetResult();
            }
            else
            {
                (action.DynamicInvoke(currentInputArgument) as Task).GetAwaiter().GetResult();
            }
        }
        else
        {
            currentInputArgument = action.DynamicInvoke(currentInputArgument);
        }
    }

    return Task.CompletedTask;
}

問題は、(少なくとも私が思うに)この責任の連鎖は、新しいタスクを返す/開始するメソッドの連鎖であるため、エンティティAの更新が発生すると、マネージスレッドid = 1によって処理され、その後しばらくの間ここでも、同じエンティティAが管理対象スレッドid = 2によって処理されるためだけに到着します。これはにつながります:

System.InvalidOperationException: 'エンティティオブジェクトはIEntityChangeTrackerの複数のインスタンスから参照できません。

マネージスレッドid = 1からのDataContextは既にエンティティAを追跡しているためです(少なくとも、それは私が思うとおりです)。

私の質問は、私の場合にどのようにDataContextをキャッシュできるかです。君たちは同じ問題を抱えていましたか?私はこれを読んで、この答えと、1つの静的DataContextを使用して理解したことからも、オプションではありません。(2)

  1. 免責事項:私たちはアプリケーションを継承したと言っておくべきでしたが、なぜそのように実装されたのか答えることはできません。
  2. 免責事項2:EFの経験はほとんどありません。

コミュニティからの質問:

  1. 使用しているEFのバージョンは何ですか?5.0
  2. エンティティがコンテキストよりも長生きするのはなぜですか?-そうではありませんが、エンティティがコンテキストよりも長く生きる必要がある理由を尋ねているのかもしれません。私は、キャッシュされたDataContextを使用するリポジトリを使用して、データベースからエンティティを取得し、キャッシュとして使用するメモリ内コレクションに格納します。

これは、エンティティが「抽出」される方法です。ここDatabaseDataContextで、私が話しているキャッシュされたDataContext はどこにありますか(データベースセット全体が含まれるBLOB)

protected IQueryable<T> Get<TProperty>(params Expression<Func<T, TProperty>>[] includes)
{
    var query = DatabaseDataContext.Set<T>().AsQueryable();

    if (includes != null && includes.Length > 0)
    {
        foreach (var item in includes)
        {
            query = query.Include(item);
        }
    }

    return query;
}

次に、私のコンシューマーアプリケーションがAMQPメッセージを受信するたびに、責任パターンのチェーンが、このメッセージとそのデータがすでに処理されているかどうかのチェックを開始します。だから私はそのようなメソッドがあります:

public async Task<TEntity> Handle<TEntity>(TEntity sportEvent)
            where TEntity : ISportEvent
{
    ... some unimportant business logic

    //save the sport
    if (sport.SportID > 0) // <-- this here basically checks if so called 
                           // sport is found in cache or not
                           // if its found then we update the entity in the db
                           // and update the cache after that
    {
        _sportRepository.Update(sport); /* 
                                         * because message update for the same sport can come
                                         * and since DataContext is cached by threadId like I said
                                         * and Update can be executed from different threads
                                         * this is where aforementioned exception is thrown
                                        */

    }
    else                   // if not simply insert the entity in the db and the caches
    {
        _sportRepository.Insert(sport);
    }

    _sportRepository.SaveDbChanges();

    ... updating caches logic
}

AsNoTracking()メソッドを使用してデータベースからエンティティを取得するか、エンティティを「更新」または「挿入」するたびにエンティティをデタッチするとこれを解決できると思っていましたが、解決しませんでした。


まだ答えはありませんが、使用しているEFのバージョンを教えてください
Simon Price

また、これを見て、これがすべてのstackoverflow.com/questions/41346635/…
Simon Price

@ SimonPrice、5.0
kuskmen

エンティティAを更新した後、追跡を解除できます。しかし、これは並行性の問題を処理せず、発生を最小限に抑えます
ilkerkaran

@ilkerkaran、しかし更新/挿入後に追跡を外しても、後でdbに保存できないということではありませんか?私は基本的に、基準に基づいて更新または挿入を呼び出し、その後すぐにを呼び出しSaveChangesます。
kuskmen

回答:


2

DbContextの新規作成には一定のオーバーヘッドがあり、DIを使用してWebリクエスト内のDbContextの単一のインスタンスを共有すると、このオーバーヘッドの一部を節約できますが、単純なCRUD操作では、アクションごとに新しいDbContextを新規作成できます。

これまでに投稿したコードを見ると、DbContextのプライベートインスタンスがリポジトリコンストラクタで新しくなり、次に各メソッドのリポジトリが新しくなります。

次に、メソッドは次のようになります。

public async Task<TEntity> Handle<TEntity>(TEntity sportEvent)
        where TEntity : ISportEvent
{
        var sportsRepository = new SportsRepository()

        ... some unimportant business logic

        //save the sport
        if (sport.SportID > 0) 
        {
            _sportRepository.Update(sport);
        }
        else
        {
            _sportRepository.Insert(sport);
        }

        _sportRepository.SaveDbChanges();

}

public class SportsRepository
{
    private DbContext _dbContext;

    public SportsRepository()
    {
        _dbContext = new DbContext();
    }

}

他のリポジトリクラスとDbContextを共有する方法として、スタブエンティティの使用を検討することもできます。


はい、残念ながら、datalayerプロジェクトは新しいサービスと古いウェブサイトアプリケーションの両方で使用されており、変更の対象ではありません。:/私はすべてのフローに対して1つのシングルトンdbcontextを使用することになり、パイプラインをマルチスレッド化するときに後でこれを変更することを考えていると思います
kuskmen

0

これは既存のビジネスアプリケーションに関するものなので、ベストプラクティスについて講義したり、アーキテクチャの変更を提案したりするのではなく、問題の解決に役立つアイデアに焦点を当てます。

これは明らかであることはわかっていますが、エラーメッセージを言い換えると、何が起こっているのかをよりよく理解するのに役立ちます。

エラーメッセージは、エンティティが複数のデータコンテキストによって使用されていることを示しています。これは、複数のdbcontextインスタンスがあり、エンティティがそのようなインスタンスの複数で参照されていることを示しています。

次に、HTTPリクエストごとに使用されていたスレッドごとのデータコンテキストが存在し、エンティティがキャッシュされることが質問によって示されます。

したがって、キャッシュミス時にエンティティがdbコンテキストから読み取られ、ヒット時にキャッシュから返されたと想定することは安全に思われます。2番目のDBコンテキストインスタンスを使用して、1つのDBコンテキストインスタンスからロードされたエンティティを更新しようとすると、失敗します。この場合、両方の操作でまったく同じエンティティインスタンスが使用され、キャッシュにアクセスするためのシリアル化/非シリアル化は行われていないと結論付けることができます。

DbContextインスタンス自体は、内部の変更トラッカーメカニズムを通じてエンティティキャッシュであり、このエラーはその整合性を保護するための安全策です。アイデアは、複数のdbコンテキスト(スレッドごとに1つ)と共有エンティティキャッシュを介して同時リクエストを処理する長期実行プロセスを持つことなので、パフォーマンスとメモリの両方で非常に有益です(変更の追跡により、時間内のメモリ消費が増える可能性があります。 )メッセージごとにdbコンテキストのライフサイクルを変更するか、各メッセージが処理された後で変更トラッカーを空にしようとします。

もちろん、エンティティの更新を処理するには、キャッシュから取得した直後で、変更が適用される前に、エンティティを現在のdbコンテキストにアタッチする必要があります。


洞察をありがとう、私はあなたに同意しますが、ここで問題は残っています。私の現在の回避策は、「リポジトリ」をスキップしてデータコンテキストを直接操作することです...
kuskmen
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.