Access ASP.NET Visual Studio

ASP.NETからAccessを使用してPDF作成した3(ODBC不調、10010エラー)

Access

ASP.NET+Access 安定稼働に向けて奮闘

Access帳票元データはSQL Server 2012 EXPRESSに有ってAccessからODBC接続でリンクテーブルを作成して取り出している。Accessのみの帳票テストでは全く問題ないのだが、ASP.NETから COMのAccessアプリケーションで実行すると、何故か不安定で失敗する。ODBC接続に問題有りと判断した。

帳票データは ODBC不調のためAccessのテーブルに変更

Access単体であればODBC接続のリンクテーブルで全く問題ないのだが、ASP.NET+Accessオートメーションを呼び出すとODBC接続が出来ない様だ。イベントビューワにもログが残らない。ODBCの設定をさんざんトライしたがいい加減ハマってしまったのでAccessの実テーブルに方針変更。
SQL ServerからAccessのテーブルにデータを流し込む事は実績はあったのだが、今回Visual Studioのバージョンが上がっている。それにAccessも .mdb の実績しかない。Access 2016 は .accdb。

余談だが、.adpはODBCの設定不要で直接SQL Serverにダイレクト接続出来て非常に重宝していたのだがAccess2010までしか使えない。どういうこと!って言いたい感じ。なんと冷たい仕打ち。

SQL Server から Accessのテーブルにデータ書き込み

ここに accessへの接続方法の歴史が書いてある。非常に良い 重要

ASP.NETからACCESS2007以降(JETエンジンからACEエンジンに変わった)に接続する方法は
OLEDB(aceoledb.dll)から ACEエンジンにアクセスして .accdbにアクセスする。

'/////////////////////////////////////////////////////////
' MDB ACCDB 接続 ACCES2007からJET→ACE
'/////////////////////////////////////////////////////////
Imports System.Data.SqlClient
Imports System.Web.HttpContext
Imports System.Data.OleDb

Public Function mdbACEcn(ByVal cn, ByVal sv, ByVal db, ByVal usr, ByVal psw)
If cn Is Nothing Then
     cn = New OleDbConnection
     cn.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;" &
          "Data source=" & db
End If
If Not cn.State = ConnectionState.Open Then
     Try
          cn.Open()
     Catch ex As Exception
          Return -1
     End Try
End If

Return cn
End Function

取り合えず接続してみようということで いきなりこれで接続したが下のエラーで駄目

{“The ‘Microsoft.ACE.OLEDB.12.0’ provider is not registered on the local machine.”}

なにやらライブラリーらしきものがインストールされてませんよ とのエラー
Access2016インストール済みでもだめなんかいな~ 2016用の単体の配布パッケージを探したが見つからない。

結局下の 2010 再頒布可能コンポーネントをインストールをしたら接続できた。
「Microsoft Access データベース エンジン 2010 再頒布可能コンポーネント」内のACE.OLEDB.12.0を使う

これでようやくASP.NETからAccessへ接続できた

次にSQL Server から Accessテーブルへのデータの書き出しだが SqlBulkCopy を使ってみたがさすがにAccessへの接続では使用できなかった。下はあまり推奨されない方法だがデータ量そう多くなければ問題ない。

'//////////////////////////////////////////////
' SQL Server → Access       SQL ServerとAccessのテーブル名は同一にすること
' SQLTable:SQL Server のテーブル名
' ds:SQL Server のDATASET
' cn:Accessへの接続
' ad_mdb: Access用OleDbDataAdapter
' ds_mdb: Access用DATASET
' rw_mdb: Access用DATAROW
'//////////////////////////////////////////////
    Dim ad_mdb As OleDbDataAdapter
    Dim ds_mdb As DataSet
    Dim rw_mdb As DataRow
    Dim cb_mdb As OleDbCommandBuilder

Function write2mdb(ByVal SQLTable As String, ByVal ds As DataSet, ByVal cn As OleDbConnection) As Integer
     If mdb_r(SQLTable, cn) = -1 Then 'Access空読みして追加用に開いて準備
          Return -1
     End If

     Dim rw As DataRow
     Dim i As Integer = 0

     Try
          For Each rw In ds.Tables(0).Rows   'SQL Serverの各行に対して
               rw_mdb = ds_mdb.Tables(SQLTable).NewRow 'Accessの新行

               For i = 0 To ds.Tables(0).Columns.Count - 1
                    rw_mdb(i) = rw(i)
               Next

               ds_mdb.Tables(SQLTable).Rows.Add(rw_mdb)
               ad_mdb.Update(ds_mdb, SQLTable) 'コマンドビルダーを準備しておかないとエラーとなる
          Next
     Catch e As Exception
          Return -1
     End Try
End Function
'//////////////////////////////////////////////
'		mdb_r()
'//////////////////////////////////////////////
Function mdb_r(ByVal SQLTable As String, ByVal cn As OleDbConnection) As Integer
        ad_mdb = New OleDbDataAdapter("select * from " & SQLTable, cn)
        ds_mdb = New DataSet
        cb_mdb = New OleDbCommandBuilder(ad_mdb)        'write2mdb()のad_mdb.Update(ds_mdb, SQLTable)で必要

        Try
            ad_mdb.Fill(ds_mdb, SQLTable)
        Catch e As Exception
            Return -1
        End Try

        Return ds_mdb.Tables(SQLTable).Rows.Count
End Function

これでようやくSQL Serverから Accessの帳票用テーブルに書き出しができた。

イベントビューアー システム イベントID 10010 対策

これでAccess帳票用データは準備できたので、いざPDF作成実行!
下の画面キャプチャーのイベントID 10016 については、解決した。これもDCOMのエラーだが直接今回のAccessアプリケーションには関係ないので、対応について後日別のページに投稿予定。

10010エラー、これにはてこづった。というか、とりあえずの策で対応した。エラーの内容は

サーバー {73A4C9C1-D68D-11D0-98BF-00A0C90DC8D9} は、必要なタイムアウト期間内に DCOM に登録しませんでした。

WEBアプリケーション内でASP.NETの吐いたエラーは

Retrieving the COM class factory for component with CLSID {73A4C9C1-D68D-11D0-98BF-00A0C90DC8D9} failed due to the following error: 80080005.

73A4C9C1-D68D-11D0-98BF-00A0C90DC8D9 って何?
検索すると Microsoft Access Application のアプリケーションID らしい。
この 10010エラーはWEBサーバー起動後 一度もWEBサーバーにログオンせずに、今回のCOMアプリケーションを実行すると必ずと言っていいほど発生する。WEBサーバーに一度ログオンすれば、後はWEBサーバーからログアウトしている状態でもこのエラーは発生しない。
ちなみに同時刻にApplication のログにも MSACCESS.EXE のエラーが記録されている

ファイル名を指定して実行 DCOMCNFG(GUI では「コンポーネントサービス」)、ローカルセキュリティポリシー 等 下記サイトなどを参考に何日もトライしたが 「WEBサーバー起動後 必ず1回WEBサーバーにログオンしないと」 10010 エラーは解消されない。いい加減見切りを付けたい心境。

 

プロセスモニターでWEBサーバーの詳細な動作を確認してみた

どうしても10010解決できないので、マイクロソフトのフリー「プロセスモニター」をインストールして 10010発生時と発生しないときの詳細なWEBサーバーのプロセスを比較してみた。

【 正常時のプロセスモニターのログ 】

 

下は【 エラー発生時のプロセスモニターのログ 】
C:\Windows\SysWOW64\bcryptprimitives.dll をロード後 次の
C:\Program Files (x86)\Microsoft Office\root\VFS\ProgramFilesCommonX86\Microsoft Shared\OFFICE16\Mso20win32client.dll が実行されない。まったく原因不明。

手詰まり状態なので電源ONで自動ログオンの設定をする

とりあえずWEBサーバーに一度ログオンすれば良さそうなので自動ログオンの設定をした。

WEBサーバー起動で自動ログオンの設定後、ほぼエラー回避できた。
これでもまだ たまに失敗するので、さらに次の対応をした。

スタートアップにACCESSを登録

C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp\
ここに
“C:\Program Files (x86)\Microsoft Office\root\Office16\MSACCESS.EXE” のショートカットを配置した。
これで、今の所良さそう。

出来がったPDFの表示方法

// 概念として JavaScript を msg として用意して Response.Writeで表示する

<SCRIPT language="JavaScript">
<!--
     NW=window.open("/XXX.PDF");
     NW.focus();
     self.blur();
// -->
</SCRIPT>

 Current.Response.Write(msg)

WEB環境のCOMアプリケーションの環境設定は大変だ

Windowsアプリならばこれほどまで大変ではないのかも。
WEBアプリといってもイントラ運用なのでセキュリティ環境をもっと簡単にできないものだろうか。
インターネット用はこれ、イントラ用これ というようにセキュリティ環境を自動で設定できるようなものがあれば助かる。セキュリティはムズイ。

コメント