2

私は現在、ASP に変換している WinForms アプリを持っており、データをキャッシュすることの重要性をすぐに認識しています。

私の WinForms アプリでは、SQL Server DB から取得したデータを使用Dictionary(of String, List(of String))してイベントに入力したフォーム レベルの変数を作成し、2 つのリンクされたコンボ ボックスを関連付けました (これはプログラム全体で他のさまざまなことに使用されました)。Form_Load()WinForms アプリの優れた点は、この変数がフォーム レベルの変数であり、フォームと共に消滅するため、プログラム全体で使用できることです。

現在、ASP プロジェクトで同じ種類の機能を取得しようとしているので、次のようにプロパティとして設定し、キャッシュに関連付けました。

Private ReadOnly Property MyDict As Dictionary(Of String, List(Of String))
    Get
        Dim dict As Dictionary(Of String, List(Of String))
        dict = Cache("MyDict")

        If dict Is Nothing Then

            ... QUERY DB AND POPULATE DICTIONARY ...

            Cache("MyDict") = dict
        End If

        Return dict
    End Get
End Property

私の最初の (そして最も重要な) 質問は、私がこれを正しく行っているのか、それともキャッシングを十分に理解していないのかということです。

私の次の質問は、これを正しく行っている場合、キャッシュの有効期間をどのように/どこで宣言するかです。プログラムを再実行したとき、キャッシュされたデータは以前の実行からまだ利用可能であることに気付きました...ページの存続期間中は利用できるようにしたいと思いますが、一度閉じた後は利用できません(可能な場合.

第三に、良いアドバイス/リンク/トリックは大歓迎です!!

ありがとう!!!

PS-私のコードがVBであることは承知していますが、これは言語固有ではなく.Netの概念レベルであるため、C#の回答も問題ありません。ありがとう。

4

2 に答える 2

1

キャッシュのアプローチは少し問題があり、達成しようとしているものに対しておそらく過度に複雑です。キャッシュの有効期間の設定に関する質問は、これが最適でないアプローチである理由の一部です。従属ドロップダウンを設定するためのより伝統的なアプローチを使用すると、多くの手間が省けます。

WinForms 開発から来て ASP.NET に慣れるのが最も難しいことの 1 つは、サーバー側のデータがポスト間で「失われる」という事実です。

WinForms アプリケーションでは、DataTable をグローバル変数などとして宣言できます。ボタンをクリックしたり、ドロップダウン リストの値を変更したりするだけでは、DataTable がまだそこにあるという事実には影響しません。人口。フォームが破棄されるまでテーブルが「生きている」ため、そこにあります。ASP.NET では、サーバー側オブジェクトはページの処理中のポストバック中にのみ存在し、ポストバック時に再作成する必要があります。

これを抽象化する背後にある魔法のほとんどは、ポストバック間でページ上のオブジェクトの状態を保存するViewStateを介して行われます。たとえば、コードでデータベースにクエリを実行し、結果を DataTable に保存してから、その DataTable を DataGrid にバインドする場合、ViewState は DataGrid の内容を保存します。次のポストバックでは、DataGrid のコンテンツを利用できますが、DataGrid にバインドされていた最初の DataTable は存在しません。

キャッシュを使用してオブジェクトの永続的な有効期間を模倣し、ASP.NET アプリケーションを WinForms アプリケーションのように動作させようとするのは、テクノロジの誤用です。それは本質的に悪いわけではなく、そうすることが不可能というわけでもありません。間違ったアプローチを使用しているだけです。基本的に、仕事に間違ったツールを使用しています。

これをやろうとするよりも、特定のタスクを実行する方法の例を探した方がよいでしょう。あなたの場合、 「Cascading Drop-Down List ASP.NET」またはそれらの行に沿って何かを検索したいと思うでしょう。いくつかのオプションが利用可能ですが、これはどれよりも優れています: http://www.aspsnippets.com/Articles/Creating-Cascading-DropDownLists-in-ASP.Net.aspx

また、まだ慣れていない場合は、.NET アプリを作成するときにASP.NET ページ ライフサイクルを理解することが非常に重要です。新しい ASP.NET 開発者がページ ライフサイクルを理解できていないこと以上に、ASP.NET 開発の単一のコンポーネントを失うものはありません。これを理解することは、私たちがあなたに投げかけることができる何よりもあなたを助け、あなたが尋ねている質問に直接関係しています.

于 2013-02-26T15:38:49.850 に答える
1

まず、Windows フォームでは、メイン フォームのインスタンスを 1 つ保持する 1 つのプロセスがあります (セカンダリ フォームや、メイン フォームの多数のインスタンスを作成する完全に許容されるケースについては説明しません)。

ここで、ASP.NET には 1 つのプロセスがあり、その中に多数の「メイン」ページ インスタンスが存在します。Web アプリケーションにアクセスする個別のユーザーごとに 1 つずつあると言う人もいるかもしれません。これは部分的に正しいです。「メイン」ページ インスタンスの瞬間的な数は、アクティブなユーザーの数よりも多くなる可能性があります (MVC ではない ASP.NET について話しています)。

WinForms で行っていたのと同じように、引き続きグローバルにアクセスできます。唯一の違いは次のとおりです。

  1. WinForms では、一般にメイン フォームが一意であるため、それをグローバル コンテナーとして使用できます。ASP.NET の場合、メイン ページのグローバル インスタンスが 1 つだけではないため、これを行うことはできません (たとえば、同じブラウザーからの同じセッションの場合でも、ページを更新すると、ほとんどの場合、新しい Page インスタンス. それをテストするには: そのページの暗黙的な public パラメーターなしコンストラクターを実装し、ブレークポイントを使用してそれをチェックします)

  2. 多くのブラウザからのリクエストを処理しているすべての異なるスレッドをすべて、一意の共有メモリにアクセスさせることは、一般的に危険です (特に、自分が何をしているのかよくわからない場合)。

私は個人的に次の考えに完全に同意するわけではありませんが、一般的に、初心者はかなり冗長なSELECTコマンドでデータベースを攻撃する必要があります。

データベースを攻撃しない簡単な解決策を提供できるように、問題を少し単純化します。キャッシュは必要なかったとしましょう。データベースから大量のものを読み取り、Web アプリケーションを再起動するまで二度と読み取らないという事実に同意する必要がありました。情報が読み取られると、Web アプリケーションの隅々で利用できるようになります。

その場合、簡単な解決策があります。

  1. アプリケーションの起動時に通知を受け取るには、よく知られている「グローバル アプリケーション クラス」( Global.asax ) とその「Application_Start」メソッドを使用します (ソース ファイルと同じようにプロジェクトに追加するだけで、Add新しい項目ダイアログ)

  2. ディクショナリのように機能し、ASP.NET アプリケーション内でグローバル情報を共有できるようにするグローバル HttpApplicationState クラスを使用します。

そのようです:

public class Global : System.Web.HttpApplication {

    protected void Application_Start(object sender, EventArgs e) {
        // .. read the database here

        HttpContext.Current.Application["SOME_KEY"] = "ANY OBJECT";
        // .. etc
    }

このようにして、ASP.NET アプリケーションのどこからでもグローバル HttpApplicationState インスタンスに記述した内容を次のように読み取ることができます。

public partial class WebForm2 : System.Web.UI.Page {
    protected void Page_Load(object sender, EventArgs e) {

        object obj = this.Context.Application["SOME_KEY"];
        // ...etc...
    }
}

アプリの再実行について: ほとんどの場合、Web サーバー (特に IIS だけでなく、ASP.NET 開発サーバーも) は停止しません。アプリが停止した場合に、アプリを「再実行」するたびに開始されます。ただし、デバッグを停止する (Visual Studio の [停止] ボタンをクリックする) と、それがすべてです。Web サーバーのプロセスから切り離し、平穏に実行したままにします。

アプリを「再実行」するとき、Web サーバーが既に実行されている場合 (ASP.NET 開発サーバーがクラッシュすることもあり、IIS もクラッシュすることがありますが、それほど頻繁ではありません)、Web サーバーに「IDE のデバッガーを再接続する」だけです。そして、すべてが同じであることを発見します。

それだけではありません: 再構築する場合 (再構築が不要な場合は強制的に)、Web サーバーは停止しませんが、(分離された AppDomain で実行される) アプリを破棄し、新しいアセンブリを再読み込みして起動しますまた。Global.asax の "Application_Started" メソッドをログに記録すると、これらすべてを確認できます。

編集

これがキャッシュを持つ安全な方法です (これは、多くのリーダーがいくつかのグローバル データに同時にアクセスできるように最適化されていますが、それでも少し遅くなります - キャッシュを持つのは贅沢なことです :))。

まず、次のようなクラスを作成します。

public sealed class SafeCache<T> {

    private readonly ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
    private readonly Func<T> expensiveReader;

    private readonly TimeSpan lease;
    private DateTime lastRead;
    private T data;

    public SafeCache(TimeSpan lease, Func<T> expensiveReader) {
        this.lease = lease;
        this.expensiveReader = expensiveReader;
        this.data = expensiveReader();
        this.lastRead = DateTime.UtcNow;
    }

    public T Data {
        get {
            this.rwLock.EnterReadLock();
            try {

                if (DateTime.UtcNow - this.lastRead < this.lease)
                    return this.data;

            } finally {
                this.rwLock.ExitReadLock();
            }

            this.rwLock.EnterUpgradeableReadLock();
            try {

                if (DateTime.UtcNow - this.lastRead < this.lease)
                    return this.data;
                else {
                    this.rwLock.EnterWriteLock();
                    try {

                        this.data = expensiveReader();
                        this.lastRead = DateTime.UtcNow;

                        return this.data;

                    } finally {
                        this.rwLock.ExitWriteLock();
                    }
                }

            } finally {
                this.rwLock.ExitUpgradeableReadLock();
            }
        }
    }

}

次に、Global.asax を使用してそのインスタンスを作成し、任意のキーで HttpApplicationState グローバル インスタンスに配置します。

public class Global : System.Web.HttpApplication {

    protected void Application_Start(object sender, EventArgs e) {
        HttpContext.Current.Application["SOME_KEY"] = new SafeCache<SomeRecord[]> (
          lease: TimeSpan.FromMinutes(10),
          expensiveReader: () => {
             // .. read the database here
             // and return a SomeRecord[]
             // (this code will be executed for the first time by the ctor of SafeCache
             // and later on, with every invocation of the .Data property getter that discovers 
             // that 10 minutes have passed since the last refresh)
          }
        );
        // .. etc
    }

次のような小さなヘルパーを作成することもできます。

public static class Helper {
    public static SomeRecord[] SomeRecords {
        get { 
           var currentContext = HttpContext.Current;
           if (null == currentContext) // return null or throw some clear Exception

           var cache = currentContext.Application["SOME_KEY"] as SafeCache<SomeRecord[]>;
           return cache.Data;
        }
    }
}

そしてもちろん、必要に応じてそれを使用してください。

public partial class WebForm2 : System.Web.UI.Page {
    protected void Page_Load(object sender, EventArgs e) {

        SomeRecord[] records = Helper.SomeRecords;
        // ...etc...
    }
}

編集の終わり

于 2013-02-26T15:46:28.473 に答える