タグ:SQL Server 2005 ( 18 ) タグの人気記事
(SQL)SQL Server 2005 でDBログインユーザとデータベースユーザの一覧取得
SQL Server のデータベースログインユーザと、各データベースのユーザをSQLで列挙する方法です。

ログインユーザは master データベースの syslogins テーブルに定義されているようです。
データベースユーザは各データベースの sysusers テーブル定義されているようです。

下記のようなSQLで確認できます。

/*ログインユーザ列挙*/
USE master;
SELECT * FROM syslogins;
 
/*データベースユーザ列挙*/
USE DB名;
SELECT * FROM sysusers;
 
/*おまけ。データベースの一覧列挙*/
USE master;
SELECT name FROM sysdatabases;


参考:
KYO'S ROOM 【SQL Server】:
[PR]
by jehoshaphat | 2009-08-10 18:36 | SQL | Trackback | Comments(0)
(SQL Server)データベース復元時に必要な権限
あるデータベース専用のユーザを作って、db_owner のロールを与えてます。
バックアップもうまくいったんで、てっきり復元もできるかと思ったら、

System.Data.SqlClient.SqlError: ユーザーにはデータベース 'データベース名' を復元する権限がありません。
重要度:14
SQL Serverエラーナンバー:3110

と怒られました。

で調べてみると、復元を実行するユーザは以下のいずれかのメンバでないとけないようです。
・dbo
・sysadminサーバロール
・dbcreatorサーバロール

dbo というのは未だにちょっと概念が分かってないんで、とりあえず dbcreater サーバロールのメンバにユーザ加えることで対応しました。

参照:
MSDN:RESTORE (Transact-SQL)の権限の項目
[PR]
by jehoshaphat | 2009-07-17 23:49 | SQL | Trackback | Comments(0)
(SQL Server)データベースの復元時にエラーナンバー:3102が発生
ある SQL Server のユーザでログインし、データベースを復元しようとすると下記のようなエラーが。。。


System.Data.SqlClient.SqlError: データベース 'データベース名' はこのセッションで使用中なので、RESTORE では処理できません。この操作を実行するときは、master データベースを使用することをお勧めします。
重要度:16
SQL Serverエラーナンバー:3102


いろいろ探してると、CodeHour:SQL Server で復元できない時にピンポイントな答えが。。
どうやら、復元処理を実行しているユーザの「既定のデータベース」と復元先のデータベースが同じためにおこるらしいです。

ということ、SQL Server ユーザ作るときに、最初から既定のデータベースを master にすることにしました。
[PR]
by jehoshaphat | 2009-07-17 23:38 | SQL | Trackback | Comments(0)
(ネットワーク)DNS環境が無いLANではNetBIOS orver TCP/IPとそれが使うポートを開放しないとけない!?
SQL Server をインストールしたクリーンインストール直後の XP SP3 のマシンに、別PC(Vista)から Management Studio からアクセスをしようとしました。
この時、XP マシンのWindowsファイアウォールは有効で、UDP 1434 と sqlserver.exe については通信を許可するように設定してあります。
また、この XP マシンのIPアドレスはDNSサーバには登録してません。

その際、 Management Studio の「サーバの参照」→「ネットワークサーバ」で、自動的にLANからみつけた SQL Server インスタンスから選択させました。
「サーバの参照」→「ネットワークサーバ」で出てきたリストにはちゃんと対象の XP マシンが表示されてるので、つながるだろうともって接続しましたが、接続エラーに。。。

つまり SQL Swever Browser のサービスはネットワークから使えるのに、SQL Server インスタンス自体がネットワーク的につながらないのです。
ちゃんと FW で sqlserver.exe 許可してるのにおかしいと思ったんで、Management Studio からIPアドレスを直接たたいてアクセスするとつながりました。

どうやら名前解決あたりがあやしいらしいです。

ということで、一体どういうパケットを投げて失敗してるのか Wireshark というパケットキャプチャソフトでチェックしてみました。
すると、ブロードキャストで UDP 137 宛てにその XP マシン名を投げてるパケットがあります。

下記がパケットキャプチャしたイメージです。
e0091163_1745358.jpg


もしかして思って、XP マシンのファイアウォールの設定を見ると、「ファイルとプリンタの共有」のチェックが外れてます。というより、インストール直後はデフォルトでチェックが無いっぽいですね。

ここで、「ファイルとプリンタの共有」の詳細で、UDP 137 を許可してやると、 Management Studio の「サーバの参照」からでもアクセスできるようになりました。
e0091163_1754866.jpg


どうやら最初の失敗時の流れとしては下記のような感じになっていたっぽいです。(名前解決の詳細は(MCP70-642)2.5 クライアントコンピュータの名前解決構成参照。)

クライアントPC:Management Studio でLAN内の SQL Server インスタンス検索
  ↓
サーバPC:UDP1434 はFW許可されてるので、 SQL Server Browser が応答を返す(この時サーバのIPではなく名前を返す)
  ↓
クライアントPC:ユーザが一覧に出たLANのインスタンスを選択し、接続(この時サーバ名でアクセス)
  ↓
クライアントPC:サーバ名の名前解決を試みる。
    まずDNSサーバに問合わせ。→対象マシンはDNSサーバに登録してないのNG。
    クライアントは Vista なので、LLMNR で問合せ。→対象マシンはXPなのでNG。
    NetBios による名前解決。ブロードキャスト(UDP137)で問合せ。→対象マシンはUDP137をFWで拒否ってるのでNG。
  ↓
クライントPC:接続失敗

DNSが無いLAN内での名前解決には UDP 137 が未だに必要なんだ。。ということでここからちょっといろいろ試してみました。


まず、TCP/IPの設定で、NetBIOS 設定というがあります。ここは NetBIOS orver TCP/IP の有無に関する設定ですが、デフォルトは「既定値」になっています。

規定値の意味についてWindowsヘルプを見るとIP アドレスが自動的に取得される場合は、[既定値] オプションが選択されます。これにより、このコンピュータは、IP アドレスと構成リースを取得するときに、DHCP サーバーによってオプションで提供される NetBIOS orver TCP/IP(NetBT) 設定を使用するようになります。[NetBIOS over TCP/IP を無効にする] DHCP オプションが DHCP サーバーにより提供される場合は、このオプションの値で NetBT を有効にするか、無効にするかが決まります。[NetBIOS over TCP/IP を無効にする] DHCP オプションが DHCP サーバーにより提供されない場合は、NetBT は有効になります。とあります。
今回DHCPは有効になっていますが、DHCPサーバはルータで NetBIOS に関するオプションは無いので有効になるんですね。
e0091163_1771234.jpg


試しにサーバとなる PC で NetBIOS over TCP/IP(NBT) を無効にしてみました。(DNSには未登録です)
ファイアウォールでは NetBIOS が使うポート UDP 137,138 TCP 139 を開放します。
そして、クライアントPCのエクスプローラから \\コンピュータ名 でアクセスを試みます。
結果名前解決に失敗したようで、「ネットワークパスが見つからない」というエラーとなりました。

ということで、NetBIOS が使うポートが開放されてても、NBT が無効かつDNSサーバに名前が登録されていない場合は、\\コンピュータ名 でアクセスできないということが分かりました。

次に、Windows ファイル共有においては Windows 2000 あたりから CIFS という SMB を拡張した NetBIOS に依存しないファイル共有のプロトコルが追加されてます。
CIFS はポート 445 のみ使うので、ファイアウォールで TCP445 のみを許可 + NetBIOS 無効にしてクライアントから接続。(DNSには未登録です)
結果は接続失敗でした。

で、調べてみると CIFS では名前解決には DNS を使うようです。そして DNS がダメなときは NetBOIS によるブロードキャストで名前解決を試みるようです。上記では DNS も NetBIOS もダメということで、名前解決に失敗し接続できなかったんですね。
詳しくはITPro:Windowsに潜むデフォルト設定の落とし穴(下)の図と、@IT:第20回 ファイル共有プロトコルSMB/CIFSからの一連の連載が非常に参考になります。


LAN内で名前解決するには、DNSサーバに登録するか、 NetBIOS over TCP/IP(NBT) を使うかになるんですが、NBT を使う場合は、デフォルトでは遮断になっている UDP 137 ポートをあける必要と、TCP/IP の設定で、「NetBIOS over TCP/IP を有効にする」(DHCPサーバでNetBIOS有効の設定か項目ない時はは既定値でもOK)にしとかないと、ダメだということがよく理解できました。


ついでに、DNSサーバにIPとホスト名登録済みかそうでないかと、NetBIOS over TCP/IP が有効無効かという条件で、ファイアウォールでファイル共有と名前解決のポート(NetBIOSのポートのみ。DNSで使用するポートは開放済み)の開放可否の組み合わせで、ファイル共有ができるかどうかの表を作ってみました。

※クライアントPCは NBT が有効になっていることとします。

┌─────┬──────┬──────┬──────────────┐
│ │ │ │FWで許可しているポート │
├─────┼──────┼──────┼────┬─────┬───┤
│ │ │ │UDP137 │UDP137 │TCP445│
│ │ │ │TCP139 │TCP445 │ │
│ │ │ │(NBT) │(CIFS+NBT)│(CIFS)│
├─────┼──────┼──────┼────┼─────┼───┤
│パターン1 │DNS登録済み │NetBIOS有効 │○ │○ │○ │
├─────┼──────┼──────┼────┼─────┼───┤
│パターン2 │DNS登録済み │NetBIOS無効 │× │○ │○ │
├─────┼──────┼──────┼────┼─────┼───┤
│パターン3 │DNS未登録 │NetBIOS有効 │○ │○ │× │
├─────┼──────┼──────┼────┼─────┼───┤
│パターン4 │DNS未登録 │NetBIOS無効 │× │× │× │
└─────┴──────┴──────┴────┴─────┴───┘

パターン1は DNS による名前解決+NetBIOS による名前解決OKの状態ですね。
パターン2は DNS による名前解決はできるけど NetBIOS による名前解決はできない状態です。DNS対応のCIFSでのみアクセスできます。
パターン3は NetBIOS による名前解決なので、CIFSオンリーはダメです。NetBIOSのみのファイル共有かCIFS+NetBIOS名前解決というハイブリッドなら行けます。
パターン4は名前解決手段が無いので、どっちもダメです。(ただ、Vista以降なら LLMNR があるので、CIFSによるアクセスは出来るかも知れません。)


小規模なネットワークではLAN内にDNSサーバ運用してるところはまれなので、結局コンピュータ名で何かしようとすると、NBT を有効にし UDP137(ファイル共有する場合は TCP139 or TCP445)を開放しないといけないということですね。
[PR]
by jehoshaphat | 2009-07-08 17:20 | ネットワーク | Trackback | Comments(1)
(SQL Server)mdfファイルを別SQL Serverに移行した時のDBユーザの問題
例えば、SQL Server で "testDB" というデータベースを運用しているとします。
この SQL Server には "userA" という SQL ユーザがおり、SQL 認証でのログインが可能で、"testDB" に対して db_owner のロールを持っているとします。
e0091163_15592288.jpg


この "testDB" データベースをデタッチして、新たに SQL Server をインストールした別のサーバに "testDB" の mdf,ldf ファイルを移してアタッチします。

この時、どうやら mdf ファイルに前のサーバで設定した データベースユーザの情報が残っているらしく、Management Studio でみると、"testDB" のセキュリティに "userA" が表示されます。
ただ、SQL Server 全体のセキュリティには表示されません。
ちょうど下記のような状態です。(SQL Server からユーザ削除した時と同じ現象ですね。)
これは "testDB" の "userA" のプロパティです。ログインができなくなってます。
e0091163_16124921.jpg


この状態で、下記のように "userA" を作っても、うまく "testDB" とのマッピングができません。

USE [master]
GO
CREATE LOGIN userA WITH PASSWORD=N'1234', DEFAULT_DATABASE=testDB, CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF
GO
USE testDB
GO
CREATE USER userA FOR LOGIN userA
GO
USE testDB
GO
EXEC sp_addrolemember N'db_owner', N'userA'
GO

このように、マッピングされていない状態となってしまいます。
e0091163_16132227.jpg


それで、上記のコマンドの前にいったん "testDB" に関連付けられてる "userA" を削除する下記 SQL を実行したら、上記コマンド実行後にちゃんと "testDB" と "userA" がマッピングされるようになりました。

USE [testDB]
GO
DROP USER [userA]
GO


まあ、Management Studio から手動でマッピングしてもいいんですが、パッケージ製品として、インストール時にバッチファイル等で初期設定するときとかは SQL コマンドでやったほうが楽ですね。
[PR]
by jehoshaphat | 2009-07-08 16:15 | サーバがらみ | Trackback | Comments(0)
(SQL Server)バッチでシステムドライブにあるmdfファイルをアッタチさせようとしたけれど。。。
(SQL Server)SQLサーバの無人インストールとインストール後の各種設定設定 Part3とかなり関連のあるネタです。


バッチファイルで SQL Server 2005 Express のインストールと mdf のアタッチさせるんですが、mdf ファイルはシステムドライブの固定のフォルダにおいてるとします。たとえば C:\test\testdb.mdf とします。

最初は下記のようにしてました。
・バッチファイル(Windows認証)

>sqlcmd -S localhost\REALSTYLE -i makeuser.sql

・SQLファイル( makeuser.sql)

CREATE DATABASE RhythmPro ON
(FILENAME = N'%systemdrive%\test\testdb.mdf' ),
(FILENAME = N'%systemdrive%\test\testdb_log.ldf' )
FOR ATTACH;


SQL ファイルの中で %systemdrive% と入れたんですが、環境変数は SQL スクリプトでは使えないようです。

ということで、下記のようにバッチファイルの中に SQL クエリを記述すると、環境変数が置換されちゃんと動きました。

>sqlcmd -S localhost\SQLEXPRESS -Q "CREATE DATABASE TestDatabase ON (FILENAME = N'%systemdrive%\test\testdb.mdf' ),(FILENAME = N'%systemdrive%\test\testdb_log.ldf' ) FOR ATTACH;"


あと、SQL Server の自動インストールバッチファイルと同じバッチファイル上でに sqlcmd コマンドを使おうとすると、ダメみたいです。
どうやら SQL Server インストール中に環境変数 Path に sqlcmd があるフォルダへのパスが登録されるっぽいですが、いったん終了して再度コマンドプロンプトを立ち上げないと、環境変数反映されないっぽいです。
ここら辺注意が要りますね。
[PR]
by jehoshaphat | 2009-07-08 00:46 | SQL | Trackback | Comments(0)
(.Net)ネットーワーク上のSQL Server インスタンスの一覧取得

クライアントソフト初回起動時に、DBの接続先を設定させる要件で、今までは直に SQL Server のホスト名を指定させてましたが、ユーザビリティを向上させるために、自動的にLAN内の SQL Server を検索する機能を付けてみました。
(SQL Server Management Studio のログインで参照を押した時のような感じです。)

結構ややこしいだろなと思って探すと、あっさり見つかりました。
MSDN:SQL Server のインスタンスの列挙に詳しく取り上げられています。

下記は、ネットワーク上の SQL Server インスタンスを検索し、ListViewコントロールに表示するメソッドです。
''' <summary>
''' ネットワーク上のSQL Server インスタンスを検索し、結果をListViewに表示
''' </summary>
''' <remarks></remarks>
Private Sub SetDatabaseServerListView(ByRef lsvServer As ListView)
'リストビューからいったん情報クリア
lsvServer.Items.Clear()
 
'ネットワーク上の SQL Server Instance取得
Dim instance As SqlDataSourceEnumerator = SqlDataSourceEnumerator.Instance
Dim dtbl As DataTable = instance.GetDataSources()
 
For Each row As DataRow In dtbl.Rows
'ListViewに情報追加
Dim svItems() As String = {row("ServerName"), row("InstanceName"), row("Version")}
Dim listViewItem As New ListViewItem(svItems, 0)
listViewItem.SubItems(0).Name = "SvName"
listViewItem.SubItems(1).Name = "InstanceName"
lsvServer.Items.Add(listViewItem)
Next
End Sub


あまりに簡単にできたので、ちょっと拍子抜け。。

追記(2009/7/6):
row("ServerName"), row("InstanceName"), row("Version") ですが、どうやら値が DBNull になる場合もあるようです。(MSDNには書いてませんが。。。)
なので、

If row("ServerName") Is DBNull.Value OrElse row("InstanceName") Is DBNull.Value OrElse row("Version") Is DBNull.Value OrElse Then
Continue For
End If
Dim svItems() As String = {row("ServerName"), row("InstanceName"), row("Version")}

としたほうが良さそうです。
[PR]
by jehoshaphat | 2009-06-28 23:21 | .Net開発 | Trackback | Comments(0)
(SQL Server 2005)データベースの内容をダンプする bcp ツール
SQL Server で MySQL のダンプのようにデータを含むクエリを作ってくれる簡単な方法はないのかと思って調査してる時に見つけたツールです。

この bcp ツールというのは SQL Server に付属しており、テーブルデータのエクスポートやインポートが簡単にできるようです。
といっても、データがタブ区切りでエクスポートもしくはインポートできるだけなので、MySQL のようなクエリ付ではありません。
(しかも、テーブル単位でしかできないのがちょっと不便ですが。。)

bcp コマンドの詳細はMSDN:bcp ユーティリティを参照。

コマンドのオプションで -c,-w と -n,-N があるんですが、-c,-w だとテキスト形式で落とすので可読性はありますが、そのデータをインポートするときに問題が発生するかもしれません。-n,-N はネイティブデータ型を使用するので吐き出したデータをインポートする可能性があるならこっちの方が無難でしょう。
-c と -w , -n と -N の違いはUnicode文字を使用するかどうかなので、-N でネイティブ型のUnicode文字使用するのがお勧めです。

簡単な使い方は【Microsoft SQL Server】 BCPユーティリティの使用法[SQLServer]BCPユーティリティ とはで紹介されてます。

インポートの際には一旦今のデータを消しておかないと一意制約かけてる列ではエラーになってしまいます。

一応下記のような感じで軽く試してみました。

ネイティブデータ型(Unicode)で出力
C:\Users\TestUser>bcp TestDB.dbo.CustomerTable out c:\testest.txt -N -S hostname\SQL2005DEV -T

コピーを開始しています...

15 行コピーされました。
ネットワーク パケット サイズ (バイト): 4096
クロック タイム (ミリ秒) 合計 : 1 平均 : (15000.00 行/秒)

テキスト型で出力
C:\Users\TestUser>bcp TestDB.dbo.CustomerTable out c:\testest_char.txt -c -S hostname\SQL2005DEV -T

コピーを開始しています...

15 行コピーされました。
ネットワーク パケット サイズ (バイト): 4096
クロック タイム (ミリ秒) 合計 : 1 平均 : (15000.00 行/秒)


出力したデータをインポート(データを消さずにインポートしようとしたのでユニーク制約に引っ掛かってしまった)
C:\Users\TestUser>bcp TestDB.dbo.CustomerTable in c:\testest.txt -n -S hostname\SQL2005DEV -U sa -P passwd -E

コピーを開始しています...
SQLState = 23000, NativeError = 2627
Error = [Microsoft][SQL Native Client][SQL Server]制約 'aaaaaCustomerTable_PK' の PRIMARY KEY 違反。オブジェクト 'dbo.CustomerTable' には重複したキー
を挿入できません。
SQLState = 01000, NativeError = 3621
Warning = [Microsoft][SQL Native Client][SQL Server]ステートメントは終了されました。

BCP コピー in が失敗しました

一旦テーブルのデータを消してからインポートするとうまくいった。
C:\Users\TestUser>bcp TestDB.dbo.CustomerTable in c:\testest.txt -n -S hostname\SQL2005DEV -U sa -P passwd -E

コピーを開始しています...

15 行コピーされました。
ネットワーク パケット サイズ (バイト): 4096
クロック タイム (ミリ秒) 合計 : 1 平均 : (15000.00 行/秒)


-c(テキスト形式)でエクスポートしたときはこんなファイルになります。

31真実 太郎シンジツ タロウ19760520
62真実 花子シンジツ ハナコ19811114
106岸川 秀樹キシカワ ヒデキ19690619
117鈴本 伊一郎スズモト イイチロウ19560907


あんまり使うことはないかもしれませんが、覚えておくと便利なツールかもしれません。
[PR]
by jehoshaphat | 2009-04-28 01:51 | SQL | Trackback | Comments(0)
(.Net)SQL Server でバックアップ時(復元時)の進行状況を取得しプログレスバーに表示する(ソース編)
(.Net)SQL Server でバックアップ時(復元時)の進行状況を取得しプログレスバーに表示する(説明編)の続きとういより、文字数オーバーで載せれなかったコードです。
(なお、プログレスバーを貼り付けているフォームクラスのコードは面倒なので掲載してません。
クラス名は Progress で、プログレスバーコントロール名は ProgressBar です。)


Public Class TestBackup
 
''' <summary>
''' SQL Server のバックアップを行う(バックアップファイル名は日時.bak)
''' エラー時、成功時ともに、イベントビュアーにログを残す
''' </summary>
''' <returns>True:正常 False:異常</returns>
Public Function BackupNow() As Boolean
'コネクションオブジェクト作成
Dim conn As SqlConnection = Me.GetDbConnection()
'プログレスバー表示クラス生成
prg = New ProgressBarControl()
 
Try
conn.Open()
'コマンドオブジェクト生成
Dim cmd As New SqlCommand()
cmd.Connection = conn
'STATS = 1 なので1秒おきに進捗率を通知
cmd.CommandText = "BACKUP DATABASE [testdatabase] TO DISK = N'C:\test.bak' WITH NOFORMAT, INIT, NAME = N'テスト', SKIP, NOREWIND, NOUNLOAD, STATS = 1"
'復元時だとこういうコードになります。
'cmd.CommandText = "RESTORE DATABASE [testdatabase] FROM DISK = N'C:\test.bak' WITH FILE = 1, NOUNLOAD, REPLACE , STATS = 1"
'これをTrueしないとInfoMessageがまとめて発行されてしまう(ただし、TrueにするとSQLクエリ実行中に例外発生してもスローしないので注意!)
conn.FireInfoMessageEventOnUserErrors = True
'進捗情報取得のためのイベントハンドラを追加
AddHandler conn.InfoMessage, New SqlInfoMessageEventHandler(AddressOf SqlConnect_InfoMassage)
'プログレスバー表示(別スレッド)
prg.Show()
'バックアップクエリ実行
cmd.ExecuteNonQuery()
Catch ex As Exception
'エラー処理のコードがここに入る(ログに残すとかメッセージボックスとか)
Return False
Finally
conn.Close()
If prg IsNot Nothing Then
'プログレスバー表示用別スレッドを停止。プレブレスバー表示フォームを閉じる。
prg.Close()
End If
End Try
Return True
End Function
 
 
''' <summary>
''' 進行状況ダイアログのプログレスバーに進捗を表示する。
''' SqlConnection.InfoMessageのイベントハンドラ。
''' </summary>
Private Sub SqlConnect_InfoMassage(ByVal sender As Object, ByVal e As System.Data.SqlClient.SqlInfoMessageEventArgs)
Dim conn As SqlConnection = DirectCast(sender, SqlConnection)
For Each sqlErr As SqlError In e.Errors
'エラーの重要度が10より大きいなら例外発生させ処理停止
If sqlErr.Class > 10 Then
'Falseにすることで、次回より例外がスローされるようになる
conn.FireInfoMessageEventOnUserErrors = False
'エラーを ApplicationExceptionとして例外生成(しかし、この例外はスローされないっぽい。この次の例外からスローされる)
Dim ex As New ApplicationException(sqlErr.ToString())
'この下のThrowでは実際にはThrowされないようなので、ここでイベントビュアーにエラー出力
'エラー処理のコードがここに入る(ログに残すとかメッセージボックスとか)
Throw ex
End If
Next

'メッセージを分解し、intに変換
Dim idx As Integer = e.Message.LastIndexOf(" パーセント処理されました。")
If idx < 1 Then
Return
End If
'パーセント
Dim intPer As Integer
Try
intPer = CType(e.Message.Substring(0, idx), Integer)
Catch ex As Exception
Return
End Try
'プログレスバー表示更新
If prg IsNot Nothing Then
prg.ValuePercent = intPer
End If
End Sub
End Class
 
 
''' <summary>
''' プログレスバーフォームを別スレッドで表示さすためのラッパクラス
''' </summary>
Public Class ProgressBarControl
 
'進行状況表示クラス(このクラスのコードは省略。ただフォームにプログレスバーを乗せてるだけ)
Private m_statusForm As Progress
'現在のプログレスバーの値
Private m_ValuePercent As Integer
 
''' <summary>
''' プログレスバーの値を設定、取得するプロパティ
''' </summary>
Public Property ValuePercent() As Integer
Get
Return m_ValuePercent
End Get
Set(ByVal value As Integer)
m_ValuePercent = value
If m_statusForm IsNot Nothing Then
'm_statusFormのインスタンスを持っているスレッドで、SetProgressValuePercentメソッドを処理させる
m_statusForm.Invoke(New MethodInvoker(AddressOf SetProgressValuePercent))
End If
End Set
End Property
 
''' <summary>
''' プログレスバーを閉じる。(と同時にプログレスバー表示スレッドも自然と消える)
''' </summary>
Public Sub Close()
If m_statusForm IsNot Nothing Then
'm_statusFormのインスタンスを持っているスレッドで、ProgressCloseメソッドを処理させる
m_statusForm.Invoke(New MethodInvoker(AddressOf ProgressClose))
End If
End Sub
 
 
''' <summary>
''' プログレスバーを表示する。プログレスバー表示クラスを別スレッドで作成し、表示。
''' </summary>
Public Sub Show()
Dim thread As Threading.Thread = New Threading.Thread(AddressOf Run)
thread.Start()
End Sub
 
''' <summary>
''' 別スレッドで動くメソッド。プログレスバー表示クラスのインスタンスを作成し、ShowDialog する。
''' </summary>
Private Sub Run()
m_statusForm = New Progress()
m_statusForm.ShowDialog()
m_statusForm.Dispose()
End Sub
 
 
''' <summary>
''' プログレスバーの値を更新する(MethodInvoker デリゲートから呼ばれる)
''' </summary>
Private Sub SetProgressValuePercent()
m_statusForm.ProgressBar.Value = m_ValuePercent
End Sub
 
''' <summary>
''' プログレスバーを閉じる(MethodInvoker デリゲートから呼ばれる)
''' </summary>
Private Sub ProgressClose()
m_statusForm.Close()
End Sub
 
End Class

[PR]
by jehoshaphat | 2009-04-28 01:36 | .Net開発 | Trackback | Comments(0)
(.Net)SQL Server でバックアップ時(復元時)の進行状況を取得しプログレスバーに表示する(説明編)
Widowsアプリケーションから SQL Server のバックアップ、リストア時に進捗をパーセント表示する方法です。

参考にさせてもらったのはSQLServer2005のバックアップ中に進行状況を得るには です。
ここを見ると、BACKUP DATABASE クエリに WITH STATS を加えると OleDbConnection.InfoMessage で進捗率を取得できるということでした。
SqlConnection クラスにも InfoMessage イベントはあったので簡単に実装できるかと思いきやここからが苦戦しました。

まず、SqlConnection.InfoMessage イベントで WITH STATS で指定した間隔毎にメッセージは習得できるんですが、InfoMessage イベント発生のタイミングが問題でした。どうやら、SqlCommand.ExecuteNonQuery() でクエリを処理し終わった後に InfoMessage イベントが1回だけ走っている用なのです。
クエリ中にたまったメッセージを1回の InfoMessage イベントでコレクションで渡してる感じのようです。

で、これをリアルタイムに(クエリ実行しながら) InfoMessage イベントを起こせないものかと思って MSDN さまよっているとありました。
MSDN:接続イベント (ADO.NET)の「InfoMessages としてのエラー処理」項目で、SqlConnection の FireInfoMessageEventOnUserErrors プロパティを true に設定すると、InfoMessage イベントを発生できる あります。

MSDN:SqlConnection.FireInfoMessageEventOnUserErrors プロパティ のリファレンスを見てみると、「FireInfoMessageEventOnUserErrors を true に設定した場合、それまで例外として処理されていたエラーが InfoMessage イベントとして処理されるようになります。すべてのイベントは即座に発生し、イベント ハンドラによって処理されます。FireInfoMessageEventOnUserErrors を false に設定した場合、InfoMessage イベントはプロシージャの最後で処理されます。 」とありました。

ということで、SqlConnection.FireInfoMessageEventOnUserErrors プロパティを true に指定すると、リアルタイムに InfoMessage イベントが発生し、進捗状況を取得することができるようになりました。

うまくいったかのように思ったんですが、ここでさらに問題が。。
SqlConnection.FireInfoMessageEventOnUserErrors = true にしたことによって、本来クエリ中で発生してた例外がスローしなくなったっぽいのです。
本来 FireInfoMessageEventOnUserErrors プロパティを true にすると例外として処理されていたエラーが InfoMessage イベントとして処理され、例外を発生させなくするものなので、当然と言えば当然。。

しかし、クエリを Try Catch でくくっており例外処理ありきでコーディングしているため、何とかして、例外発生時に Catch で例外を捕捉したいものです。

いろいろ探しまたが、結局スマートな方法はなかったので、強引な方法で行きました。
InfoMessage イベント内でエラーの重大度レベルが一定以上(今回は11以上)なら、FireInfoMessageEventOnUserErrors を false にして ApplicationException を throw するようにしたのです。

あともうひとつスマートではないのは進捗率をプログレスバーに表示するための値ですが、InfoMessage イベントで取得する進行状況のメッセージは "○○ パーセント処理されました。" というものです。仕方なく "パーセント処理されました。" を削除して、int にキャストするようにしました。

とういことで説明が長くまりしたが、コードはこんな感じです。(復元用は省略。バックアップとSQLのクエリが違うだけなので。。)
プログレスバーは別のフォームに貼り付けており、マルチスレッドで値を更新しています。(いつもお世話になっている進行状況ダイアログを表示するを参考にしてます)

と思ってコードを貼り付けようとしたら、Exciteブログが文字数超過エラー。。
仕方なく2ページに分けました。
(.Net)SQL Server でバックアップ時(復元時)の進行状況を取得しプログレスバーに表示する(ソース編)でコードのっけてます。

追記:
SQL Server インストール時に、クライアント ツールをインストールするオプションを選択することで、SMO というSQL Server 管理オブジェクトから容易にバックアップやリストアその他の処理ができるようです。(SQL Server ManagementStudio は内部的に SMO を使ってるっぽいです。)
この記事で取り上げてる進捗率取得ももっと容易にできるようです。
ただ、クライアントツールが入ってないといけないのがちょっとネックですね。
SMO については MSDNマガジン:SQL Server 管理オブジェクトで紹介されてます。(進行状況のサンプルもあります。)
[PR]
by jehoshaphat | 2009-04-28 01:35 | .Net開発 | Trackback | Comments(0)