Silverlight をインストールするには、ここをクリックします*
Japan変更|すべてのMicrosoft のサイト
MSDN
|MSDN ライブラリ|デベロッパー センター|ダウンロード情報|開発ツール製品|コミュニティ|ご意見・ご要望|サイトマップ
MSDN Home > 連載コラム > Adventures in Visual Basic .NET > COM+ よ、汝はいずこに

COM+ よ、汝はいずこに

Rockford Lhotka
Magenic Technologies

April 22, 2002
日本語版最終更新日 2002 年 7 月 15 日

vbEnterprise.exe サンプル ファイルは MSDN® Code Center からダウンロードできます。

.NET の EnterpriseServices からすべての COM+ 機能へのアクセスが提供されると言うことは簡単です。確かにその通りですが、単に名前が違うだけで COM と同様に .NET で COM+ サービスを使うことができると誤解される恐れがあるため、もう少し説明が必要です。

実際、COM+ が COM 開発にもたらした圧倒的メリットの一部は、.NET においてはあまり顕著ではありません。逆に、Microsoft® Visual Basic® .NET での開発では、Visual Basic 6.0 の開発者が利用できなかった COM+ の機能を完全に利用できます。

また、COM+ は Microsoft Transaction Server (MTS) と同じではないことも指摘しておく必要があります。Microsoft Transaction Server は、Microsoft Windows NT® 4.0 のアドオン パッケージでした。COM+ は、MTS のすべての機能および付加機能を提供し、Windows® 2000 および Windows XP に直接統合されています。EnterpriseServices を使用して .NET で記述されたコードは COM+ で実行可能ですが、MTS では実行できません。

エンタープライズ アプリケーションを開発するからと言って必ずしも EnterpriseServices を使用する必要がある訳ではありません。EnterpriseServices には多くのサービスと機能が含まれていますが、これらはアプリケーションでの有効性を個別に評価する必要があります。

これらのサービスは非常に強力で多くの状況においてコーディングを大幅に簡略化できますが、その使用は、機能がアプリケーションの要件に対して有効である場合のみに限定する必要があります。

この記事では、これらのサービスについて考察し、それぞれが有効である場合のガイドラインをいくつか紹介します。その後、最も一般的な機能である 2 フェーズ トランザクション サポートを利用するコードを作成します。

EnterpriseServices から提供される主要なサービスを次の表にまとめます。

サービスまたは機能 説明 使用の可否
2 フェーズ トランザクション保護 2 つ以上のデータベースの更新時において、更新に対するトランザクション保護を提供します。この機能は単一のデータベースの更新には必要ありません (単一のデータベースに複数のテーブルがある場合でも必要ありません)。

この機能を使用すると、コンポーネント間のトランザクションの調整を心配せずに、データを更新するコンポーネントを個別に多数作成することもできます。

"自動トランザクション" を提供します。

この機能は、トランザクションを更新するデータベースが 2 つ以上ある場合に使用します。

再利用可能なデータ アクセス コンポーネントを作成するときに使用します (実際にデータ アクセス コンポーネントを再利用するユーザーは非常に少数です)。

この機能を使用すると、独自のトランザクションの管理を心配する必要がなくなります。同じ Connection オブジェクトと Transaction オブジェクトを手動で使用する代わりに、EnterpriseServices のトランザクション保護を使用してコードを簡略化します。

単一のデータベースのみを更新し、他の構成から呼び出される再利用可能データ コンポーネントを作成しない場合は、使用しないでください。

ジャスト イン タイム (JIT) アクティベーション 特定のメソッド呼び出しに使用するオブジェクトを作成し、メソッドが完了した時点でそれを破棄します。 他の機能に EnterpriseServices を使用している場合にのみ使用します。

この機能は XML Web サービスおよび .NET Remoting から Singlecall オブジェクトで自動的に取得されます。

疎結合イベント (LCE) COM イベントを使用したパブリッシャ/サブスクライバ イベント モデルを提供します。 サブスクライバ オブジェクトが常時実行されず、イベントが公開されるときにサブスクライバ オブジェクトを起動する必要がある場合に使用します。

パブリッシャとサブスクライバが常時実行されている場合は、.NET イベントの使用を参照し、この機能を実行するように委任します。

オブジェクト構築文字列 コンポーネントが単一の構成文字列を必要とする場合は、EnterpriseServices から提供できます。 他の機能に EnterpriseServices を使用している場合にのみ使用します。

通常は、アプリケーションの構成ファイルがこの目的に使用されます。

オブジェクト プーリング オブジェクト数が最少カウントと最大カウント間の範囲を維持するように同一のオブジェクトのプールを保持する必要がある場合、この機能を使用します。 同一オブジェクトのプリロード セットが必要な場合に使用します。

一度に実行するオブジェクトの最大数を制御して、オブジェクトが解放されるまでクライアントを待機させる場合に使用します。

プールに必要なオブジェクトが 1 つだけの場合は、.NET Remoting の Singleton オプションを参照します。

キュー コンポーネント この機能を使用すると、クライアント コードからオブジェクト上のメソッドを呼び出すことができます。これらのメソッド呼び出しは記録され、キューに入れられた後、将来のある時点で実際のオブジェクトで再実行されます。

キュー コンポーネントは強力で、メッセージの再試行と配信不能メッセージ処理を自動的に行い、呼び出し元からオブジェクトに対してセキュリティおよび識別情報をフローします。

クライアントがオブジェクトを使用する方法を大幅に変更せずに、アプリケーションに非同期動作を挿入するために使用します。

メッセージ再試行と配信不能メッセージ処理機能が必要な場合に使用します。

メッセージ キューのみが必要な場合は、System.Messaging 名前空間を参照します。

ロール ベースのセキュリティ 呼び出し元の ID とそのロールを基準にしてコンポーネントへのアクセスを制御できます。

CLR ロール ベースのセキュリティは TCP Remoting チャネルを使用しないので、HTTP チャネルを使用するための追加作業が必要になることに注意してください。

指定したユーザー アカウントで中間層コンポーネントを実行する場合に使用します。

これは、管理時に中間層のセキュリティの制御が必要な場合に使用します。

中間層のコンポーネント間でセキュリティ情報のフローが必要な場合に使用します。.NET Framework には、Web サービス、Remoting、および EnterpriseServices 外部のその他の中間層コンポーネントに適用できる多数のセキュリティ機能があります。

SOAP サービス Windows XP の COM+ 内から XML Web サービス としてコンポーネントを直接公開できます。

この機能は .NET ランタイムを実際に使用し、SOAP を介して COM+ アプリケーションを公開し、COM+ で実行される .NET コンポーネントに対する効率的なアクセスを提供することに注意してください。

他の機能に対して EnterpriseServices を使用し、Windows XP から SOAP を介してコンポーネントを利用可能にする場合にのみ使用します。

Windows 2000 の場合は、この記事の後半で説明する Remoting テクノロジを使用できます。

同期 複数スレッドがどのようにオブジェクトにアクセスするかを自動制御します。 アクティビティを構成するスレッドのみがオブジェクトのメソッドを呼び出すことを確実にする場合に使用します。

これは、ほとんどの Web サービス、Remoting、または JIT アクティビティ シナリオでは必要ありません。

これらすべての機能の中で、最も広く知られ頻繁に使用されているものは 2 フェーズ トランザクション サポートです。不幸にして、"2 段階" の部分が省略されることが多いので、多くの開発者がトランザクション サポートの取得に使用するテクノロジと理解し、単一データベースに対するアクセスをトランザクション面で保護することに使用されている傾向があります。

この点は慎重に評価する必要があります。2 フェーズ トランザクションを使用した場合、単一データ ベースでの操作には必要のないパフォーマンス コストが必要になります。しかし、コードの簡略化と高い柔軟性の点から自動トランザクションのメリットは魅力的です。多くの場合、EnterpriseServices トランザクションによる使用性の向上がパフォーマンスに対する影響よりも重要視されます。また、EnterpriseServices を使用する必要なく取得できる COM+ 特有の機能が多く存在するという点にも注目する価値があります。このような機能には、以下のものがあります。

  • データベース接続プーリング : ODBC、OLEDB、および ADO.NET で管理されたプロバイダによって提供されます。SQL Server 向けの .NET 管理プロバイダには高度に最適化された接続プーリング アルゴリズムがあり、OLEDB や ODBC で使用されるものに比べて非常に高速であることは注目に値します。
  • スレッド管理 : XML Web サービスには IIS および ASP.NET から提供され、Remoting サーバーには .NET Remoting サブシステムから提供されます。
  • シンプルなトランザクション : ADO.NET またはストアド プロシージャが、単一データベース内の複数のテーブルの更新に必要なすべてのトランザクション保護を提供できます。
  • キュー メッセージ : System.Messaging が MSMQ の機能へのアクセスを提供します。これは、より抽象的なレベルで MSMQ を使用してキュー メッセージだけではなく、メッセージ再試行と配信不能メッセージ処理機能も提供するキュー コンポーネントとは異なります。

この情報は、アプリケーションで EnterpriseServices の使用を要する場合と要しない場合を決定する際に参照できます。

しかし、エンタープライズ アプリケーションの作成時にトランザクションを介して複数のデータベースを更新する必要が生じる場合があります。このため、次のセクションでは、2 フェーズ トランザクション機能に重点を置いて EnterpriseServices の機能を使用するために必要なコードについて説明します。

EnterpriseServices を使用する

EnterpriseServices を利用する .NET アセンブリを記述するときは、最終的に COM+ アプリケーションを実行することになります。そのためには、.NET コードに特別な機能が必要です。結局の所、COM+ は COM ベースのテクノロジであるため、その環境で機能する一定レベルの COM 相互操作性がアセンブリに必要です。さらに、必要に応じて環境と対話できるようにするため、COM+ コンテキスト オブジェクトへのアクセスも必要です。

System.EnterpriseServices.dll を参照する

エンタープライズ サービスを利用して適切に COM+ と対話するアセンブリを作成するには、プロジェクト内で System.EnterpriseServices.dll への参照を追加する必要があります。

Visual Studio® .NET を起動して、「PubsServices」という名前で新しい "クラス ライブラリ" を作成します。次に [参照の追加] ダイアログ ボックスで、図 1 に示すように DLL への参照を追加します。

図 1. System.EnterpriseServices.dll への参照を追加する

アセンブリ属性

COM+ への .NET アセンブリ配置は、自動的に行う (同一コンピュータ上の .NET クライアント アプリケーションがアセンブリを起動する場合)、または regsvcs.exe コマンド ライン ユーティリティを使って行うことができます。いずれの場合も、アセンブリが正しく COM+ アプリケーションに読み込まれるように情報を提供する必要があります。

Visual Basic .NET プロジェクトは常に AssemblyInfo.vb ファイルで起動されます。このファイルにはアセンブリ全体に影響する設定の一部が含まれています。EnterpriseServices を処理するには、このファイルに数行のコードを追加する必要があります。

ファイルの最上部で次のように名前空間をインポートします。

Imports System.Reflection
Imports System.Runtime.InteropServices
Imports System.EnterpriseServices

次の行をファイルに追加します。

' EnterpriseServices の属性
<Assembly: ApplicationName("PubsServices")> 
<Assembly: Description("Pubs data access services")> 
<Assembly: ApplicationActivation(ActivationOption.Library)>

ApplicationName 属性は、アセンブリを配置する COM+ アプリケーションの名前を示します。このアプリケーションが既に存在する場合はアセンブリが追加されます。アセンブリが存在しない場合は自動的に作成されます。

Description 属性は、アセンブリに関する説明を人間が理解できる形で提供します。

ApplicationActivation 属性は、アセンブリを COM+ にインストールする際に新規 COM+ アプリケーションの作成が必要な場合にのみ使用されます。新しいアプリケーションが作成されるとき、この属性に基づいてライブラリ アプリケーションまたはサーバー アプリケーションのどちらとしてアプリケーションが作成されるかが決定されます。アプリケーションが COM+ に既に存在する場合、この値によって現在の設定が変更されることはありません。

キュー コンポーネント機能を使用する場合、次の行も追加します。

<Assembly: ApplicationQueuing()>

その他の EnterpriseServices 機能にも同様のアセンブリ レベルの属性がある場合があります。個別の機能とその属性の詳細については、.NET Framework SDK のヘルプを参照してください。

厳密な名前

COM+ で実行されるコンポーネントにはもう 1 つの要件があります。.NET ランタイムでアセンブリを一意に識別できるようにする "厳密な名前" が必要です。厳密な名前は、アセンブリの名前、バージョン番号、および一意の公用で構成されます。

作成者または作成組織に既に公用がある場合は、それを利用します。公用がない場合は、次のようなコマンドで sn.exe コマンド ライン ユーティリティを起動して作成します。

sn –k mykey.snk

生成されたファイルに含まれるキーを使用してアセンブリに厳密な名前を付けます。作成したすべてのアセンブリに同じ公用で署名できるように、自分の組織には 1 つのキーを設定します。

アセンブリに厳密な名前を付けるには、次のコードを AssemblyInfo ファイルに追加します。

' 厳密な名前
<Assembly: AssemblyKeyFile("mykey.snk")>

アセンブリがコンパイルされると、キーのパブリックの部分が自動的に DLL に組み込まれて DLL に厳密な名前が付けられ、EnterpriseServices で使用できるようになります。

COM+ アプリケーションを構成する

AssemblyInfo に追加した属性に十分な情報がないために COM+ アプリケーションを完全に構成できない場合があります。EnterpriseServices では、開発者が関与する必要がある属性にのみプログラムを介したアクセスが提供されます。それ以外の属性 (サーバー アプリケーションが実行されるアカウントなど) は、ソフトウェアをサーバーにインストールする際にシステム管理者が設定します。

たとえば、サーバー アプリケーションの作成時に、アプリケーションが "対話ユーザー" として実行されるように構成すると、現在ログインしているユーザーの ID が既定で使用されます。予期しない結果が発生する可能性があることに加えて、他のユーザーがログインしたときにコンポーネントが呼び出されることになりセキュリティの問題が生じるため、これは理想的ではありません。また、誰もサーバーにログオンしていないときにコンポーネントが完全に失敗する恐れもあります。

このため、事前に手動でアプリケーションを作成するか、.NET によってアプリケーションが自動作成された後に修正を加えて特定のユーザーでのみ実行するように設定を変更することが重要です。この設定は、[管理ツール] の [コンポーネント サービス] コンソールおよびアプリケーションのプロパティの設定を使用して実行します。図 2 は既定値ではなく特定のユーザーが入力された [ID] タブです。

図 2. 特定のユーザー名でアプリケーションを実行する

構成が必要なその他の詳細設定には、アプリケーションが最後に使用された後に何分間実行状態を維持するかということに加えて、削除、変更、およびデバッグの許可設定が含まれます。これらのオプションはコンポーネントのプロパティ ダイアログ ボックスの [詳細設定] にあります。

ServicedComponent をサブクラス化する

EnterpriseServices の機能を使用するクラスまたは EnterpriseServices に関連するクラスは、System.EnterpriseServices.ServicedComponent のサブクラスである必要があります。Class1.vb のコードを次のように変更します。

Imports System.EnterpriseServices

Public Class AuthorService
  Inherits ServicedComponent

End Class

このクラスは、ServicedComponent からの継承によって、EnterpriseServices を使用する機能を取得します。このクラスが使用する特定のサービスは、クラスに配置した属性、クラス内のメソッド、およびこれらのメソッドに記述したコードによって制御されます。

クラス属性

使用するサービスのほとんどでは、1 つ以上の属性をクラスに追加する必要があります。すべてのサービスに適用される属性もあります。

最も広く適用されるものに、<EventTrackingEnabled()> があります。この属性によってコンポーネントの監視が有効化され、存在するオブジェクト数とアクティブなオブジェクト数を含むコンポーネントの状態が [コンポーネント サービス] 管理コンソールに表示されます。

<EventTrackingEnabled(True)> _
Public Class AuthorService

この他の例として、オブジェクトのプールを有効化するには次の行を追加します。

<ObjectPooling(True, 10, 50)> _
Public Class AuthorService

また、同期を有効化するには、次の行を追加します。

<Synchronization(SynchronizationOption.Required)> _
Public Class AuthorService

EnterpriseServices の各機能には独自の属性セットがあります。この属性セットは、機能を使用または実装するために記述するクラスに適用する必要があります。これらの機能の詳細および例については、.NET Framework SDK のヘルプを参照してください。

メソッドの属性

また、多くのサービスには、動作をさらに細かく制御するためにメソッドに対して適用できる関連属性があります。たとえば、次の属性を適用すると、ロール ベースのセキュリティを使ってメソッドのセキュリティを保護することができます。

  <SecurityRole("Admin")> _
  Public Sub DoWork()

クラス属性の場合と同様、EnterpriseServices の各機能にはメソッドに適用される独自の属性セットがあります。各機能の詳細と例については、.NET Framework SDK のヘルプを参照してください。

この例では、最も一般的な機能である 2 フェーズ トランザクション サポートを取り上げ、トランザクション コンポーネントの作成方法を説明します。

2 フェーズ トランザクション サポート

エンタープライズ アプリケーションの多くは、複数のデータベースとの同時対話を実行します。トランザクション設定で複数のデータベースの更新を可能にする必要が生じる場合もありますが、この実行は非常に複雑です。これを処理するテクニックは 2 フェーズコミットと呼ばれ、それぞれのデータベースで更新が行われますが、すべてのデータベースで準備ができたことが確認されるまで更新はコミットされません。

2 フェーズコミットの実装に要求される複雑性はすべて、COM+ によって管理される分散トランザクション コーディネータ (DTC) によって処理されます。.NET の場合、開発者は COM+ と直接対話する必要はなく、EnterpriseServices を利用してほとんどの詳細を処理できます。

他のサービスの場合と同様、トランザクション サービスでは、クラスは ServicedComponent から継承し、トランザクション要件を表す属性を含む必要があります。Class1.vb のコードを次のように変更します。

Imports System.EnterpriseServices

<Transaction(TransactionOption.Required), _
 EventTrackingEnabled(True)> _
Public Class AuthorService
  Inherits ServicedComponent

End Class

Transaction 属性を使用すると、以下のオプションに従ってオブジェクトのトランザクション要件を指定できます。

  • Disabled : オブジェクトは他のトランザクション コンポーネントと同一のコンテキストで実行されますが、それ自体はトランザクションではありません。
  • NotSupported : オブジェクトは他のトランザクション コンポーネントとは別個に実行され、それ自体もトランザクションではありません。
  • Required : オブジェクトは既存のトランザクションに関連します。既存のトランザクションがない場合は新規トランザクションが 1 つ作成されます。
  • RequiresNew : 既存のトランザクションが存在する場合でも、オブジェクトは常に新規のトランザクションで実行されます。
  • Supported : オブジェクトは既存のトランザクションに関連します。既存のトランザクションがない場合は、トランザクションなしで実行されます。

このクラスのメソッドはすべてトランザクション面で保護されているので、メソッドによって実行されるすべてのデータ アクセスは DTC によって管理され、2 フェーズコミット処理のオーバーヘッドおよびメリットが生じます。

メソッド自体が <AutoComplete()> 属性を持ち、トランザクションを管理するためのコードと COM+ との対話方法を制御する場合もあります。

AutoComplete が TRUE の場合、メソッドの成否はメソッドでエラーが発生するかどうかによって決まります。エラーが発生すると、トランザクション全体がロールバックされます。エラーが発生しない場合は、トランザクションのコミットが承認され、他のコンポーネントが成功したという前提に基づいてコミットされます。

AutoComplete が FALSE の場合、開発者がメソッドの成功または失敗の承認処理を直接制御します。言い換えると、メソッドの処理結果を検出するコードをメソッドに記述し、適切な承認処理を行う必要があります。

AutoComplete を使用する

ほとんどの場合、最も単純なコーディング アプローチという理由から AutoComplete が使用される傾向にあります。たとえば、次のコードをクラスに追加して作成者の姓を更新するメソッドを記述することができます。

  Private DB1 As String = _
    ConfigurationSettings.AppSettings("DB1")

  <AutoComplete(True)> _
  Public Sub UpdateName(ByVal au_id As String, ByVal lname As String)
    Dim cn As New SqlConnection(DB1)
    cn.Open()
    Dim cm As New SqlCommand()

    Try
      cm.CommandType = CommandType.Text
      cm.Connection = cn
      cm.CommandText = "UPDATE authors SET au_lname='" & lname & _
        "' WHERE au_id='" & au_id & "'"
      cm.ExecuteNonQuery()
    Finally
      cm.Dispose()
      cn.Close()
      cn.Dispose()
    End Try
  End Sub

この場合、System.Data.SqlClient と System.Configuration 名前空間をファイル最上部にインポートする必要もあります。

このコードは、サーバー上の Pubs データベースへの接続を開いて特定の作成者の姓を更新する標準 ADO.NET コードです。コードの多くは Try..Finally ブロックにあるため、エラーの有無に関係なく確実にデータベースを閉じることができます。

ここで <AutoComplete(True)> 属性に注意してください。これは、エラーが発生した場合にのみトランザクションをロールバックする承認処理を実行することを示しています。エラーを実際にトラップするコードがないため、エラーに遭遇したらエラーが発生し、トランザクション全体をロールバックする承認処理が行われます。エラーに遭遇しなかった場合、コードによってトランザクションのコミット承認処理が行われます。

手動トランザクション

AutoComplete は、EnterpriseServices を使用してトランザクション保護されたコードを作成する最も簡単な方法です。手動で承認処理を実行するようにコードを変更することができます。

  <AutoComplete(False)> _
  Public Sub UpdateName(ByVal au_id As String, ByVal lname As String)
    Dim cn As New SqlConnection(DB1)
    cn.Open()
    Dim cm As New SqlCommand()

    Try
      cm.CommandType = CommandType.Text
      cm.Connection = cn
      cm.CommandText = "UPDATE authors SET au_lname='" & lname & _
        "' WHERE au_id='" & au_id & "'"
      cm.ExecuteNonQuery()
      ContextUtil.SetComplete()
    Catch
      ContextUtil.SetAbort()
    Finally
      cm.Dispose()
      cn.Close()
      cn.Dispose()
    End Try
  End Sub

ここでは、開発者が詳細を手動で処理することが <AutoComplete(False)> によって示されています。これは、ここで SetComplete または SetAbort を明示的に呼び出す必要があることを意味します。前者は成功を示し、後者はエラーを示します。

これらの 2 つのメソッドは、ContextUtil オブジェクトにあります。コードでは、ContextUtil オブジェクトを使用できます。これは ServicedComponent サブクラスがあるためです。このオブジェクトは、Visual Basic 6.0 で使用されるものと同じコンテキスト オブジェクトに対するアクセスを提供し、トランザクションのコミットまたはロールバックを手動で承認処理できるようにします。

このため、エラーの発生を検出した場合に SetAbort を呼び出し、検出しない場合に SetComplete を呼び出せるよう、エラー処理に Catch ブロックを追加しました。

この例では、AutoComplete を使用するメリットはありませんでした。実際、さらにコードを記述する必要があるので AutoComplete の使用は不適切と言えます。一般的に AutoComplete が最適の方法ですが、エラーを発生させずにロールバックの承認処理を行う必要がある場合は、手動アプローチの方が適しています。

複数のデータベースを処理する

ここまでは、単一のデータベースのみを更新していたため 2 フェーズ トランザクションを使用する必要はありませんでした。次に、2 フェーズ トランザクションのメリットを理解するために 2 つのデータベースを更新するようにコードを書き直します。

これにはいくつかの方法があります。複数のアセンブリ (データベースごとに 1 つのアセンブリ) を作成することができます。複数のクラス (データベースごとに 1 つのクラス) を同一のアセンブリに作成することもできます。また、複数のメソッド (データベースごとに 1 つのメソッド) を同一のクラスに作成することもできます。これらのシナリオはすべてサポートされますが、採用する方法を決定する根拠が問題になります。

データベースやビジネスの手順に従ってアプリケーションを分割する場合は、複数のアセンブリを作成する方法が適しています。これは、ここで作成するシナリオ以外でコンポーネントの一部を再利用可能にすることを目的としています。この例では、1 組の作成者テーブルを更新します。他のトランザクションの一部としていずれか一方の作成者テーブルのみを更新することが予想される場合、複数のアセンブリを作成する方法が適しています。

現実には、この種の再利用は非常に限られています。多数のコンポーネントに細分化されたデータ アクセス オブジェクトを持つアプリケーションは多く存在しますが、その再利用のレベルは非常に低いものです。複数のコンポーネントが存在すると複雑さが増すため、多くの場合、再利用を抑えて複雑性を抑えることが予想されます。

同一アセンブリに複数のオブジェクトを作成する方法もほとんど同じです。複数のオブジェクトを作成する理由は、将来、他のオブジェクトを呼び出さずに 1 つのオブジェクトだけを呼び出せるように、各オブジェクトを個別に公開できるようにするためです。この種の再利用も非常に限られたものになります。

実際、多くの場合において、他のコンポーネントを呼び出さずに 1 つのコンポーネントを呼び出すことは無意味なので、コンポーネントはトランザクションの面においてラップされています。たとえば、請求書のヘッダーを保存せずに明細だけを保存することは無意味です。これらを個別のオブジェクトに分解して別々に呼び出せるようにすると、誤用を招く恐れがあり、長期的な観点から保守作業が困難になります。

多くの場合、呼び出すクライアントに対して単一のオブジェクトから単一の Public メソッドを公開するだけの方が賢明です。このようなメソッドは、適切な Private メソッドを呼び出し、シナリオに従ってデータ更新を実行できます。これで、パブリック インターフェイスの複雑化が防止され、誤用を減らすことができます。複雑性と誤用を減らせば、長期的な保守が容易になります。言うまでもなく、コードの再利用の可能性を犠牲にするため、アプリケーションのコンテキストに合わせて選択肢を評価する必要があります。

次の例では、データベースを更新する 1 組の Private メソッドとクライアントからの起動が可能な Public メソッドを実装します。Public メソッドでは AutoComplete を使用してトランザクションを管理します。

  <AutoComplete(True)> _
  Public Sub UpdateName(ByVal au_id As String, ByVal lname As String) 
    UpdateDB1(au_id, lname) 
    UpdateDB2(au_id, lname) 
  End Sub

エラーが発生すると、ロールバックの承認処理が実行されることを思い出してください。UpdateDB1 メソッドと UpdateDB2 メソッドが失敗したらエラーが発生するように記述した場合、エラーが発生するとトランザクション全体が失敗します。

UpdateDB1 メソッドは、前のセクションで記述したコードと同じものを Private メソッドに配置しただけで、特別な属性は含まれていません。

  Private Sub UpdateDB1(ByVal au_id As String, ByVal lname As String)
    Dim cn As New SqlConnection(DB1)
    cn.Open()
    Dim cm As New SqlCommand()

    Try
      cm.CommandType = CommandType.Text
      cm.Connection = cn
      cm.CommandText = "UPDATE authors SET au_lname='" & lname & _
        "' WHERE au_id='" & au_id & "'"
      cm.ExecuteNonQuery()
    Finally
      cm.Dispose()
      cn.Close()
      cn.Dispose()
    End Try
  End Sub

このメソッドは、UpdateName メソッドによって起動されます。トランザクションを制御する属性は UpdateName メソッドにあるので、このメソッドには必要ありません。トランザクションは上位レベルで管理されます。

UpdateDB2 についても同様です。まず、データベース接続文字列に定数を追加する必要があります。

  Private Const DB2 As String = _
     ConfigurationSettings.AppSettings("DB2")

この接続文字列は、.NET Framework SDK によって追加された SQL Server のインスタンスを参照します。ほとんどの .NET 環境には、通常の SQL Server とこの第 2 インスタンスの両方があるので 2 フェーズ トランザクションを簡単にテストできます。

次のように UpdateDB2 を記述します。

  Private Sub UpdateDB2(ByVal au_id As String, ByVal lname As String)
    Dim cn As New SqlConnection(DB2)
    cn.Open()
    Dim cm As New SqlCommand()

    Try
      cm.CommandType = CommandType.Text
      cm.Connection = cn
      cm.CommandText = "UPDATE authors SET au_lname='" & lname & _
        "' WHERE au_id='" & au_id & "'"
      cm.ExecuteNonQuery()
    Finally
      cm.Dispose()
      cn.Close()
      cn.Dispose()
    End Try
  End Sub

これは同じコードを第 2 のデータベースに適用しただけです。ここでも、直接的なトランザクション保護は UpdateName メソッドの <AutoComplete()> 属性によって自動処理されるため実装していません。ここで必要なことは、このコードが失敗したらエラーが確実に発生するようにすることです。言い換えれば、このコードでエラーがキャッチされないようにする必要があります。

ここで一息つきましょう。COM+ アプリケーションで実行し、要求に応じて 2 つのデータベースのトランザクションを更新するアセンブリができました。

コンポーネントを使用する

アセンブリの記述は全体的な作業の半分です。この後に、クライアント コードからアセンブリを呼び出す方法を決定する必要があります。

技術的には DCOM を使用してアセンブリを呼び出すことができますが、そうすると COM 相互運用機能 を通して .NET オブジェクトを公開する必要があります。これは、Visual Basic 6.0 や ASP などの COM クライアントから呼び出す場合を除いて適切ではありません。

.NET クライアント アプリケーションでコンポーネントとの通信を行うには、よりネイティブな .NET テクノロジを使用した方が適しています。その結果、次の 3 つの主なオプションが考えられます。

  • 同一コンピュータからの直接呼び出し
  • XML Web サービス
  • .NET Remoting

これらの 3 つのオプションでは、コンポーネントに対する実際の呼び出しを行うには、COM+ で実行されるコンポーネントと同じサーバー上で実行されるコードが必要です。

最初のオプションでは、同一のコンピュータ上で実行されるクライアント アプリケーションがあり、EnterpriseServices を使用してコンポーネントを直接呼び出すことになります。

2 番目のオプションでは、他のコンピュータ上のクライアントが XML および SOAP を使用してコンポーネントを起動できるようにする XML Web サービスを構築します。この場合、コンポーネントを直接公開するのではなく、クライアントの呼び出しを取得し、それを COM+ で実行するコンポーネントに委任する XML Web サービスを構築します。

.NET Remoting を使用すると、Visual Basic 6.0 の DCOM の動作と同様の方法でリモート クライアントがコンポーネントを直接起動できるようになります。サーバー上で COM+ で実行するオブジェクトとネットワークを介して対話できるようにするために、コンポーネントをクライアントに直接公開する Remoting ホスト アプリケーションを構築することができます。

直接呼び出し

最もシンプルなオプションから説明します。ここでは、PubsServices を直接参照して呼び出すクライアント アプリケーションを作成します。このクライアントは、サーバー コンポーネントと同じコンピュータで実行する必要があります。通常、Windows フォーム クライアントではこの状況は発生しませんが、ASP.NET アプリケーションは PubsServices と同じサーバーで実行する場合があるため、Web フォーム クライアントで活用できることもあります。

ソリューションに「PubsEntWeb」という名前の新しい ASP.NET Web アプリケーションを追加します。

PubsServices プロジェクトへの参照を追加する必要があります。[参照の追加] ダイアログ ボックスの [プロジェクト] タブを使用して参照をプロジェクトに追加します。

次に、図 3 に示すように 2 つのラベル、2 つのテキスト ボックス、および 1 つのボタンをページに追加します。

図 3. PubEntWeb の外観例

テキスト ボックスにそれぞれ「txtID」と「txtName」という名前を付け、ボタンには「btnSave」という名前を付けます。txtID コントロールの Text 値に注意してください。これは、テストを簡単にするため作成者テーブルの項目の 1 つをポイントする既定値です。

ボタンをダブルクリックし、PubsServices コンポーネントと対話するためのコードを追加します。

  Private Sub btnSave_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles btnSave.Click

    Dim svc As New PubsServices.AuthorService()
    svc.UpdateName(txtID.Text, txtName.Text)
  End Sub

ここではオブジェクトを作成してメソッドを呼び出しただけで、特別な処理は行っていません。アセンブリが COM+ で実行され適切に動作しているかどうかの確認は、すべて EnterpriseServices によって処理されます。

前のセクションでデータベース接続文字列の取得に System.Configuration を使用したことを思い出してください。このデータの web.config に次のセクションを追加します。

   <appSettings>
      <add key="DB1" 
value="Data Source=ineroth;Initial Catalog=pubs;User ID=sa;Password= " />
      <add key="DB2" 
value="Data Source=ineroth\NETSDK;Initial Catalog=pubs;User ID=sa;Password=" />
   </appSettings>

ソリューションをビルドして、PubsServices DLL と Web フォーム アプリケーションを作成します。

COM+ でアセンブリを登録する

これは .NET クライアントであるため、COM+ アプリケーションの作成が自動的に試行され、そのアプリケーションにアセンブリが登録されます。しかし、ここで Windows のセキュリティが機能します。ASP.NET は、厳しく制御されたアカウントで実行されますが、このアカウントにはインストールを実際に実行するために必要な特権はありません。

つまり、アプリケーションを実行する前に手動でアセンブリを登録する必要があります。これは、regsvcs.exe コマンド ライン ユーティリティを使用して実行します。このユーティリティを実行する最も簡単な方法は、[Visual Studio .NET コマンド プロンプト] を選択してコマンド プロンプトを使用する方法です。[Visual Studio .NET コマンド プロンプト] は [スタート] メニューの [Microsoft Visual Studio .NET] にある [Visual Studio .NET ツール] に含まれています。

Web フォーム アプリケーションの仮想ルートにある BIN ディレクトリに移動して、次のコマンドを入力します。

regsvcs pubsservices.dll

これで DLL が COM+ に登録され、事前に AssemblyInfo フォルダに配置した情報に基づいてアプリケーションが作成されます。この操作は、System.EnterpriseServices.RegistrationHelper クラスを使用したプログラミングによって実行できることも覚えておいてください。

ここで COM+ アプリケーションに戻り、[ID] タブの情報を変更して、アプリケーションが確実に適切なユーザー アカウントで実行されるよう設定します。

これでアプリケーションを実行することができるようになりました。2 フェーズ トランザクション保護が適用され、両方のテーブルが両方のデータベースで更新されていることを確認する必要があります。[コンポーネント サービス] 管理コンソールでコンポーネントの状態を調べます (オブジェクトのインスタンスがアクティブであることを確認します)。

図 4. コンポーネントの状態を示す [コンポーネント サービス] 管理コンソール

多くの場合、クライアントと同じコンピュータ上で COM+ コンポーネントを実行させるだけでは不十分なこともあります。多くの Web アプリケーションでは、Web サーバーによるデータベースへの直接アクセスを防止するセキュリティ条件が必要になります。Windows アプリケーションの場合、中央のアプリケーション サーバーでデータ アクセス コンポーネントを実行することによる実質的なスケーラビリティ上のメリットがあります。また、一般に COM+ をすべてのクライアント ワークステーションで実行することは実用的ではありません。

ネットワークを経由してサーバー コンポーネントをリモートで起動するには、次の 3 つのオプションがあります。

  • Web サービス
  • .NET Remoting
  • DCOM

DCOM を使うオプションは、Visual Basic 6.0 で行ったものと同じです。COM+ から COM+ アプリケーション プロキシをエクスポートして、クライアント コンピュータにインストールします。次に、.NET アプリケーションをクライアントにコピーすると、DCOM を介してリモート コンポーネントが起動されます。これを実現するためのコードは、COM+ アプリケーション プロキシの配置を除いて、この記事で説明した同一コンピュータからの直接呼び出しの例と同じです。

次のセクションでは、XML Web サービス および .NET Remoting を使用する 2 つの新しいアプローチについて説明します。

Web サービス

Web サービスは、ネットワークによるアセンブリの起動に使用できます。2 つのアプローチが可能です。最初のアプローチは、すべてのメソッド呼び出しを PubsServices に委任する ASP.NET Web サービスを作成することです。もう 1 つのアプローチは Windows XP でのみ実行可能ですが、COM+ オプションを使用して SOAP を介してアセンブリを直接公開することができます。

まず、Web サービス プロジェクトを作成し、最初の方法でコンポーネントを公開します。

ASP.NET Web サービス プロジェクトをソリューションに追加して「PubsEnterprise」という名前を付けます。

[参照の追加] ダイアログ ボックスを使用して PubsServices プロジェクトへの参照を追加し、Service1.asmx のコード ウィンドウを表示します。サーバー コンポーネントへのアクセスを提供する Web メソッドを作成するコードを追加します。

  <WebMethod()> _
  Public Sub UpdateName(ByVal au_id As String, ByVal lname As String)
    Dim svc As New PubsServices.AuthorService()
    svc.UpdateName(au_id, lname)
  End Sub

クライアントが Web サービスを呼び出します。次に、Web サービスが COM+ の AuthorService コンポーネントのインスタンスを作成して、その UpdateName メソッドを呼び出します。クライアントは COM+ サービスの呼び出しを直接行う訳ではありませんが、Web サービス テクノロジを通してサービスを利用することができます。

Web サービスを呼び出す

PubsEntWeb プロジェクトを更新して、この新しい Web サービスを利用できます。図 5 に示すように、[Web 参照の追加] ダイアログ ボックスを使用してサービスへの参照を追加します。

図 5. Web 参照を追加する

これで PubServices を直接呼び出さずに、Web サービスを使用して名前を更新するためにページの背後にあるコードを更新することができます。

  Private Sub btnSave_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles btnSave.Click

    Dim svc As New localhost.Service1()
    svc.UpdateName(txtID.Text, txtName.Text)
  End Sub

Web サービスのメソッド名とパラメータは、AuthorService オブジェクト自体と同じであるため、必要な変更は、AuthorService オブジェクトではなく Web サービス オブジェクトのインスタンスを作成することだけです。

この時点で、Web アプリケーションは以前と同様に実行されますが、COM+ コンポーネントは Web サービス から起動されています。Web アプリケーションとサーバー コンポーネントの間にネットワーク ホップを挿入したため実質的なパフォーマンスが低下しますが、アプリケーションのセキュリティを強化する内部ファイアウォールが成立しています。

Remoting

リモート クライアントに対してサーバー コンポーネントを利用可能にする簡単で強力な方法は、.NET Remoting を使用することです。このアプローチでは、手動で Web サービス ラッパーを作成することなくクライアントに対してアセンブリを直接公開することが可能になります。

ここで紹介する技法は、Windows XP で SOAP を介して COM+ アプリケーションを公開するチェック ボックスを選択した際に実行される機能と基本的に同じです。この機能は、Windows XP でのみ使用できますが、ここで説明する技法はアセンブリをクライアントに直接公開することができるので重要です。

Remoting を通してオブジェクトをホストするには、カスタム ホスト アプリケーション (Windows サービス) を作成する、または ASP.NET プロジェクトを使用してオブジェクトをホストするという 2 つの方法があります。Windows サービスを作成するオプションについては以前の記事で説明したので、この記事では ASP.NET を使用するオプションを使用します。

空の Web オブジェクトをソリューションに追加して「PubsEntRemote」という名前を付けます。Visual Studio .NET によって新しい空の仮想ルートの作成および構成が行われます。

[参照の追加] ダイアログ ボックスを使用して、PubServices プロジェクトへの参照を追加します。この操作によって PubsServices.dll がアプリケーションの BIN ディレクトリにコピーされます。これは Remoting を通して機能を公開するときに必要です。

最後に、[プロジェクト] の [新しい項目の追加] メニュー オプションを使用して、「Web.config」という名前の Web 構成ファイルをプロジェクトに追加します。このファイルに、コンポーネントをクライアント アプリケーションに公開する Remoting を構成する XML を追加します。次のコードを Web.config ファイルに追加します。

<configuration>
  <system.runtime.remoting>
    <application>
      <service>
        <wellknown 
          mode="SingleCall" 
          objectUri="AuthorService.rem"
          type="PubsServices.AuthorService, PubsServices" />
        </service>
      </application>
    </system.runtime.remoting>    

このセクションは、<system.web> 要素ではなく、ピア レベルに含まれていることに注意してください。

このセクションのキーは、<service> 要素で、ここでクライアントに公開する特定のクラスを定義します。この例では、PubsServices アセンブリに組み込まれている AuthorService クラスが AuthorService.rem の URI を使用して公開されるように指定しています。これは SingleCall モードで構成され、クライアントからのメソッド呼び出しごとにオブジェクトの新規インスタンスを取得して要求に応じます。これは、Web サービスを使ったアプローチの場合と同じ動作です。

サーバー名および仮想ルートと組み合わせて、Remoting 経由でコンポーネントをアクセス可能にする URL が次のように定義されました。

http://myserver/PubsEntRemote/AuthorService.rem

リモート サーバーを呼び出す

この時点で、Web サービスではなく Remoting を利用したクライアント アプリケーションの更新が可能になっています。まず、Web フォーム の背後にあるコードに戻り、Web サービス に依存しないようにコードを変更します。

  Private Sub btnSave_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles btnSave.Click

    Dim svc As New PubsServices.AuthorService()
    svc.UpdateName(txtID.Text, txtName.Text)
  End Sub

これは、最初に AuthorService オブジェクトを作成して使用したときのコードと同じです。Remoting によって、リモート オブジェクトを使用するコードは、オブジェクトと直接対話するコードと同じになっています。ここで完全な場所の透過性が得られています。

言うまでもなく、クライアント アプリケーションに AuthorService オブジェクトをローカルではなくリモートで起動するように指示する必要があります。これは Web.config ファイルを使用するか、プログラミングによって Global.asax ファイルを更新することによって行います。Global.asax ファイルのコード ウィンドウを開き、Application_Start メソッドを次のように変更します。

  Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
    ' アプリケーション起動時に発生させます。
    RemotingConfiguration.RegisterWellKnownClientType( _
      GetType(PubsServices.AuthorService), _
      "http://localhost/pubsentremote/authorservice.rem")
  End Sub

この変更によって、ASP.NET アプリケーションが開始されると Remoting サブシステムが構成され、AuthorService オブジェクトがリモート サーバー上で起動されることが認識されます。これで、オブジェクトをローカルで使用する代わりに、ページのコードやアプリケーションの他の場所から自動的にリモート オブジェクトと対話できるようになっています。

アプリケーションを実行すると以前と同様に機能しますが、アプリケーションは Remoting を使用して AutherService プロジェクトを起動します。これは、Web サービスを使ったアプローチと同様に問題なくファイアウォールを通過しますが、Web サービスのオプションに比べてより短時間で簡単にコード化ができます。

まとめ

この記事の例に使用したクライアントは Web フォーム アプリケーションですが、Windows フォーム アプリケーションにも同じ技法を適用することができます。

EnterpriseServices は、アプリケーションの用途に応じて必要性の異なる機能で構成されるセットです。EnterpriseServices はアプリケーションの用途ごとに評価が必要で、エンタープライズ アプリケーションを作成するという理由だけで使用すべきものではありません。

そのサービスの中にアプリケーションで有用なものがある場合は、ServicedComponent をサブクラス化し、アセンブリ、クラス、およびメソッドに属性を追加すれば EnterpriseServices が提供する構築済みの機能を簡単に利用できます。




Microsoft