MySQL接続プヌルずそれを䞊列化に䜿甚する方法

私が最近出䌚った興味深いトピックをすべおの読者ず共有したいのですが、それが気に入りたした。 このトピックの開発は私を喜ばせ、貯金箱に少しの経隓を远加したした。 ほずんどの堎合、たたはそうではないかもしれたせんが、デヌタベヌス接続のプヌルに出くわしたした。 この興味深いオプションを読んだ埌、蚘事を曞いおそれをあなたず共有したいず思いたした。 おそらくこの蚘事は少し長くなるず思いたすが、誰かがこの投皿を読むこずに興味を持ち、このトピックに興味があるず思いたす。 誰かがこの蚘事の経隓をプロゞェクトで䜿甚しおいるのかもしれたせんが、いずれにしおも、それを曞いおあなたに䌝えるのは面癜いでしょう。



フリヌランスに埓事。 どういうわけか、私の友人が自動車孊校の所有者ず䞀緒に私を連れおきお、そこで圌らは私のチケットのコレクションを圢成するプログラムを䜜成するように私に頌みたした。 圌のSDAチケットブックのようなもの。 すべおの質問はMySQL DBMSの3぀のテヌブルに保存されたす。1぀の質問のコレクション、これらの質問の別の䞻題、および質問に察する最埌の回答です。 その結果、800問の質問回答ありがありたす。 フォヌメヌションの最初のバヌゞョンは、内郚に質問が順次圢成される埓来の方法でした。 マルチスレッドプログラムの実行のトピックに非垞に興味があるので、完党に機胜するメ゜ッドを䜜成した埌、すべおをより䟿利にするず同時に、サンプリング速床を远加するこずにしたした。



たず、SDAチケットのバヌゞョンを䜜成するために、問題のトピックを考慮する必芁がありたす。 たた、トピックは12個のみです。 最初の8぀のトピックは40の質問で構成され、残りの120は質問です。各トピックにはチケットの特定の番号があり、最初の8぀のトピックはチケットの1぀の質問に察応したす。 生成された質問のバヌゞョンを保存するために、蟞曞蟞曞が䜿甚されたす。各蟞曞には、特定のトピックに関する質問のリストが保存されたす。 さらに、質問の順序は垞に異なっおいる必芁がありたす。 0から40たでの数字のシヌケンスを繰り返しなしで生成するメ゜ッドが必芁です。そのため、チケットずそれに応じた質問を遞択するこずになりたす。 これらすべおを念頭に眮いお、すべおの質問を生成するための次のアルゎリズムを取埗したす。





最初の2぀のステップの実装ず操䜜はロゞックに䟝存し、私の意芋では、サンプリングの芁求に応じお1回実行できたす。 3番目のステップは、質問をどれだけ異ならせたいか、各トピックの前に新䞖代を行うか、すべおのトピックに察しお1回行うかによっお異なりたす。 私は䞀床だけ生成するこずを遞択したしたが、私の意芋ではこれで十分です。 4番目のステップは最も頻繁に機胜し、この堎所ではサンプリング時に最も時間がかかるはずです。 最埌のステップは簡単で、スキップしおください。



DB接続



デヌタベヌスぞの接続を怜蚎しおください。 これを行うには、接続文字列ConnectionおよびDataReaderを栌玍する抜象クラスを䜿甚したす。



public abstract class SqlBase { protected static readonly String Connect; protected readonly MySqlConnection SqlConnection; protected MySqlDataReader SqlDataReader; static SqlBase() { Connect = String.Format("Database={0};Data Source={1};User ID={2};Password={3};CharSet=utf8", Settings.Default.Database, Settings.Default.DataSource, Settings.Default.UserId, Settings.Default.Password); } protected SqlBase() { try { this.SqlConnection = new MySqlConnection(Connect); this.SqlConnection.Open(); } catch (Exception ex) { throw new Exception(ex.Message, ex); } } }
      
      





抜象クラスが奜きなのは、䟿利な䜜業ロゞックを自分で䜜成しお隠すこずができ、掟生物が他の詳现に完党に集䞭し、他の詳现を心配せずに、基本クラスに責任を割り圓おるこずができるからです。 この堎合、掟生クラスのむンスタンスを䜜成するず、デヌタベヌスを操䜜するために必芁な条件が自動的に取埗され、クラス自䜓のロゞックを実装しお質問のサンプルを䜜成するこずができたす。



 public sealed class SqlPerceptionQuestions : SqlBase { public Dictionary<Int32, List<Question>> GetQuestion() { Generation(); GetTheme(); return Request(); } private Dictionary<Int32, List<Question>> Request() { var _collectionDictionary = new Dictionary<Int32, List<Question>>(); for(int ctr = 0; ctr < 12; ctr++) { using (var _questions = new SqlQuestions()) { if (ctr < 8) { _collectionDictionary[ctr] = _questions.GetQuestionSmall((Int16)ctr); } else { _collectionDictionary[ctr] = _questions.GetQuestionGreat((Int16)ctr); } } return _collectionDictionary; } private async void GetTheme() { //  } //       40  private List<Question> GetQuestionSmall(Int16 numTheme) { var _listQuestions = new List<Question>(); for (Int16 numCard = 0; numCard < 40; numCard++) { _listQuestions.Add(GetQuestion(numCard, numTheme)); } return _listQuestions; } //       120  private List<Question> GetQuestionGreat(Int16 numTheme) { var _listQuestions = new List<Question>(); for (Int16 numQuestion = 0; numQuestion < 3; numQuestion++) for (int numCard = 0; numCard < 40; numCard++) { _listQuestions.Add(GetQuestion(numQuestion, numTheme, numQuestion)); } return _listQuestions; } //          private Question GetQuestion(Int16 numCard, Int16 numTheme, Int16 numQuestion = 0) { //  } //    private List<String> GetResponse(Int32 questions_id) { //  } }
      
      







これは最もシンプルで最も同期したバヌゞョンで、玄2秒ず200〜400ミリ秒で動䜜したす。これにより、この間ずっずナヌザヌむンタヌフェむスがブロックされたす。 非垞に最初の実装がかなりの時間玄6秒働いたため、これは良い動䜜バヌゞョンです。 改善埌、玄2秒しか出たせんでした。



非同期サンプリングバヌゞョンの䜜成



すべおが正垞であり、すべおがすでに機胜しおいたすが、本来あるべき状態ですか 結局のずころ、コン゜ヌルアプリケヌションではなく、同期メ゜ッドブロッキングがありたす。 正しく完党に機胜するプログラムが必芁です。これは0.5秒でもブロックされたせんが、どのような負荷でも適切に機胜したす。 これを行うには、たず、GetQuestionメ゜ッドを曞き換えたす。 TAPTask-based Asynchronous Patternパタヌンに埓っお非同期にしたしょう。 誰が気にかけおいるか、むンタヌネットで読んでいるか、私が本圓に奜きなかなり良い本を持っおいたす-このトピックが非垞によく説明されおいるAlex DavisによるC5.0の非同期プログラミング、たたはここを芋おください 。 曞き換えるず、次のようになりたす。



 public async Task<Dictionary<Int32, List<Question>>> GetQuestionAsync() { return await Task.Factory.StartNew(() => { Generation(); GetTheme(); return Request(); }, TaskCreationOptions.LongRunning); }
      
      





このメ゜ッドで最も興味深いものを考えおみたしょうTask.Factory.StartNew。 バヌゞョン.NET 4.5以降では、Task.Runのバヌゞョンを䜿甚できたす。これは、䜜成時のパラメヌタヌが少ない単玔な宣蚀によっお以前のものずは異なりたす。 本質的に、Task.RunはTask.Factory.StartNewよりも䟿利なシェルであり、非同期タスクの単玔な䜜成に非垞に適しおいたすが、同時に制埡の柔軟性がやや劣りたす。 どのストリヌムが蚈算を実行するか、たたはどのように蚈画されるかをより正確に制埡する必芁がある堎合は、Task.Factory.StartNewを䜿甚したす。 興味があれば、 こちらをチェック この堎合、TaskCreationOptions.LongRunningなどのパラメヌタヌも指定したため、このオプションを䜿甚したした。このパラメヌタヌは、このタスクを長時間ずしおマヌクし、このワヌクアむテムが長期間実行され、他のワヌクアむテムをブロックする可胜性があるこずを意味したす。 このフラグは、過剰なサブスクリプションが予想されるTaskSchedulerの情報も提䟛したす。これにより、䜿甚可胜なハヌドりェアスレッドの数よりも倚くのスレッドを䜜成できたす。 このパラメヌタヌを䜿甚するず、グロヌバルおよびロヌカルキュヌを含むThreadPoolを完党に回避できたす。 「タスクスケゞュヌラ」の詳现をご芧ください。



したがっお、デヌタ収集生成の非同期実行は既に取埗されおおり、メむンストリヌムはブロックされたせん。



サンプルの䞊列化



その埌、䞊列化に進みたす。これにより、デヌタサンプリングの速床が向䞊したす。 ただし、このためには、メむンクラスを少しやり盎し、基本クラスを倉曎し、ルヌプの䞊列動䜜を確保するために別のクラスを远加しお、異なるスレッドのデヌタ競合、クリティカルセクション、倉数の問題を回避する必芁がありたす。

最初に、 SqlPerceptionQuestionsクラスに新しいネストされたクラスSqlQuestionsを䜜成したす。 質問ず回答を取埗するメ゜ッドを転送し、SqlBaseから掟生させたす。倖郚クラスでは、それらを取埗するためにメ゜ッドを残し、䞀床だけ呌び出しお、䞀連の数字を圢成したす。



SqlQuestionsクラスコヌド 



  internal sealed class SqlQuestions : SqlBase { internal List<Question> GetQuestionSmall(Int16 numTheme) { var _listQuestions = new List<Question>(); for (Int16 numCard = 0; numCard < 40; numCard++) { _listQuestions.Add(GetQuestion(numCard, numTheme)); } return _listQuestions; } internal List<Question> GetQuestionGreat(Int16 numTheme) { var _listQuestions = new List<Question>(); for (Int16 numQuestion = 0; numQuestion < 3; numQuestion++) for (int numCard = 0; numCard < 40; numCard++) { _listQuestions.Add(GetQuestion(numQuestion, numTheme, numQuestion)); } return _listQuestions; } private Question GetQuestion(Int16 numCard, Int16 numTheme, Int16 numQuestion = 0) { //  } private List<String> GetResponse(Int32 questions_id) { //  } }
      
      





ルヌプを䞊列化するには、Parallel.Forを䜿甚したす。 これは、デヌタのロヌドを耇数のストリヌムに敎理するための非垞に䟿利な方法です。 ただし、これには、1぀のConnectionで1぀のDataReaderを凊理できるため、少なくずも耇数のデヌタベヌス接続を䜜成する必芁があるずいう事実も䌎いたす。 .Requestメ゜ッドを曞き換えたす



 private Dictionary<Int32, List<Question>> Request() { var _collectionDictionary = new Dictionary<Int32, List<Question>>(); var _po = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }; Parallel.For(0, 12, _po, ctr => { using (var _questions = new SqlQuestions()) { if (ctr < 8) { _collectionDictionary[ctr] = _questions.GetQuestionSmall((Int16)ctr); } else { _collectionDictionary[ctr] = _questions.GetQuestionGreat((Int16)ctr); } } }); return _collectionDictionary; }
      
      





そしお、接続を開いた埌、それを閉じる必芁がありたす。 さらに、これはすべおサむクルで行われたす。 これをすべお実装するために、個別の掟生クラスSqlQuestionsを䜜成するこずにしたした。 接続を閉じるには、.Disposeを呌び出したす。この䞭で、閉じるずきに行う必芁があるこずを曞き留めたす。 これを行うには、最初に基本クラスで.Disposeメ゜ッドを宣蚀したす。



  public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected abstract void Dispose(Boolean disposing);
      
      





そしお、掟生クラスで異なる方法で実装したす。 なぜそう たず、デヌタベヌス接続を䜜成しお閉じた堎合はどうなりたすか 接続を開いおから閉じるず、しばらくの間玄3分間MySQL接続プヌルに配眮されたす。新しい接続を開くず、接続はプヌルから取埗されたす。぀たり、再オヌプンのための時間ずリ゜ヌスは䜿甚されたせん。 新しいメ゜ッドを起動しお、デヌタベヌス接続を開くのにかかる時間を確認したしょう。このため、接続が開かれおいるコヌドの基本クラスStopwatchに挿入し、出力にあるものを確認したす。 コヌド



 protected SqlBase() { Stopwatch st = new Stopwatch(); st.Start(); try { this.SqlConnection = new MySqlConnection(Connect); this.SqlConnection.Open(); } catch (MySqlException ex) { throw new Exception(ex.Message, ex); } st.Stop(); Debug.WriteLine("    : " + st.Elapsed.Seconds.ToString() + "  " + st.Elapsed.Milliseconds.ToString() + " "); }
      
      





開通時間
画像






最初の接続は最も長く、 SqlPerceptionQuestionsクラスの接続を開きたす。これは、メ゜ッドの党期間にわたっお開かれたす。 埌続の接続は、 SqlQuestionsクラスをむンスタンス化するずきにルヌプ内で開かれた接続です。 私のコンピュヌタヌのプロセッサヌの数を考慮に入れるず、最倧4サむクルで4぀の接続が開かれたす。 合蚈で5぀の接続が最初に開かれ、サむクルではそれらが開いたり閉じたりしたす。 したがっお、最初の5぀の接続を開くには時間が必芁です。その埌、サむクル内で叀い接続が閉じられ、新しい接続が開かれるず、接続はプヌル内にあり、必芁になるたびに単玔に発行されるため、時間ずリ゜ヌスはかかりたせん。 このため、クラスのクリヌンアップはわずかに異なる方法で実装されたす。 SqlPerceptionQuestionsクラスでは、メ゜ッドは次のようになりたす。



 protected override void Dispose(bool disposing) { if (!this.disposed) { if (SqlDataReader != null) { SqlDataReader.Close(); SqlDataReader.Dispose(); } SqlConnection.Close(); SqlConnection.Dispose(); MySqlConnection.ClearAllPools(); } disposed = true; }
      
      





SqlQuestionsクラスでも、MySqlConnection.ClearAllPools;の行を陀きたす。 結局のずころ、このたたにしおおくず、次のような状況になりたす。



開通時間
画像






ご芧のように、スレッドプヌルを絶えずクリヌニングするこずにより、接続が垞に開かれ、その埌の結果が生じたす。



MySql接続プヌル



この瞬間をより詳しく考えおみたしょう。 Connector / Net MySqlは、接続プヌリングをサポヌトしお、䜿甚率の高いアプリケヌションデヌタベヌスのパフォヌマンスずスケヌラビリティを向䞊させたす。 この機胜はデフォルトで有効になっおいたす。 接続文字列オプションを䜿甚しお、無効にするか、特性を倉曎できたす。 接続文字列は、接続プヌルに関するオプションをサポヌトしおいたす。



オプション名 䟡倀による

デフォルト
詳现
キャッシュサヌバヌのプロパティ、CacheServerProperties オフ プヌルからの接続が返されるたびに、いく぀かのシステム倉数SHOW VARIABLESのパラメヌタヌを曎新するかどうかを決定したす。 このオプションを有効にするず、接続プヌルコンテキストぞの接続が高速化されたす。 アプリケヌションには、他の接続によっお行われた構成の倉曎は通知されたせん。 このオプションは、バヌゞョンConnector / Net 6.3以降に远加されたした。
接続ラむフタむム、ConnectionLifeTime 0 接続がプヌルに返されるず、その䜜成時間が珟圚の時間ず比范され、ConnectionLifeTime倀を超えるず、この接続は砎棄されたす。 これは、運甚サヌバヌずオンラむンサヌバヌの間で負荷を分散するクラスタヌ構成で圹立ちたす。 倀がれロ0の堎合、プヌル内の接続は可胜な限り最倧の時間埅機したす。
接続リセット、ConnectionReset 停 trueの堎合、プヌルから取埗されたずきの接続状態がリセットされたす。 デフォルト倀を䜿甚するず、サヌバヌが接続を取埗するための远加の凊理サむクルを回避できたすが、接続ステヌタスはリセットされたせん。
最倧プヌルサむズ、最倧プヌルサむズ、MaximumPoolsize、maxpoolsize 100 プヌル内に存圚できる接続の最倧数。
最小プヌルサむズ、最小プヌルサむズ、MinimumPoolSize、minpoolsize 0 プヌルに入れられる化合物の最小数。
プヌリング 本圓 trueの堎合、MySqlConnectionオブゞェクトはプヌルから取埗され、必芁に応じお䜜成され、察応するプヌルに远加されたす。 定矩されおいる倀は、true、false、yes、およびnoです。


これらのパラメヌタヌを䜿甚しお、必芁に応じおプヌルを管理できたす。 䟋



デフォルトでは、MySQL Connector / Net 6.2以降、3分ごずに実行され、3分以䞊スタンバむ䜿甚されおいないであるプヌルから接続を削陀するバックグラりンドゞョブがありたす。 プヌルをクリアするず、クラむアント偎ずサヌバヌ偎の䞡方でリ゜ヌスが解攟されたす。 これは、クラむアント偎では各接続が゜ケットを䜿甚し、サヌバヌ偎では各接続が゜ケットずストリヌムを䜿甚するためです。



䞊列呌び出しスタック



興味を匕くために、たずえば.GetQuestionメ゜ッドにブレヌクポむントを眮いお、䞊列呌び出しスタックを芋るず、次のようになりたす。



䞊列スタック
画像






スクリヌンショットからわかるように、䞭断されおいるスレッドの1぀にあり、コヌルスタックから、このメ゜ッドが質問の小さなコレクションをロヌドするメ゜ッドから呌び出されたず刀断したす40。 その巊偎にはさらに3぀のスレッドがあり、そのうち2぀はコレクションに質問を远加するための行でこの時点で停止したす。このスレッドは、小さな質問のコレクションも凊理したす。 そしお最埌のスレッド4は凊理に埓事しおおり、この時点で質問に察する回答を受け取りたす。倧芏暡なコレクション120からの質問に察しおのみです。 これらの4぀のスレッドはすべお䞊列ルヌプで䜜成され、残りのルヌプ反埩スレッドずほが同時に動䜜したす。 これらのスレッドは、プログラム内のスレッドの合蚈数に含たれ、そのうち8぀は残りの4぀がプログラムの他のタスクを解決したす。



最埌の仕䞊げ-䟋倖凊理



そしお最埌に、プログラムが機胜するためには、䟋倖凊理が必芁です。 突然、フィヌルドたたは他のパラメヌタがデヌタベヌスから倉曎されたか、プログラム自䜓で予期しない゚ラヌが発生したした。 GetQuestionAsyncメ゜ッドを曞き換えたす



 public async Task<Dictionary<Int32, List<Question>>> GetQuestionAsync() { return await Task.Factory.StartNew(() => { try { Generation(); GetTheme(); return Request(); } catch (AggregateException ex) { throw new AggregateException(ex.Message, ex.InnerExceptions); } catch (Exception ex) { throw new AggregateException(ex.Message, ex.InnerException); } }, TaskCreationOptions.LongRunning); }
      
      





AggregateException䟋倖凊理は、䟋倖が発生した堎合、Parallel.Forルヌプがこのタむプの゚ラヌを発生させるずいう事実に関連しおいるため、凊理しお呌び出し元に枡す必芁がありたす。 䞊列ルヌプがこの皮の゚ラヌを生成するのは論理的です。 この瞬間をより詳现に考えおみたしょう。このため、.GetQuestionのSqlク゚リを倉曎し、デヌタベヌステヌブルに存圚しないパラメヌタヌの1぀を故意に誀っお瀺したした。 取埗するもの



゚ラヌ
画像






さらに、デバッグを続行するず、合蚈でこの䟋倖が4回発生したすが、これは非垞に論理的です。 1぀の理由に関連しおいるにもかかわらず、4぀すべおを凊理するには、䜕らかの方法でそれらを配眮する必芁がありたす。AggregateExceptionが適しおいたす。



䟋倖凊理は、.GetThemeメ゜ッドで䟋倖が発生した堎合、䟋倖が1぀あり、それもキャッチする必芁があるずいう事実に関連しおいたす。



呌び出しコヌドは次のずおりです。



 private async void Button_Click(object sender, RoutedEventArgs e) { Stopwatch st = new Stopwatch(); st.Start(); try { SqlQuest = new SqlPerceptionQuestions(); collectionQuest = await SqlQuest.GetQuestionAsync(); } catch (AggregateException ex) { ex.ShowError(); } catch(Exception ex) { ex.ShowError(); } finally { if (SqlQuest != null) SqlQuest.Dispose(); } st.Stop(); Debug.WriteLine("  : " + st.Elapsed.Seconds.ToString() + "  " + st.Elapsed.Milliseconds.ToString() + " "); Debugger.Break(); }
      
      







最埌に...



䞀般に、䞊列バヌゞョンのサむクルを曞いたずきに、デヌタベヌス接続の頻繁なオヌプンずクロヌズがどれほど倧きな圱響を䞎えるかに぀いお考えたした。 フォヌラムにアクセスしお賢い人に尋ねるず、接続プヌルに぀いお孊びたした。 それから圌は圌自身に尋ねたした、それを䜜成する必芁があるか、それは暗黙的に䜜成されたすか 私は少し実隓しおMySQLのドキュメントを読みたした。その結果、このこずは非垞に成功しおいるずいう結論に達したした、接続プヌルです



倉曎埌のすべおのコヌド
 //  public abstract class SqlBase : IDisposable { protected static readonly String Connect; protected readonly MySqlConnection SqlConnection; protected MySqlDataReader SqlDataReader; protected Boolean disposed; static SqlBase() { Connect = String.Format("Database={0};Data Source={1};User ID={2};Password={3};CharSet=utf8;CacheServerProperties=true", Settings.Default.Database, Settings.Default.DataSource, Settings.Default.UserId, Settings.Default.Password); } protected SqlBase() { Stopwatch st = new Stopwatch(); st.Start(); try { this.SqlConnection = new MySqlConnection(Connect); this.SqlConnection.Open(); } catch (Exception ex) { throw new Exception(ex.Message, ex); } st.Stop(); Debug.WriteLine("    : " + st.Elapsed.Seconds.ToString() + "  " + st.Elapsed.Milliseconds.ToString() + " "); } ~SqlBase() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected abstract void Dispose(Boolean disposing); } //     public sealed class SqlPerceptionQuestions : SqlBase { public async Task<Dictionary<Int32, List<Question>>> GetQuestionAsync() { return await Task.Factory.StartNew(() => { try { Generation(); GetTheme(); return Request(); } catch (AggregateException ex) { throw new AggregateException(ex.Message, ex.InnerExceptions); } catch (Exception ex) { throw new AggregateException(ex.Message, ex.InnerException); } }, TaskCreationOptions.LongRunning); } private Dictionary<Int32, List<Question>> Request() { var _collectionDictionary = new Dictionary<Int32, List<Question>>(); var _po = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }; Parallel.For(0, 12, _po, ctr => { using (var _questions = new SqlQuestions()) { if (ctr < 8) { _collectionDictionary[ctr] = _questions.GetQuestionSmall((Int16)ctr); } else { _collectionDictionary[ctr] = _questions.GetQuestionGreat((Int16)ctr); } } }); return _collectionDictionary; } private void GetTheme() { } private void Generation() { } protected override void Dispose(bool disposing) { if (!this.disposed) { if (SqlDataReader != null) { SqlDataReader.Close(); SqlDataReader.Dispose(); } SqlConnection.Close(); SqlConnection.Dispose(); MySqlConnection.ClearAllPools(); } disposed = true; } //        internal sealed class SqlQuestions : SqlBase { internal List<Question> GetQuestionSmall(Int16 numTheme) { var _listQuestions = new List<Question>(); for (Int16 numCard = 0; numCard < 40; numCard++) { _listQuestions.Add(GetQuestion(numCard, numTheme)); } return _listQuestions; } internal List<Question> GetQuestionGreat(Int16 numTheme) { var _listQuestions = new List<Question>(); for (Int16 numQuestion = 0; numQuestion < 3; numQuestion++) for (int numCard = 0; numCard < 40; numCard++) { _listQuestions.Add(GetQuestion(numQuestion, numTheme, numQuestion)); } return _listQuestions; } private Question GetQuestion(Int16 numCard, Int16 numTheme, Int16 numQuestion = 0) { } private List<String> GetResponse(Int32 questions_id) { } protected override void Dispose(bool disposing) { if (!this.disposed) { if (SqlDataReader != null) { SqlDataReader.Close(); SqlDataReader.Dispose(); } SqlConnection.Close(); SqlConnection.Dispose(); } disposed = true; } } }
      
      








All Articles