Silverlight をインストールするには、ここをクリックします*
Japan変更|すべてのMicrosoft のサイト|サインイン
MSDN
|MSDN ライブラリ|デベロッパー センター|ダウンロード情報|開発ツール製品|コミュニティ|ご意見・ご要望|サイトマップ
MSDN Home > 連載コラム > Diving Into Data Access > データ リーダの秘密を探る

データ リーダの秘密を探る

Dino Esposito
Microsoft Corporation

April 12, 2001
日本語版最終更新日 2001年6月18日

この記事は、もともと MSDN Online Voices のコラム "Diving Into Data Access" に掲載されたものです。

ADO.NET についてお話するとき、ADO を無視することは困難です。 ほかに理由がなければ、データ アクセス用の一部の .NET に限定されたクラスの中で ADO の名前が ハード コードされているので、最終的には ADO に言及することになります。

もっとも単純なことは、ADO.NET という名前が、単純に .NET に適合する ADO という示唆を含んでいることです。 この定義は真ですが、もっと詳しい説明が必要になります。

ADO と ADO.NET はデザイン センタが異なります。 しかし、両方とも論理的には同じ機能のセットを提供しようとしています。 行おうとしていることだけが同じで、それぞれが独自の設計パターンに注目しています。 その結果、旧バージョンとの互換性に関連する問題を引き起こす 2 つのかけ離れた API を持つことになります。

しかし、構文と意味の直接的な違いを除いて考えれば、 Microsoft の最新のデータ アクセス戦略の基盤である共通の要素を認識できます。

前回のコラムで、.NET の管理されたプロバイダが、UDA の自然の法則による OLE DB のダーウィンの 進化論の次の段階であることを説明しました。 同様に、ADO.NET は ADO の自然な進化の次の段階です。 進化という言葉はダーウィンの進化論を意識したものです。

進化は新しい環境への適応に関係しています。 新しい環境は .NET です。したがって、ADO は新しいコンピュータ処理の事例に適合するように 動作や姿勢を修正することが要求されます。 ADO はこのような進化による変更を経験する唯一のソフトウェアというわけではありません。 進化による変更を経験するものには OLE DB、COM(+)、MSMQ、および ASP があります。

ADO を使ってレコードを読み取る

ADO は、接続された 2 層アプリケーション (大部分) がより多階層の Web ベースのアーキテクチャに 移行していく過程に導入され、成長してきました。 全体的な ADO オブジェクト モデルのデザインはこの推移を反映しています。

Web は、より多くの情報がダウンロードされクライアントでキャッシュされるような、 非接続のモデルの採用を余儀なくしてきました。 ADO 内では、Recordset オブジェクトに隠された 2 つの考え方があります。

Recordset ではサーバー カーソルを使用できます。 サーバー カーソルはデータ ソースに常時接続する考え方です。 同時に、同じオブジェクトの別の側面として、非接続の状態で作業できます。 次の 2 つの方法でレコードの適切な表現を構築できます。 1 つは、データ ソースから行をフェッチし、その後接続を解除する方法です。 もう 1 つは、固定スキーマを持つ XML ファイルも含めて、必要な情報をすべてクライアントのディスク ファイル から読み取る方法です。

ADO の非接続の機能は、等価な API で接続と非接続の機能が使用できるように注意を払って、 Recordset オブジェクト インターフェイスに装備されました。 その結果、Recordset は、やや複雑で、重要な管理が必要な扱いにくいものになりました。

いずれにせよ、ADO は次の 3 つの方法でデータを取得できます。

  1. サーバー カーソルを使用する常時接続を使用して。

  2. レコードの集合全体を読み取り、処理するために特別に考慮された、高速で、読み取り専用の、 前方参照のみのメカニズムを使用して。

  3. 静的なスナップショットを取得し、データ ソースから切断された状態でそのスナップショットを処理し、 その後変更を送信する方法で。

どの方法で作業する場合でも、常に Recordset オブジェクトを使用し、 適切なカーソルの種類とカーソルが機能する場所を選択することにより操作モードを選択します。 (詳細については、ADO のドキュメント を 参照してください。)

明白な欠点は、Recordset オブジェクトをいかに最適化し、十分に設計しようとも、 実際に必要である以上のものをメモリに読み込むことです。 さらに、入力パラメータを処理し、おそらくそのパラメータの値を確定するために、 余分な層のコードを追加します。 ADO のプログラミング インターフェイス全体を考えてみても、 これは絶対に必要なことで、パフォーマンスにも影響します。

ADO.NET は、昔の "分割し支配せよ(divide et impera)" のモットーを適用することにより、 データの読み取り構造を単純化します。 ADO.NET は 2 つの新しいオブジェクトを導入します。 これらのオブジェクトの名前は特に馴染みのあるものではありませんが、 確かによく知られた機能を公開します。 そのオブジェクトとは DataReaderDataSet です。

DataReader オブジェクトは、ADO.NET で ADO の読み取り専用の、前方参照のみの 既定のカーソルに相当するものです。 DataSet は、データの静的なスナップショットを使ってプログラムから設定できるコンテナです。 この意味では、非接続のレコードセットのリポジトリと見なすことができます。 ADO.NET オブジェクト モデルにはレコードセット オブジェクトは存在しませんが、 DataTable オブジェクトが .NET で非接続のレコードセットに相当します。

ADO.NET には、サーバー カーソルの明示的なサポートはありません。 将来のバージョンでは、データ プロバイダが SQL Server 7.0 以降のとき、 ADO.NET がサーバー カーソルをサポートすることを期待できます。

現時点でサーバー カーソルを必要とする場合は、 .NET アプリケーションに ADO タイプ ライブラリをインポートし、 それを使ってコードを作成します。 これが適切か、不適切か、それとも不快なことかについてより詳しく検討する場合は、 以前のコラム「.NET アプリケーションにおける ADO の不安定な立場」を参照してください。

ADO.NET は、高度な相互運用および非接続の環境でシームレスに機能するように、 XML を意識して設計されています。 この観点から、今日大多数の人々が作成および計画している種類のアプリケーションにとっては、 ADO.NET の方が ADO よりも明らかに適しています。 これは、コーディングを単純化し、Web ベースのクライアントを持つデータ アクセス コンポーネントの作業を最適化します。

XML/HTTP サポートを備えた SQL Server 2000 のような Web が有効なデータベース サーバーを持っていれば、 さらに適切に機能します。 ところで、最新の XML for SQL Server 2000 の Web Release 1 を確認することを忘れないようにしてください。 XML for SQL Server 2000 では、挿入、更新、および削除のサポートが強化され、 SQL Server のテーブルに大規模な XML ファイルを読み込むことに対するサポートが強化されています。

実際の構文に違いがあるにもかかわらず、ADO と ADO.NET の背景にあるプログラミングの考え方をできる限り統一し、 データ アクセスの開発者の学習曲線を平坦にし、精通しなければならない新しい概念を軽減するようにしています。

ADO が存在する痕跡

いくつかの ADO.NET のコードの中に、しばしば ADO が存在していることを示す痕跡を見出すことがあります。 例が必要ですか ? 次の ADO コードを見てみましょう。 このコードはやや趣が異なりますが、多くの ASP ページや中間層のコンポーネントの本体に埋め込まれて実行されます。

Set oCN = Server.CreateObject("ADODB.Connection")
oCN.Open strConn
Set oCMD = Server.CreateObject("ADODB.Command")
Set oCMD.ActiveConnection = oCN
Set oRS = oCMD.Execute(strCmd)

このコードでは、明示的に Connection オブジェクトと Command オブジェクトを 作成しており、このコードを実行すると新しい Recordset オブジェクトが返されます。 Connection オブジェクトには、希望するカーソルの種類とカーソルを実行する場所に関する情報が含まれます。 adOpenStatic 型のカーソルを選択した場合は、場所にはクライアント側を選択する必要があり、 この場合は安全に接続を閉じることができ、その後レコード全体を処理できます。 それ以外の場合は、フェッチした行全体のナビゲートが完了するまで、接続を開いたままにする必要があります。

レコードセットを取得後は、以下のようにループを使用してスクロールします。

While Not oRS.EOF
Response.Write(oRS("lastname") & "<BR>")
oRS.MoveNext
Wend

この種のコードはこのままでは ADO.NET で機能しません。 ただし、ADO.NET DataReader オブジェクトを使ってコードを記述すれば、 少なくとも機能面ではほぼ等価になります。

まず、コマンドの実行を管理する特別なオブジェクトを作成します。 基本 .NET クラスは DBCommand です。 通常このクラスは使用しません。 ADOCommand クラスや SQLCommand クラス、およびユーザー定義の派生クラスのような、 よりシリアル化された .NET クラスの 1 つを使用します。 DBCommand はデータ ソースが理解できるコマンドを表します。

SQLCommand クラスは、SQL Server コマンドの機能を埋め込みます。 それに対して、ADOCommand は OLE DB プロバイダに対して COM の手法で行う任意のアクセスをラップします。 これらのオブジェクトは同じプログラミング インターフェイスを持ち、多くのコンストラクタを持っています。 もっとも頻繁に使用されるのは以下のものです。

Dim oCMD As New SQLCommand(strCmd)

また、接続オブジェクトをコマンドに関連付ける必要があります。 ADO と同様に、この関連付けは明示的にも暗黙的にも行うことができます。 明示的に行う場合は、通常対象とするデータ プロバイダに応じて ADOConnection オブジェクトまたは SQLConnection オブジェクトを作成します。 ADOConnection は COM Interop ブリッジを経由して OLE DB プロバイダに接続します。 それに対して、SQLConnection は登録された SQL Server に接続します。

Dim oCN As New SQLConnection(strConn)

DBCommand ベースのオブジェクトの別のコンストラクタを使用することにより、 ADO.NET クラスが DBCommand の ActiveConnection プロパティを使って公開する 暗黙の接続オブジェクトを作成するように設定できます。

Dim oCMD As New SQLCommand(strCmd, strConn)
oCMD.ActiveConnection.Open()

明示的な接続オブジェクトを使用することにより、 コード内で同じ接続オブジェクトを繰り返し再利用できることは言うまでもありません。 同じオブジェクト変数を使用して別のデータ ソースに接続できます。 その結果、アプリケーションがメモリを使用する量をわずかながら削減できます。

どちらの方法で接続オブジェクトを作成したとしても、 その接続オブジェクトは常に明示的に開き、明示的に閉じる必要があることを覚えておいてください。

oCMD.ActiveConnection.Open()

ストアド プロシージャを起動する場合は、 CommandType プロパティを設定することにより ADO.NET ランタイムにそのことを通知します。

oCMD.CommandType = CommandType.StoredProcedure

上記で説明した ADO コードに相当する ADO.NET コードは次のようになります。

Dim oCN As New SQLConnection(strConn)
Dim oCMD As New SQLCommand(strCmd, oCN)
Dim oDR As SQLDataReader = Nothing
oCMD.ActiveConnection.Open()
oCMD.Execute(oDR)
While(oDR.Read)
      Response.Write(oDR["lastname"].ToString() + "<br>")
End While

オブジェクトが異なる名前を持ち、異なるメソッドとプロパティのセットを持つにもかかわらず、 論理的なスキーマは ADO で行っていることと非常によく似ています。

この ADO.NET コードでは、SQLDataReader オブジェクトが ADO Recordset の役割を演じます。 ただし、多くの Recordset の機能が不足しています。 SQLDataReader は、単に前方参照のみの手法でデータをスクロールすることに優れているだけです。 ADO.NET では、このような小さな機能ごとに小さなオブジェクトを持つことになります。

さらに、SQLDataReader オブジェクトは読み取りが完了すると、1 つ前方の位置に 自動的にレコード ポインタを移動するので、プログラマにはより馴染みやすいものになっています。 つまり、Read メソッドには Recordset の MoveNext メソッドに相当する機能が 組み込まれています。 既定の位置がストリームの先頭で、最初のレコードよりも前にあることを考えてください。 任意のデータ フィールドにアクセスする前に常に Read を呼び出す必要があります。

要するに、COM ブリッジを構築したくなければ、既存の ADO コードをすべて書き直す必要があります。 しかし、学習すべき新しい概念はごくわずかです。 ADO の開発者は既に ADO.NET の道半ばまで達しています。

ADO.NET データ リーダ

ADO.NET には 2 つのデータ リーダ オブジェクト、SQLDataReaderADODataReader が あります。 これらは共に System.Data.Internal 名前空間の抽象クラスである DBDataReader から 派生されています。

このようなクラスは非常に基本的なコンポーネントの動作を定義します。 コンポーネントは接続を経由してデータ ソースにバインドされ、 フェッチした行のセットを前方にスクロールさせます。 以下の図は ILDASM がクラス インターフェイスを表示する方法を示しています。

DBDataReader クラス

図 1. DBDataReader クラス

以前に説明したように、.NET Framework は DBDataReader から 2 つのクラス、 ADODataReaderSQLDataReader を作成します。 前者は OLE DB 経由のデータ アクセスを管理します。 後者は SQL Server の管理されたプロバイダを対象とします。

以下に、2 つのクラスを C# で宣言する方法を示します。

public sealed class ADODataReader : DBDataReader, IDataReader,
   IDataRecord 
public sealed class SQLDataReader : DBDataReader, IDataReader,
   IDataRecord, ISQLDataRecord

新しい sealed 属性を使用していることに注目してください。 この属性は、継承に関してクラスを隠蔽します。

完全を期すために、同じ宣言が Visual Basic.NET ではどうなるかを見ておきましょう。

NotInheritable Public Class ADODataReader
   Inherits DBDataReader
   Implements IDataReader, IDataRecord
NotInheritable Public Class SQLDataReader
   Inherits DBDataReader
   Implements IDataReader, IDataRecord, ISQLDataRecord

Visual Basic.NET の宣言は、作業の論理的な違いを示すために異なるキーワード、 ImplementsInherits を使用させているので、より情報量が多くなっています。 この宣言は、データ リーダ クラスが最小限 DBDataReader から継承されていて、 2 つ以上の特定のインターフェイスを実装していることを明確に示しています。

ADODataReader と SQLDataReader は共にそこから継承できません。 この理由は何でしょう ?

データ リーダは単なるオブジェクトで、特定のデータ ソースから取り出されたデータ レコードの 前方参照のみのストリームを高速に読み取るメカニズムを提供します。 データ リーダ オブジェクトは、データ ソースとは無関係に同じ変更不可のプログラミング インターフェイスが 公開されることが期待されます。 より細かい継承を有効にすると、プログラミング インターフェイスに一部隠された機能を持つように、 データ リーダを専用化することになります。

このデザインは、カスタム データ ソースに対するデータ リーダを作成する必要がある場合に、 特に役に立ちます。 この場合、単に DBDataReader から継承し、2、3 のインターフェイスを実装するだけです。

SQL Server テーブルのスクロール方法を変更する必要がある場合は、 常に集合体に頼ることができます。 基本的には、標準の SQLDataReader のインスタンスを内部的にホストし、 独自のデータ リーダ インターフェイスを用意するメソッドを利用するデータ リーダ クラスを作成します。

シール クラスから

図 2. シール クラスから "継承する" ために集合体を使用

データ リーダ クラスを一意にし、非常に特殊なものにしているのは、 そのクラスが公開するインターフェイスのセットです。 このようなインターフェイスには、IDataReaderIDataRecord があります。 さらに、SQL Server データ リーダは ISQLDataRecord を実装します。 このインターフェイスは SQL Server 固有のデータ型を公開します。

データ リーダが非常に特殊なオブジェクトであることはこれで明らかになったと思います。 これらのオブジェクトがほかの .NET クラスよりも多少機能を持っているからではなく、 これらのオブジェクトが特定の設計規則に従っているので特殊なのです。 たとえば、データ リーダは DBCommand の派生オブジェクトの Execute メソッドを使用してのみ作成できます。 コンストラクタを直接使用することによっては作成できません。 繰り返しますが、これらのオブジェクトが ADO.NET で演じる役割にその理由を見出す必要があります。 データ リーダは、"接続された" データ コンシューマと特定のデータ プロバイダ間の "変更不可" のインターフェイス である必要があります。

レコードをフェッチするためになぜこのような変更不可のインターフェイス (OLE DB でも同様です。OLE DB と .NET の考察 を参照してください。) が必要なのでしょうか ? データ リーダは接続された環境で機能するからです ! 実際、非接続のオブジェクトであるデータ セットはシール クラスではなく、 コンストラクタにより作成できます。

データ リーダが使用中の間は、関連する接続オブジェクトはビジー状態になります。 接続オブジェクトがデータ リーダを処理している間に実行できる唯一の操作は、 データ リーダの Close メソッドを使用して接続を終了することです。

OLE DB と SQL Server のデータ リーダ (ADODataReader クラスと SQLDataReader クラス) は、一時的な内部オブジェクトの作成を回避する極めてスリムなオブジェクトです。

基本的には、クライアントがデータを読み取るために繰り返し呼び出しを行う場合 (つまり、ループ内で Item プロパティまたは GetString メソッドを呼び出す場合)、 常に同じオブジェクトを使用してデータが渡されます。 このため、データ リーダを使用してアクセスされるデータは読み取り専用と考える必要があります。 また、中間オブジェクトを使用してこのデータを操作する場合は、十分な注意が必要です。

IDataReader インターフェイス

IDataReader インターフェイスは極めて単純で、結果セットによるナビゲーションと 行によるナビゲーションの 2 つのレベルのナビゲーションを提供します。

IDataReader を使って、複数の SELECT ステートメントを実行し、結果を返すストアド プロシージャ からのさまざまな結果をナビゲートします。 データ リーダの既定の位置は最初の結果です。 NextResult を使用して次の結果に移動し、 HasMoreResults を使用して結果が残っているか確認します。

データ リーダの内部ポインタが特定の結果に設定された後、その行をナビゲートできます。 Read を使用して次の行に移動し、Close を使用して読み取りを終了します。 HasMoreRows は、ストリームの現在位置と最終位置の間に行が残っているかどうかを示すプロパティです。

おわかりのように、このインターフェイスのメソッドが対応するナビゲーション モデルは 1 つだけです。 1 つのレコードをドリル ダウンするには、別のインターフェイス IDataRecord を使用します。

IDataRecord インターフェイス

このインターフェイスは、行で使用できるひとかたまりの情報にアクセスするように設計されています。 もっとも頻繁に使用するプロパティは明らかに Item です。 これはデータ リーダ オブジェクトのインデクサです。 名前によって特定のフィールドにアクセスする場合は常にこのプロパティを使用します。

Dim oDR As SQLDataReader = Nothing
oCMD.Execute(oDR)
While(oDR.Read)
      Response.Write(oDR["lastname"].ToString() + "<br>")
End While

FieldCount は、現在レコードのフィールド数を取得するプロパティです。

インターフェイスの残りは、GetXXX というたくさんのメソッドです。 XXX にはデータ型を表します。 つまり、GetStringGetInt32 などといったメソッドです。 たとえば、GetInt32 メソッドは指定したフィールドの 32 ビットの符号付き整数値を返します。 フィールドは名前または位置で識別できます。

oDR[0].ToInt32()
oDR["lastname"].ToInt32() 

また、Object 型の汎用的な値を使用してデータを読み取ることができます。 この場合は GetValue を使用します。

説明しておく必要のある特別なメソッドに GetData メソッドがあります。 基本的には、フィールドが IDataReader を実装する .NET オブジェクトを再帰的に 指している場合に、GetData がこのようなオブジェクトを返します。

SQLDataReader オブジェクトには ISQLDataReader も実装されています。 このインターフェイスは、SQL Server 固有のデータ型に対応する一連の GetXXX メソッド を追加します。 たとえば、SQLGuid 型を処理する GetSQLGuid メソッドがあります。

まとめ

このコラムは ADO と ADO.NET のデータ読み取り機能を比較することから始めました。 レコードを読み取る ADO の 3 つの手法のうち、2 つだけが "本質的に" ADO.NET で利用でき、 ADO とは異なるオブジェクトを使用します。

ADO.NET は (いまのところ) サーバー カーソルを実装していません。 しかし、より効果的にデータ ソースからデータ レコードの前方参照のみのストリームを読み取ります。 さらに、非接続のデータのセットに対するサポートが大幅に強化されました。

この記事の大部分は、OLE DB プロバイダと .NET の管理されたプロバイダで利用できる データ リーダ オブジェクトを説明しています。 今後のコラムでは、DataSets とサーバー カーソルについて詳しくお話していくつもりです。

ダイアログ ボックス: ここ 2 年間私は何をやってきたんだろう ?

なに ? Visual Basic や ASP で OLE DB が利用できないって ? じゃ、ここ 2 年間に私がやってきたことは何なの ? これまでに、接続文字列って聞いたことがありますか ? C++ のプログラマだけが OLE DB を使用できるんですか ? 私のすべての VB DLL にそのことを話さないでください !

「VB または ASP から OLE DB を使用できません、ここ 2 年間私は何をやってきたのでしょう ?」 別の哲学的な質問のような気がします。

1 つには、前回のコラムの問題になっている文章には、ここでは重要になる余分な副詞を含んでいます。 それは、「直接」ということばです。

私は、あなたはこの 2 年間単に ADO を経由して OLE DB コンシューマを使用してきたのだと推測します。 特に、あなたは対象の OLE DB プロバイダを識別するために接続文字列を使用してきました。 いいでしょう。 これは、Visual Basic® および ASP から簡単に行えます。 と言うよりは、ADO はこのためだけに発明されたのです !

実際、ADO は OLE DB の直接呼出しの上位に構築されたオートメーション ラッパです。 Visual Basic を含めたスクリプト ベースの環境から OLE DB の使用を許可することを明確な目的としています。

OLE DB は、コンシューマ (クライアント) とプロバイダ (サーバー) 間のデータ駆動型の通信を 許可する COM ベースのプロトコルです。 "COM ベース" であることにより、中継なしに「直接」使用するためには次のことを行う必要があります。

  1. プロバイダを表す COM コンポーネントのインスタンスを作成します。

  2. データ ソースを表すインターフェイスにクエリします。

  3. セッションを作成し、適切なインターフェイスを返すことができるそのインターフェイスの メソッドを呼び出します。

  4. そのセッション インターフェイスを使用して、コマンドを作成し、別のより適切な インターフェイスを取得します。

  5. コマンド インターフェイスを使用して、コマンドを実行し、 IRowset インターフェイスを獲得します。このインターフェイスがあなたが使おう としている ADO レコードセットを許可するもっとも身近な OLE DB です。

この時点で IRowset インターフェイスを保持していますが、 レコード フィールドの内容にはまったく近づいていません。 アクセサ要素を定義する必要があります。基本的にはレイアウトを十分理解しているバッファです。 さらに、このバッファに実際のデータを設定する方法を考案する必要があります。

これが「実際」の OLE DB です。 これが悪夢のようなことであることに同意します。

インターフェイスにクエリすることやポインタや型を管理することに限らず、 これらの作業は C++ でさえやや複雑でうんざりする作業です。

原則的には、この直接的なメカニズムをすべて Visual Basic のコードそのものに移植することを考えることはできます。 実際にはこれを行おうとは思わないでしょう。 私は、かつて誰かが実際にこれを行おうとしたとは思いません。 さらに、ASP ページの内部からは行うことすらできません。

プログラミング インターフェイスが非常に複雑な場合、ラッパが唯一合理的な方法です。 主に機能を設計した言語に厳密に依存する機能によりラップする層は増えるので、 層をラップすることがこの作業の実現に着手する方法です。

したがって、ADO は Visual Basic や ASP 用に最適化された COM オートメーション ラッパです。 さらに、 Visual Studio® 6.0 の ATL コンシューマ クラスは C++ の開発者向けの優れた鎮痛剤です。

OLE DB が公開する本来のインターフェイスを C++ を使って呼び出すことは、 データの取得と設定を行う最も高速な方法です。 実際のところ、OLE DB データ アクセスに関して ADO 層の影響を示す信頼できる統計が存在するかどうかはわかりません。 私の個人的な経験に基づけば、OLE DB プロバイダにネイティブにアクセスしている C++ データ コンシューマは、 (ATL ラッパを使用しても、しなくても) 通常 ADO を使用した Visual Basic コンポーネントよりも約 2 倍高速です。

その反面、C++ を使ってコードを記述することは明らかにエラーを引き起こしがちです。 また、より優れたハードウェアを導入することにより、パフォーマンスへの影響を軽減できます。 (無数の実際のプロジェクトがこの方法で問題を解決してきました。)

要約すれば、C++ は OLE DB の言語ですが、ADO は容易で、優れた方法で、Visaul Basic および ASP アプリケーションから OLE DB を使用できるようにしています。

.NET の管理されたプロバイダでは、あなたが最も快適に感じられる言語を使って、 最終的にデータにアクセスできます。


Dino EspositoWintellect Leave-ms に勤務していて、そこで ADO.NET と ASP.NET のトレーニングとコンサルティングを行っています。彼は VB-2-The-Max Leave-ms の共同設立者で、MSDN Magazine に Cutting Edge コラムを連載しています。



Microsoft