2008年4月25日金曜日

[他]IDENTITYフィールドに指定の値を突っこむ(SQLServer)

SQLServerでは、フィールドに対してIDENTITYという属性を付けることができます。この属性を設定すると、INSERT時に自動的に値を設定してくれます。MS-Accessでいうところのオートナンバー型ですね。

しかし、事故など(間違えでDELETEしたなど)があると、値を指定できないためそのレコードを復元することができません。たとえばこの値を別のテーブルの値として設定している場合など、データを復元するためにかなりの労力が必要となります。

過去、そういう場合あきらめていましたが、IDENTITY属性のあるフィールドに無理やり値を指定する方法があることがわかりました。リファレンスはこちら
コードはこんな感じ。

SET IDENTITY_INSERT table1 ON;
INSERT INTO table1(pidtex1tex2)values(800,'1','2')
SET IDENTITY_INSERT table1 OFF;


リファレンスをみると、いろんなSET文があります。一通り眺めてみると勉強になりそうですね。

[.NET]INSERT時の自動採番される値を取得する(SQLSERVER)(その2)

GDD Blog: [.NET]INSERT時の自動採番される値を取得する(SQLSERVER)で、自動採番した値を取得する方法について書きましたが、たとえば、同一トランザクション内で自動採番されるテーブルが2つ以上あり、その2のテーブルで採番された値を後方の処理で利用する必要がある場合、SCOPE_IDENTITY()では最後のINSERTで生成した値しか取れません。でどうするかというと、IDENT_CURRENT('table_name')を使います。

■テーブルの構成
CREATE TABLE [TABLE1] (
    [pid] [intIDENTITY (11NOT NULL ,
    [tex1] [char] (10COLLATE Japanese_CI_AS NULL ,
    [tex2] [char] (10COLLATE Japanese_CI_AS NULL ,
    CONSTRAINT [PK_TABLE1PRIMARY KEY  CLUSTERED 
    (
        [pid]
    )  ON [PRIMARY
)
GO


■実行したSQL
begin transaction

insert into table1(tex1tex2)values('1','2')

print '■table1'
SELECT  '@@IDENTITY' , @@IDENTITY 
SELECT  'SCOPE_IDENTITY' , SCOPE_IDENTITY()
SELECT  'IDENT_CURRENT' , IDENT_CURRENT('table1')

insert into table2(tex1tex2)values('1','2')
print '■table2'
SELECT  '@@IDENTITY' , @@IDENTITY 
SELECT  'SCOPE_IDENTITY' , SCOPE_IDENTITY()
SELECT  'IDENT_CURRENT' , IDENT_CURRENT('table1')

rollback


■実行結果
table1
@@IDENTITY    9
SCOPE_IDENTITY    9
IDENT_CURRENT    9

table2
@@IDENTITY    1
SCOPE_IDENTITY    1
IDENT_CURRENT    9


※table1は8件、table2は0件の状態です。

(注意)GDD Blog: [.NET]INSERT時の自動採番される値を取得する(SQLSERVER)(その3)で問題点を記載しています。こちらも参照してください。

2008年4月23日水曜日

[.NET]OracleClient(その2)

GDD Blog: [.NET]OracleClient」で記載しましたODP.NETですが、なんと、Vista環境では自動トランザクション機能(mts)がサポートされていない事がわかりました。
10gだけではなく、最新の11gで提供されているODP.NETも同様のようです。

私の中では、DB使うシステムでは、自動トランザクションは必須なんですけど、これからVistaが普及しだしたら、このままだと開発できないことになっちゃいます。

Oracleが対応するのかどうか傾向もわかりません。はてどう考えればいいものか。。。

2008年4月18日金曜日

[.NET]XmlTextWriterのチョイ技

XmlTextWriterはXMLを出力する操作を助けてくれるクラスです。そしてDomのように重たくなく、且つ直感的な操作で、XMLを作成することができます。

しかし、なぜか「encoding="utf-16"」になってしまいす。 たとえば「encoding="utf-8"」にしたい場合は、WriteProcessingInstructionメソッドにて指定が可能です。(コードのコメント部分を生かす)
また、xml宣言そのものを出したくないといった場合は、「XmlWriterSettings.OmitXmlDeclaration」の指定にて出力しない方法があります。コードはこんな感じ


StringBuilder sb = new StringBuilder();

XmlWriterSettings xs = new XmlWriterSettings();
xs.Indent = true;

using (XmlWriter xw = XmlTextWriter.Create(sbxs))
{
    //xw.WriteProcessingInstruction("xml", "version='1.0' encoding='utf-8'"); //エンコードが指定
    xw.WriteStartElement("ROOT");

    xw.WriteStartElement("AAA");
    xw.WriteAttributeString("aa1""123");
    xw.WriteAttributeString("aa2""456");
    xw.WriteString("ああああ");
    xw.WriteEndElement();//</AAA>

    xw.WriteStartElement("BBB");

    xw.WriteStartElement("BBB1");
    xw.WriteString("いいいい1");
    xw.WriteEndElement();//</BBB1>

    xw.WriteStartElement("BBB2");
    xw.WriteString("いいいい2");
    xw.WriteEndElement();//</BBB2>

    xw.WriteEndElement();//</BBB>

    xw.WriteEndElement();//</ROOT>

    xw.Close();
}


<?xml version="1.0" encoding="utf-8"?>
<ROOT>
  <AAA aa1="123" aa2="456">ああああ</AAA>
  <BBB>
    <BBB1>いいいい1</BBB1>
    <BBB2>いいいい2</BBB2>
  </BBB>
</ROOT>

2008年4月14日月曜日

[.NET]INSERT時に重複レコードを取り除く

たまに、テーブルからテーブルへデータをコピーしたいときがあります。そういう時以下のようなSQLを実行します。

INSERT INTO T_ADRESS_1
  SELECT * FROM T_ADRESS_2
  WHERE
  T_ADRESS_2.ADDRESS LIKE '大阪府%'


これは、T_ADRESS_2のADDRESSが「大阪府」で始まるレコードをT_ADRESS_1にコピーするというSQLになります。

しかし、T_ADRESS_1にすでに登録されている情報がある場合、キー重複エラーになったり、不要なレコードが増えてしまいます。

1件ずつチェックしながらデータをチェックしながらコピーすれば確実なんですが、件数が多い場合パフォーマンスが出ません。

で、よく考えてみると、INSERTする前にT_ADRESS_1を同じ条件でDELETEしておけば。。。ということにいまさら気がつきました。。。その他、NOT EXISTを使う方法もありますね。

2008年4月13日日曜日

[他]コーディング規約

ソフト開発では、1つのシステム開発を複数の会社が集まって開発することがよくあります。で、そういった場合は、大体その中の1社が幹事会社となり、いろんなことを決めます。たとえば設計書のフォーマットなど。

設計書のフォーマットもモメますが、問題はコーディング規約です。 後のメンテを考えると尾を引きます。
機能ID+連番みたいなクラス名命名だったり、.NET系の開発なのに、C言語風のメソッド(関数?)命名であったり、10年くらい前にVC++で採用されていたような変数名のつけ方を採用するなど、今時の統合開発環境であれば変数名から類推する必要もない事を規約にされた場合、開発するときのテンションがすごく下がっちゃいます。 特に変数名のつけ方はアレですね。

そこで何とか今風にできないかと考えたのですが、「ms社にそれらしい規約があれば説得力があるのでは?」と同僚に言われ早速検索。すると、以下のようなものを見つけました。
前回は、ドキュメントのフォーマットを押し付けられたので、「Visual Basic のコーディング規則 」をもとに(ちょっと微妙)。。。でも「Visual Basic の名前付け規則」ちょっと弱い。けどプレフィックスについての記述もないし出元がms社なので、意外と説得力があるかも。。。

2008年4月10日木曜日

[.NET]例外の処理

最近のオブジェクト指向言語には、言語仕様に例外処理の機構が組み込まれています。 c#の場合、こんな感じですね。

try
{
    //例外の発生する可能性のある処理
}
catch (Exception ex)
{
    //例外の出た場合の処理
}
finally
{
    //リソースの開放処理
}


しかし、処理の方法が人によりまちまちで、困る場合があります。
たとえば、
  • 入力チェックに例外処理を利用している
  • 事前にチェックすれば発生しない例外の考慮をしている
  • 握りつぶしてはマズイ例外を握りつぶしている
  • 例外処理をしているが、資源の解放漏れがある
  • そもそも例外の発生を考慮していない

などあります。そこで、何かいいガイドラインは無いかと思って調べてみると、MSDNにそれらしいことが記載してありました。
その中で「Exception 基本クラスからユーザー定義例外を派生しないでください。ほとんどのアプリケーションでは、ApplicationException クラスからカスタム例外を派生します。 」
とありますが、@it掲示板によるとApplicationExceptionからの派生はあまりよくないとのことらしい。

私の解釈は、以下のとおりです。

  • 握りつぶしてよい例外以外はcatchしない
  • (基本的に無いけど)catchした場合、その例外をまたはシステム共通の例外をthrowしなおす
  • 例外処理は一箇所で行う。同時にここでログ(デバッグ用)を出力する(ASP.NETの場合、Application_Error。WindowsFormの場合Application.ThreadExceptionで処理する)

例外のハンドリング方法としてはちょっと古いかも知れませんが、まぁコレが今のところ一番いいかなぁと思っています。

ちょっといまさら感がありますが、例外処理に関してはEnterprise Library(EHAB)があります。今度こっちを調査いてみよう。



あ、資源の解放という点ではfinallyよりusingで処理します(C#)。

2008年4月7日月曜日

[.NET]BackgroundWorkerの終了方法

.NET Framework2.0以降では、BackgroundWorkerというスレッド(並行処理)を簡単に扱うクラスが存在します。使い方はこんなかんじ。

BackgroundWorker bw = new System.ComponentModel.BackgroundWorker();
bw.DoWork += new System.ComponentModel.DoWorkEventHandler(this.DoWork);
bw.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.RunWorkerCompleted);
bw.RunWorkerAsync();

private void DoWork(object sender, DoWorkEventArgs e)
{
     //バックグラウンド処理
}

private void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
     //バックグラウンド終了処理
}


しかし、以下のようにスレッドの終了待ちうけをしていると、RunWorkerCompletedが呼ばれないのです。

// スレッドの終了待ち
while (bw.IsBusy)
{
     Thread.Sleep(500);
}


で、以下の方法を試したところ、それらしく動作しました。

// スレッドの終了待ち
while (bw.IsBusy)
{
     Application.DoEvents();
}


DoEventsの呼び出しはあまり好きではありません。出来れば使わない方法があるといいんですが。。。。

2008年4月2日水曜日

[.NET]VS2005のプロジェクトをVisual C# 2008 Express Editionで開いてみました

最近新しいマシンを購入したので、Visual C# 2008 Express Editionを入れてみました。
そこで、VS2005で作ったC#・WindowsFormで作ったプロジェクトを開いてみました。

一部のファイルが更新されましたが、開くことができました。画像を表示しグレートーンに変換するアプリケーションですが、それらしく動作しました。以下にVisual C# 2008 Express Editionが更新したファイルを列挙します。

■ソリューションファイル(.sln)
  • 1行目のFormat Versionが 9.00から10.00
  • 2行目のツール名?がVisual Studio 2005 から Visual C# Express 2008

■プロジェクトファイル(.csproj)

  • 1行目の最後に、 ToolsVersion="3.5"というアトリビュートが追加された
  • <PropertyGroup><FileUpgradeFlags>,<OldToolsVersion>,<TargetFrameworkVersion>,<UpgradeBackupLocation>というタグが追加された

■Resources.Designer.cs

  • コメントのランタイム バージョンが2.0.50727.832 から 2.0.50727.1433

■Resources.Designer.cs

  • コメントが日本語になった
  • コメントのランタイム バージョンが2.0.50727.832 から 2.0.50727.1433
  • 属性Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGeneratorの値が8.0.0.0 から 9.0.0.0

プロジェクトファイルの<TargetFrameworkVersion>がv3.5になっていました。
で、調べてみると、プロジェクトの設定が.NET Framework3.5の設定になっていました。プロジェクトの設定を変更し2.0系と指定するとv2.0となりました。(設定を変更すると、プロジェクトファイルの「<TargetFrameworkVersion>」以外のタグがなくなります)

今度は、このバイナリーが2.0のみの環境で動作するか、このプロジェクトがVS2005系のツールで開けるかを確認してみよう。

2008年4月1日火曜日

[.NET]MailSlot(その3)

GDD Blog: [.NET]MailSlot(その2)」で掲載しましたMailSlot操作クラスですが、ReadFileで受信待機をします。そのため、
  • 何かしら情報を受信しないと終わらない
  • すべての受信が終わるまで、受信メッセージを取得する方法が無い

という問題がありました。そこで

  1. 待機ループし、終了フラグを監視する
  2. イベントを発行する機構を実装する
  3. IDisposableをインプリメント

することにしました。



以下、メールスロットを送受信するクラスです。
/// <summary>
/// メールスロットの操作を行います。
/// </summary>
public class MailSlot : IDisposable
{
    #region Win32 API宣言

    [DllImport("kernel32.dll")]
    static extern SafeFileHandle CreateMailslot(
        string lpName,          //メールスロット名
        uint nMaxMessageSize,   //最大メッセージサイズ
        uint lReadTimeout,      //み取りタイムアウトの間隔
        IntPtr lpSecurityAttributes);   //継承オプション

    [DllImport("kernel32.dll")]
    static extern bool GetMailslotInfo(
        SafeFileHandle hMailslot,   //メールスロットのハンドル
        ref uint lpMaxMessageSize,    //最大メッセージサイズ
        ref uint lpNextSize,          //次のメッセージのサイズ
        ref uint lpMessageCount,      //メッセージ数
        ref uint lpReadTimeout);      //読み取りタイムアウトの間隔


    [DllImport("kernel32.dll")]
    static extern bool SetMailslotInfo(
        SafeFileHandle hMailslot,   //メールスロットのハンドル
        uint lReadTimeout);         //読み取りタイムアウトの間隔

    [DllImport("kernel32.dll"SetLastError = true)]
    [returnMarshalAs(UnmanagedType.Bool)]
    static extern bool CloseHandle(SafeFileHandle hMailslot);

    [DllImport("kernel32.dll"CharSet = CharSet.Auto)]
    private static extern SafeFileHandle CreateFile(
        string lpFileName,              // ファイル名 
        DesiredAccess dwDesiredAccess,  // アクセスモード 
        ShareMode dwShareMode,          // 共有モード 
        int lpSecurityAttributes,       // セキュリティ記述子 
        CreationDisposition dwCreationDisposition,  // 作成方法 
        FlagsAndAttributes dwFlagsAndAttributes,    // ファイル属性 
        IntPtr hTemplateFile);          // テンプレートファイルのハンドル 

    #endregion

    #region 列挙体

    private enum DesiredAccess : uint
    {
        GENERIC_READ = 0x80000000,
        GENERIC_WRITE = 0x40000000,
        GENERIC_EXECUTE = 0x20000000
    }

    private enum ShareMode : uint
    {
        FILE_SHARE_READ = 0x00000001,
        FILE_SHARE_WRITE = 0x00000002,
        FILE_SHARE_DELETE = 0x00000004
    }

    private enum CreationDisposition : uint
    {
        CREATE_NEW = 1,
        CREATE_ALWAYS = 2,
        OPEN_EXISTING = 3,
        OPEN_ALWAYS = 4,
        TRUNCATE_EXISTING = 5
    }

    private enum FlagsAndAttributes : uint
    {
        FILE_ATTRIBUTE_ARCHIVE = 0x00000020,
        FILE_ATTRIBUTE_ENCRYPTED = 0x00004000,
        FILE_ATTRIBUTE_HIDDEN = 0x00000002,
        FILE_ATTRIBUTE_NORMAL = 0x00000080,
        FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000,
        FILE_ATTRIBUTE_OFFLINE = 0x00001000,
        FILE_ATTRIBUTE_READONLY = 0x00000001,
        FILE_ATTRIBUTE_SYSTEM = 0x00000004,
        FILE_ATTRIBUTE_TEMPORARY = 0x00000100
    }


    private enum MailSlotStatus : uint
    {
        MAILSLOT_NO_MESSAGE = 0xffffffff,
    }


    #endregion

    /// <summary>
    /// 受信イベントハンドラーのdelegate
    /// </summary>
    /// <param name="sender">送信元</param>
    /// <param name="e">イベントオブジェクト</param>
    public delegate void ReceiveEventHandler(object senderReceiveEventArgs e);

    //イベントハンドラー
    public event ReceiveEventHandler receive;


    private SafeFileHandle slotHandle = null;
    /// <summary>
    /// メールスロットスロットハンドル
    /// </summary>
    public SafeFileHandle SlotHandle
    {
        get { return slotHandle; }
        set { slotHandle = value; }
    }

    private bool receiveLoop = true;
    /// <summary>
    /// 受信ループ
    /// </summary>
    public bool ReceiveLoop
    {
        get { return receiveLoop; }
        set { receiveLoop = value; }
    }


    /// <summary>
    /// コンストラクタ
    /// </summary>
    public MailSlot()
    {
    }


    /// <summary>
    /// メールスロットオープン
    /// </summary>
    /// <param name="slot">メールスロット名</param>
    /// <param name="maxMessageSize">最大メッセージ数</param>
    /// <param name="readTimeout">タイムアウト</param>
    public void Open(string slotNameuint maxMessageSizeuint readTimeout)
    {
        // メールスロットをオープンし、ハンドルをプロパティに保存
        SlotHandle = CreateMailslot(slotNamemaxMessageSizereadTimeout, (IntPtr)0);
    }

    /// <summary>
    /// メールスロットクローズ
    /// </summary>
    public void Close()
    {
        // SlotHandleのnull判定がマルチスレッドでも正しく動作するようロックする
        lock (this)
        {
            if (SlotHandle != null)
            {
                //メールスロットを開放する
                CloseHandle(SlotHandle);
                SlotHandle = null;
            }
        }
    }


    /// <summary>
    /// メールスロット受信待ち
    /// </summary>
    public void ReadMailSlot()
    {
        try
        {
            //受信待機フラグがたっている間ループする
            while (ReceiveLoop)
            {

                //メールスロットの件数をチェックする
                uint msgMaxCnt = 1;
                uint nextSize = 0;
                uint msgCnt = 0;
                uint timeout = 0;

                //メールスロット情報取得
                if (!GetMailslotInfo(SlotHandleref msgMaxCntref nextSizeref msgCntref timeout))
                {
                    System.Diagnostics.Debug.WriteLine("GetMailslotInfo失敗!!");
                    continue;
                }

                //メールスロット読み取り 
                if (msgCnt > 0 && nextSize != (uint)MailSlotStatus.MAILSLOT_NO_MESSAGE)
                {
                    // スロットを読み取る
                    FileStream fs = new FileStream(SlotHandleFileAccess.Read);
                    byte[] buf = new byte[msgMaxCnt];
                    int len = fs.Read(buf0buf.Length);

                    //イベント発火
                    ReceiveEventArgs e = new ReceiveEventArgs(Encoding.UTF8.GetString(buf0len));
                    OnReceive(e);

                    //処理終了フラグの確認
                    if (e.ReceiveEnd)
                    {
                        ReceiveLoop = false;
                    }
                    //fs.Close();
                    //break;

                }
                else
                {
                    //待機する
                    Thread.Sleep(500);
                }
            }

        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.Message);
            System.Diagnostics.Debug.WriteLine(ex.StackTrace);
            throw ex;
        }
    }

    /// <summary>
    /// 受信イベント
    /// </summary>
    /// <param name="e">イベントオブジェクト</param>
    protected virtual void OnReceive(ReceiveEventArgs e)
    {
        if (receive != null)
        {
            receive(thise);
        }
    }

    #region IDisposable メンバ

    /// <summary>
    /// 資源の解放
    /// </summary>
    public void Dispose()
    {
        Close();
    }

    #endregion

    /// <summary>
    /// メッセージの送信
    /// </summary>
    /// <param name="slot">送信先スロット名</param>
    /// <param name="message">送信する文字列</param>
    public static void WriteMailSlot(string slotstring message)
    {

        try
        {
            //メールスロットを開く
            SafeFileHandle fileHandle = CreateFile(slot,
                    DesiredAccess.GENERIC_READ | DesiredAccess.GENERIC_WRITE,
                    ShareMode.FILE_SHARE_READ | ShareMode.FILE_SHARE_WRITE,
                    0,
                    CreationDisposition.OPEN_EXISTING,
                    FlagsAndAttributes.FILE_ATTRIBUTE_NORMAL,
                    (IntPtr)0);

            // 指定の文字列を出力する
            using (FileStream fs = new FileStream(fileHandleFileAccess.Write))
            {
                byte[] msg = Encoding.UTF8.GetBytes(message);
                fs.Write(msg0msg.Length);
            }
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.Message);
            System.Diagnostics.Debug.WriteLine(ex.StackTrace);
            throw ex;
        }
    }

}


以下、受信イベントオブジェクトクラスです。
/// <summary>
/// 受信イベントオブジェクト
/// </summary>
public class ReceiveEventArgs : EventArgs
{
    private string message;
    /// <summary>
    /// 受信メッセージ
    /// </summary>
    public string Message
    {
        get { return message; }
    }

    private bool receiveEnd = false;
    /// <summary>
    /// 受信終了フラグ
    /// </summary>
    public bool ReceiveEnd
    {
        get { return receiveEnd; }
        set { receiveEnd = value; }
    }

    /// <summary>
    /// コンストラクタ
    /// </summary>
    /// <param name="msg">メッセージ</param>
    public ReceiveEventArgs(string msg)
    {
        message = msg;
    }

}


使うほうはこんな感じ。

/// <summary>
/// 受信ボタン押下
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnMLReceive2_Click(object senderEventArgs e)
{
    using (MailSlot slot = new MailSlot())
    {
        //イベントを設定後、スロットをオープンし受信ループに入る
        slot.receive += new MailSlot.ReceiveEventHandler(slot_receive2);
        slot.Open(txtMQPath.Text5120xffff);
        slot.ReadMailSlot();

        // スロットをクローズする
        slot.Close();
    }
}

/// <summary>
/// 受信イベント
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void slot_receive2(object senderReceiveEventArgs e)
{
    //受信したメッセージを画面に表示
    txtMQRecieve.AppendText(e.Message);
    txtMQRecieve.AppendText(Environment.NewLine);

    //以下の文字列が着たら受信待機をやめる
    if (e.Message == "TERMINATE")
    {
        //MailSlot.ReadMailSlotが終了する
        e.ReceiveEnd = true;
    }
}