月別アーカイブ: 2009年10月

5 posts

胆嚢結石

私は胆嚢に以前から石を抱えており、一昨年この石が胆嚢の出口につまり、大変痛い思いをしました。

胆嚢の痛みは、石が胆嚢の出口に詰まることで胆汁が排出されなくなり、胆嚢がはれることによるものです。
そのときは薬を飲んで、様子をみていると数日で痛みはなくなり、それで終わりです(胆石はころころよく移動するのです)。
ただし、胆石がいつまた出口に詰まるかわかりませんので、爆弾をかかえているようなものです。

胆石は癌化する恐れが強いということなので、いつか取り出さなければいけないと思っていました。

火曜日おなかが痛いなと思っていたのですが、やがて沈静化したので、忘れていました。
ところが水曜日になってまた痛んできました。

痛烈ではないのですが、鈍痛が持続し止みません。
夕方地域の総合病院に行きました。

直ちにCTでおなかの写真をとると、確かに胆嚢に二つの石が白くはっきり写っています。
しかしもう一つが胆嚢からでて、胆管にとどまっています。

「胆管は十二指腸につながっています」と内科医。
私は「やれやれ。十二指腸から腸に落ちていけば、今回はこれで終わりだ」と思ったのですが、内科医の顔が優れません。
「外科にいってください」

たまたま専門の外科医がいて、図を描いて説明してくれました。

胆管の出口は十二指腸につながっているのだが、その出口が大変狭く、しかも出口近くに重要な臓器(すい臓と脾臓だったか?)がついていて、石がとどまるとそれらの臓器にダメージを与え、重篤になるとのこと。
「直ちに手術です。胆管に詰まった石は胃から内視鏡を使って取り出し、胆嚢にはまだ石がありますので、引き続き胆嚢そのものも摘出します」との説明。
手術の手配をしてくれましたが、胃からの取り出しは専門の内科医が施術するそうで、その手配ができなくて、結局来週の土曜日の手術になりました。
それまで悪化しなければいいのですが…

現在まで痛みは殆どありません。「悪化しないでね」と願うばかりです。

ところで私はこの病院に20年以上通っています。1年か2年に一回いろいろな箇所の検査をしてもらっています。
胆嚢もここに通い始めてまもなく「ポリープがあります」と告げられていたので、気をつけていました。

この病院は古く、狭い道をアルバイトのおじさんに誘導してもらいながらやっと駐車場に入っていく状態でしたが、丁度一年前近くに移転しました。
これまでのいかにも古いシステムの病院と違って、駐車場も広々した無人の有料駐車場に変わり、病院全体が新しいスタイルに生まれ変わりました(ただし非採算部門の縮小も伴っているようです)。

検査は当然検査室で受けます。MRIもCTも。
これまでは写真等の検査結果は患者自身が検査室で受け取って、また診察室に持って行って、医者がフィルムを透光板にかざした見ていました。

新病院では、患者は検査が終わるとまた診察室前の待合室で待っていればよく、検査結果は検査室から直ちに電子情報として診察室に送られ、パソコンの大きな画面に映し出し、マウスを使って連続画像として表示します。
しかも、お腹を横割りにも縦割りにもして見せますので、特にはっきり分かる胆石などは素人でもよく分かります。

私はハードに近い開発をしていませんが、このあたりのコンピュータの発達はいろいろ進んでいるのだなと実感しています。

MyStringBuilder

.NETで文字列を連結すると効率が悪いといわれています。

例えばこんな具合です。

    Dim str As String
    str = "雨にも負けず"
    str = str & "風にも負けず"  & vbCrLf
    str = str & "雪にも夏の暑さにも負けぬ" & vbCrLf
    str = str & "丈夫なからだをもち" & vbCrLf
    str = str & "慾はなく" & vbCrLf
    str = str & "決して怒らず" & vbCrLf
    str = str & "いつも静かに笑っている" & vbCrLf

マイクロソフトでは、StringBuilderを使うように推奨しています。
(ただし、文字列が決まっていればStringの連結が勝っているようです。)

そこでStringBuilderを自分の都合に合わせてを改良したいと思います。
StringBuilderが継承できれば楽なのですが、StringBuilderは継承できませんので、StringBuilderを要素にもつ独自のStringBuilderを作成しました。

主要な部分は次のとおりです。

Public Class MyStringBuilder

    Private m_sb As StringBuilder

    ....
    Public Sub Append(ByVal strIn As String, Optional ByVal blnReset As Boolean = False)
        If blnReset Then Reset()
        m_sb.Append(strIn)
    End Sub

    Public Sub AppendFormat(ByVal strFormat As String, ByVal ParamArray args() As Object)
        m_sb.AppendFormat(strFormat, args)
    End Sub

    .....
    Public Function Conc(ByVal strAdd As String, Optional ByVal blnReset As Boolean = False)
        If blnReset Then Reset()
        m_sb.Append(strAdd)
        m_sb.Append(ControlChars.CrLf)
    End Function

    Public Function ConcFormat(ByVal strFormat As String, ByVal ParamArray args() As Object)
        m_sb.AppendFormat(strFormat, args)
        m_sb.Append(ControlChars.CrLf)
    End Function

Appendは機能的にはStgringBuilderと同じです。

Concは次のようにメッセージの作成等改行を入れたいとき使います。先の例では、

    Dim MySB As New MyStingBuilder
    With MySB
        .Conc("雨にも負けず")
        .Conc("風にも負けず")
        .Conc("雪にも夏の暑さにも負けぬ")
        .Conc("丈夫なからだをもち")
        .Conc("慾はなく")
        .Conc("決して怒らず")
        .ConcFormat("{0}{1}{2}", "いつも", "静かに", "笑っている")
    End With
    MessageBox.Show(MySB.ToString, "MyStringBuilder", )

読みやすくなっていると思いませんか。

SQLでは改行を入れるといけません。プロパーのStringBuilderのAppendでいいのですが、一貫して[MyStringBuilder]を使いたいので[MyStringBuilder]用のAppendを定義しています。次のように使います。

    Dim MySB As New MyStingBuilder
    With MySB
       .Append("Select * From 書籍")
       .AppendFormat(" Where 著者名='{0}'", "司馬 遼太郎")
       .AppendFormat(" And 出版日 Between '{0}' And {1}", _
           New Date(1990, 1, 1), New Date(1996, 2, 1))
    End With

連続する二つの句の間にスペースが必要なので、実際にはスペースを入れるようなメソッドを用意しています。

ところで、ここには誤りがあります。
最後の文の{1}プレースホルダをクォートする必要があります。

日付リテラルはSQL Serverではシングルクォートで、Accessでは#で囲む必要があります。

私は、ディレクティブを使ってラッパー関数を用意しています。

    Public Function DateWrapper(ByVal dte As Date) As String
#If DB = "SQLServer" Then
        Return (String.Format("'{0}'", dte.ToShortDateString))
#ElseIf DB = "Access"
        Return (String.Format("#{0}#", dte.ToShortDateString))
#Else
        ....
#End If
    End Function
[DateWrapper]を通せば、DBに何を使っているかその都度気にする必要はありません。
従って先の例は次の通りです。

        .AppendFormat(" And 出版日 Between {0} And {1}", _
           DateWrapper(New Date(1990, 1, 1)), DateWrapper(New Date(1996, 2, 1)))

  

TextBoxの拡張=TextBoxEx

ちょっとした、でもとても重宝しているプログラムをご紹介します。

最初は、私が作ったものではなく、Balenaの「プログラミング Visual Basic.NET」に載っていたものです。

邦訳は2002年日経BPソフトプレスから出版され、付属のCDにソースが入っていました。そのままここに載せると著作権に抵触するかもしれません。Balenaのブログページ(http://www.dotnet2themax.com/blogs/fbalena/)から”Programming Microsoft Visual Basic .NET 2003″Chapter 18を開くと原書のサンプルページがありコードが載っています。

これは、TextBoxを継承したカスタムコントロールです。これによって、データ型や正規表現のチェックが可能です。
以下にこのプログラムの主要な部分を転載します。当然細かいところは分かりませんが、プログラムの雰囲気は分かると思います。

' 拡張されたTextBoxコントロール
Public Class TextBoxEx
    Inherits System.Windows.Forms.TextBox

    Event InvalidKey(ByVal sender As Object, ByVal e As EventArgs)

    Dim m_IsRequired As Boolean
    <Description("入力が必須である場合には、Trueにします。"), Category("検証")>_
    Property IsRequired() As Boolean
	...... 省略
     End Property

    Dim m_ErrorMessage As String
    <Description("エラー時に表示されるメッセージです。"), Category("検証"。")>_
    Property ErrorMessage() As String
	...
    End Property

  ... 省略

    ' ValidateRegexプロパティ。
     <Description("正規表現を使用して、データを検証します。"), Category("検証")>_
    Property ValidateRegex() As String
        Get
            Return m_ValidateRegex
        End Get
        Set(ByVal Value As String)
            ' これが有効な正規表現であることを検査します。
            Try
                If Value <> "" Then
                    Dim dummy As Boolean = Regex.IsMatch("abcde", Value)
                End If
                m_ValidateRegex = Value
            Catch ex As Exception
                MessageBox.Show(ex.Message, "Invalid Property", _
                        MessageBoxButtons.OK, MessageBoxIcon.Error)
            End Try
        End Set
    End Property

    Dim m_ValidateRegex As String

    ' エラーメッセージを表示するオプションのコントロール。
     Dim m_DisplayControl As Control

    <Description("エラーメッセージを表示するコントロールです。"), Category("検証")>_
    Property DisplayControl() As Control
	...... 省略
    End Property

    ' コントロールに設定される値の型です。
    Enum ValidTypes
        Any = 0
        [Byte]
        [Short]
        [Integer]
                     ... 省略
        [DateTime]
    End Enum

    Dim m_ValidType As ValidTypes

    <Description("入力する値のデータ型です。"), Category("検証")>_
    Property ValidType() As ValidTypes
        ......
    End Property

    ' Validateメソッド
     <Description("現在の値が検証テストにパスすると、Trueを返します。">_
    Function Validate(Optional ByVal DisplayMessage As Boolean = True) As Boolean
        Validate = True

        If Me.IsRequired And Me.Text = "" Then
            Validate = False
        End If
        If Validate = True And Me.Text <> "" Then
            Validate = CheckValueType(Me.Text)
        End If
        If Validate = True And Me.ValidateRegex <> "" Then
            Validate = Regex.IsMatch(Me.Text, Me.ValidateRegex)
        End If

        If DisplayMessage And Not (DisplayControl Is Nothing) And Me.ErrorMessage <> "" Then
            If Validate Then
                DisplayControl.Text = ""
            Else
                DisplayControl.Text = Me.ErrorMessage
                DisplayControl.ForeColor = m_ErrroForeColor
            End If
        End If

        If Not Validate And Me.BeepOnError Then Beep()
    End Function

    Function CheckValueType(ByVal o As Object) As Boolean
        Dim res As Object
        Try
            Select Case m_ValidType
                Case ValidTypes.Byte : res = CByte(o)
                Case ValidTypes.Short : res = CShort(o)
                     ... 以下省略
            End Select
              Return True
        Catch
            Return False
        End Try
    End Function

    ' このメソッドは、コントロールがValidatingイベントを発生させるときに実行します。
    Protected Overrides Sub OnValidating(ByVal e As System.ComponentModel.CancelEventArgs)
        If Me.Validate() Then
            MyBase.OnValidating(e)
        Else
            e.Cancel = True
        End If
    End Sub
End Class

このプログラムで一番重要なところは、最後のOnValidatingです。このコントロールにデータを入力し、抜けようとするとこのメソッド(イベント)が起動し、データの整合性をチェックし、誤りがあれば指定のコントロールあるいはErrorProviderにメッセージを表示します。
(このプログラムではエラーメッセをLabel等のコントロールに表示するようになっていますが、私はこの部分をErrorProviderに変える等少し手を入れて使っています。)

このプログラムは、Dllとしてコンパイルしこれを参照設定することで、通常のコンポーネント(TextBox等)と同様に使うことができます。

私は、数字入力および正規表現チェックには必ずこのTextBoxExを使います。

ついでですが、私は郵便番号ではMaskedTextBoxを使いますが、電話番号、携帯電話、Emailの入力にはこのTextBoxExを使います。
郵便番号は、日本中で桁数が同じですが、電話番号では市外、市内局番等桁数が異なりますので、TextBoxExでの正規表現のチェックの方が柔軟性があるからです。
ちなみに、次のような正規表現を使っています(どこかから探してきて自分なりに修正したものです。正誤の保障はありません。Emailはそのまま)。

郵便番号 "^d{3}-d{4}$"
電話番号 "^(d{2,4}-){0,1}d{2,4}-d{4}$"
携帯番号 "^d{2,4}-d{2,4}-d{4}$"
Email "^([w-.]+)@(([[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.)|(([w-]+.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(]?)$"

Entity Framework と MyDataBinding

LINQでは基本的に、厳密に型指定されたオブジェクトを使用します。これのいいところと悪いところがあります。

いいところは、タイプインの途中でインテリセンスがコーディングをサポートしてくれますので、ケアレスミスを防いでくれます。パフォーマンスも多少いいかも知れません。

ところがDBから抽出したデータをコントロールに表示したり、逆にコントロールのデータをDBに保存するときのコードが多少煩わしくなります。
この原因を作っているのは、私が[Data Binding]を嫌っているからです。.NETの[Data Binding]を信頼して使うのであればすべてがハッピーです。

私が[Data Binding]を嫌っているのは、(私が)この動きを十分にコントロールできないからです。私の予想を超えた動きをするので、「使えない」と(もしかして)勝手に思っているのです。また、[Data Binding]でFormatおよびParseを細かくプログラミングできない(と思っている)のも(しかも決定的)一因です。

[Data Binding]を使わないでデータをコントロールに表示するには、次のようにEntity(オブジェクト)のプロパティを一つずつコントロールのTextプロパティにアサインすることになります。

(いま、contextはEDMのインスタンス、BooksはこのEDMに含まれるEntitySet名、Title、AuthorおよびPriceはBook Tableの列名(=Entityのプロパティ名)とします。
また、画面にTextBox、txt書籍名、txt著者名およびtxt価格が配置されているとします。)

    Dim query = (From book In context.Books).First
    txt書籍名.Text = query.Title
    txt著者名.Text = query.Author
    txt価格.Text = query.Price

一般には画面には沢山のコントロールが配置されていますので、上のコードは書きたくありません。

ところでADO.NETでは次のように列名を文字列で書くことができます(いま検索した[DataRow]をdrとします)。

    txt書籍名.Text = dr("Title")
    txt著者名.Text = dr("Author")
    txt価格.Text = dr("Price")

したがって、コントロール[txt書籍名]と列名”Title”等のマップをあらかじめ作成しておけば、このマッピングを使ってコントロールのプロパティにアサインする仕組みを考えることができます。
例えば、このマッピングは9月18日、23日ブログで紹介しました[CtrlBindingCollection]を使います。

[CtrlBindingCollection]のメソッド[Display]を次のように定義します。
(正確ではありません。概念的な説明と理解してください。また以下で、[P_lst]、[P_Control]、[P_列名]はそれぞれ[m_lst]、[m_Control]、[m_列名]に対応したプロパティです。)

  Sub Display(ByVal dr As DataRow)
    For each c As Cell In P_lst
        c.P_Control.Text = dr(c.P_列名)
    End For
  End Sub

そうすれば、

    Dim CBC As New CtrlBindingCollection
    With CBC
     .Bind(txt書籍名, “Title”, “String”, True)
    .Bind(txt著者名, “Author”, “String”, True)
    .Bind(txt価格, “Price”, “Integer”, True)
    End With

(ここで、Bindメソッドの第一引数はコントロール、第二引数はそのコントロールに対応したDBテーブルの列名、第三引数はそのデータ型–説明のため”String”等の文字列で書いていますが、実際には独自に定義したデータ型のEnum値です–、第四引数はエンターキーで次のコントロールに移動するかどうかのフラグです)

を実行しておけば、

CBC.Display(dr)

でコントロールにデータを表示することができます(予めdrは読み込まれたDataRowだとします)。
逆にDB保存用のメソッドを作成することで、データのDBへの書き出しを一つのメソッドのコールで完了することができます。
この[CtrlBindingCollection]を十分練って汎用的にしておけば、このコレクションに登録したコントロールを一斉に操作することができます。

では、厳密に型指定されたオブジェクトを基本としているLINQではどうする。

先の例で、[txt書籍名]と [query.Title]とのマップを作ってみても何の役にも立ちません([txt書籍名]と dr(“Title”)
のマップを作っても役に立たないのと同じです)。
なぜなら、[query.Title]はインスタンスのプロパティですので(評価されて例えば”坂之上の雲”であり)、これと[txt書籍名]とのマップを保存してみても意味がないのです。

結論からいえば、解決策は[Programming Entity Framework]の17章に書かれています。

Entity Framework 奮闘記

LINQの勉強を始めたのは今年6月です。その後最新技法が[Linq to Entity]であると分かり、舵を[Entity Framework](以下EFといいます)の勉強に切り替えました。しかし[LINQ to Entity]の手ごろな教科書がありません。

LINQの勉強で4冊の本を買いました。

1. [プログラミング LINQ] Microsoft Press
2. [LINQ in Action] Manning
3. [Programming Entity Framework] O’Reilly
4. [murach’s ADO.NET 3.5 LINQ and the Entity Framework with VB 2008] Marach

2は教科書としてはよく書かれていますが、残念なことに2008年1月の出版ですので、昨年秋リリースされたEFの話題がありません。

1は和訳ですが、翻訳が悪いとの印象で必要な箇所の拾い読みをしています。原書は2008年の出版で、EFの解説が余りありません。訳書で付録としてEFの話題を追加していますが、どちらにしても入門程度の解説です。

4は各見開きの左ページは解説、右ページは表やコード、と徹底した構成になっていて、読みやすさを第一にしていますが、内容は入門程度です。

EFの解説書としては3が最適ですが、細かい字で構成も読みにくい。著者はおそらく教科書として書く気はまったくなく、「分かるやつは分かる」と至ってマイペースで書いていますので、私の力では800ページ弱のこの分厚い本を完全に理解するには半年程度必要かもしれません(当然勉強にいつまでも時間をとることはできません)。

しかし、考えてみればVisual Sdudioがもっともいい先生です。いつも文句も言わず根気よく私の解答の採点をしてくれます。ともかくプログラムを書いてVisual Sudioで格闘していれば大体話は分かってきます。

さて、こんな状態でともかくEFのプログラムを書いています。

LINQのいい点はDBアクセスプログラムが驚くほどが簡単に短くなったということ、
また、一度プログラムに読み込んだデータに対して、Queryを発行できますので(これはLINQの開発の目的のひとつでしょうが)、強力なデータ操作技術であることは間違いありません。

悪い点は(というより、困った点といった方がいいかも知れません)、一言で言えば「完成度が低い」ということです。
たとえば、ウィザードを使って[Entity Data Model](以下EDMといいます)を作成すると、データベースのテーブルに対応したクラスをプログラム上に作成します。この操作はデザイン画面で確認できますが、このモデルのメンテナンスがうまくない。
ウィザードで、あるテーブルをEDMに読み込んで、都合で削除したとします。再度そのテーブルをこのEMDに追加することができません。どうしてもこのEDMに入れたいのであれば、このEDMをゼロから再構築しなければいけません。これはこれで大騒ぎになります。

プロジェクトで一つのEDMだけ使うことにして全部のテーブルを読み込むのも一つの解決策かも分かりません。私は経験が少ないので、全部を一つのEDMにするのがいいかどうか分かりません。プロジェクトで数10以上のテーブルを使うのは珍しいことではないでしょう。
これを一度に一つのEDMにするのは、「パフォーマンスの点でどうなのか」、「大きなEDMのメンテナンスで手のつけられない大騒ぎになるのではないか」、逆にEDMを小割りにすると、EDM相互の関連が必要になったとき、また別の厄介な問題が発生し、どう処理するか。
どちらの道がいいのかまだ判断がつきません。

[LINQ to Entity]の勉強は何故か「しんどい」。たぶん理由のひとつは、[Object]とか[Entity]とか[Query]とかの普通名詞がいろいろ組み合わさって別の概念になっている。[ObjectEntity]とか[ObjectEnties]とか[ObjectEntry]とか[ObjectStateEntry]とか。何が何だか混乱します。

先にも書きましたが、現在もっともしっかり書かれた解説書は、Julia Lermanの[Programming Entity Framework] で、結局最後はこの本の解説を読むことになりますが、この本はEFの基本的枠組みの構成なりコンセプトなりのしっかりした解説をしないままに、多くの紙面を費やして様々な技術を解説していきますので、読むほうは「何がどうなっているのだ」としんどいばかりです。
来年になれば、もっといい教科書がでてくるのでしょうが、今はこの状態で進んでいくしかないのでしょう。

このブログでもEFをもう少し勉強して内容のあるお話を書ければと思います。