○racleは素人が使うものではない。

Oracleを15年ぶりくらいに使っての開発。
OracleMasterGold(8だけど)の私なら全く問題ありません。

と思ったのも束の間

DB設計をほぼ完了し、ツールからテーブルを自動生成。
続けてVisualStudioでプロジェクト作って、EntityFrameworkとかを使ってDBからデータモデルを自動生成。
さてコーディングでも始めますか。と思ったら

あれ。。

設計時にデータ項目名をキャメルスタイルで付けたのに、全部大文字になってる。。

そういえばOracleはそのままだと大文字小文字の区別がされないのね。。

項目やテーブル名をダブルコーテーションで囲めば区別されるようだけど、
ストアドとか作りにくそうで、いやだな。
ってことでアンダースコア「_」区切りに変えようとも思ったけど、結局O/Rマッパで
作成されるデータクラスの名前が全部大文字の項目になってしまう。

世間一般に出回ってるコーディング規約の類は全部大文字=定数みたいな
感じなので、これも微妙。

結局、Oracle側の項目をダブルコーテーションで囲んでやることにしました。
<めんどくさい>


続いて、テスト用にDBをもう1個作ってテストしようとしたらOracleにつながらない。
Netなんとかアシスタントとか、なんとか.oraとか触ったり、Listener再起動したり
1時間くらい悶絶してとりあえずつながるようにはなったものの原因が全くわからない。
tnspingとかではOKなのにsqlplusからつながらないとか全く意味が分からない。
<意味わからない>

とりあえず開発も一段落して、バックアップでもしようかと思ったけど
昔からあるexp、imp以外になんか同じようなもんが増えとる。
expdpとかimpdpとか?
なんか違いもよくわからんし、結局定義情報を吐き出して、
インポート時にcreateとかInsertとかが実行されるかんじのやつ。
なので、エクスポート→いろいろテスト→元に戻そうとしてインポートすると
既にあるオブジェクトに対してはエラーがでるから、一旦オブジェクトを全部消したり
しないといけないのでめんどくさい。
(無視してもいいけど、精神衛生上よくない)

自分がしらないだけかもしれないが、普通にバックアップしてすぐリストアで
完全に元に戻せる手順ってないのでしょうか?
(コールドバックアップのスクリプト作ればいいでしょうけど)
<イケてない>

文句ついでにSQLDevelopperなんてツールがあるが、遅いし使いにくい。
やっぱりOracleはSqlplusとかで腱鞘炎になるくらいキーをたたき続けないと
ストレスなく使えないみたいですね。
確かに15年前はコマンドラインでバシバシ言わしてた記憶があります。
<もう嫌だ>

というわけで、僕のような素人はSQLServerくらいがお似合いでした。
素人が手を出すと
「めんくさい、意味わからない、イケてない、もう嫌だ」
って状態に陥ります。
ディスリついでに製品名にiとかgとかcとかバズワードぶっこんでくるのもダセェ。


10年たってもいい意味でも悪い意味でもほぼ変わってないOracleでした。

.NETで構造体をメンバ配列の値を含めてコピーしたい。件

VB6からVB.NETへの変更時にちょっとはまったのでメモ。

元の処理の簡略化イメージ
構造体の配列へいろいろ計算した結果をどんどんためていく処理がある。

'構造体定義
    Public Structure ST
        Dim int01 As Integer
        Dim aInt01 As Integer()

        Public Sub New(x As Integer)
            ReDim aInt01(10)
        End Sub

    End Structure

'値のセット
    Dim arraySt1(9) As ST
    Dim iST As ST = New ST(0)


    For i = 0 To 9
        iST.int01 = i
        iST.aInt01(0) = i

        arraySt1(i) = iST

    Next


構造体STの配列arraySt1(n)に構造体X型の変数iSTの値を変えながら単純にコピーしている。
構造体は値型なのでarratSt1(n).int01は問題なくセットされるが、
arraySt1(n).aInt01(0)は最後にセットした9ですべて上書きされていた。

構造体は値型でもメンバが参照型の場合は参照先のアドレスが値として入ってるだけなんですよね。。

解決策を検討(ググって)していると
どんなオブジェクトでもコピーできる汎用のディープコピー処理


なんてのがあったので、ありがたくリスペクトさせいただきました。
今回はVB.NETジェネリック版のみなので以下のように定義

Imports System.Runtime.Serialization.Formatters.Binary
Imports System.Runtime.CompilerServices

Public Class CommonUtil

    Public Shared Function ObjCopy(Of T)(ByVal target As T) As T

        Dim CopyObj As T
        Dim BF As New BinaryFormatter

        Using MemStream As New System.IO.MemoryStream()
            BF.Serialize(MemStream, target)
            MemStream.Position = 0
            CopyObj = CType(BF.Deserialize(MemStream), T)
        End Using

        Return CopyObj

    End Function

End Class

元のソースをちょっと修正して期待通りの動作となりました。

'構造体定義
    <Serializable()> _          '★追加
    Public Structure ST
        Dim int01 As Integer
        Dim aInt01 As Integer()

        Public Sub New(x As Integer)
            ReDim aInt01(10)
        End Sub

    End Structure

値のセット
    Dim arraySt1(9) As ST
    Dim iST As ST = New ST(0)


    For i = 0 To 9
        iST.int01 = i
        iST.aInt01(0) = i

        arraySt1(i) = CommonUtil.ObjCopy(iST) '★変更

    Next


めでたしめでたし。

SQLServerのカーソル利用について

SQLServerとかでストアド使った処理でよくあるカーソル使った処理。

<よくあるカーソル利用時の処理パターン>

-- 0.カーソル取得用変数定義
DECLARE @c_key INT;
DECLARE @c_col1 NVARCHAR(50);
DECLARE @c_col2 INT;
DECLARE @c_col3 DATETIME2;

-- 1.カーソル定義
DECLARE cur1 CURSOR FOR SELECT key,col1,col2,col3 FROM tab1;

-- 2.カーソルOPEN
OPEN cur1;

-- 3.初回カーソルFETCH
FETCH NEXT FROM cur1 INTO @c_key,@c_col1,@c_col2,@c_col3;

-- 4.カーソルループ処理(取得結果なしで終了)
WHILE @@FETCH_STATUS = 0
BEGIN
-- @c_key,@c_col1,@c_col2,@c_col3とか使ってやりたい処理

-- 5.次の結果をフェッチする(内容は3と同じ)
FETCH NEXT FROM cur1 INTO @c_key,@c_col1,@c_col2,@c_col3;
END

-- 6.カーソルを閉じる
CLOSE cur1;
-- 7.解放
DEALLOCATE cur1;


だいたいこんな感じですが
いざ書こうと思ってももすぐ忘れる、書くのめんどくさい、特にフェッチする項目が多いときは超めんどくさい。
変更なんか入った日には。。
さらに今時のO/Rマッパとか糖衣構文とかに慣れ切った体ではほんとにつらい。

個人的に拒否反応が起こるところとしては

「0.カーソル取得用変数定義」
Oracleみたいに%ROWTYPEが使えればいいが、自分で定義しないといけない。
→いやいやレベル20%

「2.カーソルOPEN、3.初回カーソルFETCH」
読み込むのに手続きが多くてめんどくさい。
→いやいやレベル30%

「4.カーソルループ処理」
ここの「@FETCH_STATUS=0」っていうの毎回忘れるので調べるのめんどくさい。
→いやいやレベル35%

「5.次の結果をフェッチする」
3の初回カーソルと同じ内容をまた書かなければならない。いやすぎる。
→いやいやレベル55%

「6.カーソルを閉じる、7.解放」
まあ開いたら閉じるのが世の常なのでしょうがないケド。。開かなきゃ閉じなくてもいいのに。。
→いやいやレベル60%

ここまでは我慢の限界だとして、いざ変更が入ってカーソル取得項目が増えようもんなら
0の定義追加
1のカーソル定義変更
3のフェッチ項目追加
5のフェッチ項目追加
をやらなければならない
→いやいやレベル80%



自分がよく使うパターン
無限ループしないように気を付けるくらい。
(速度がどうのとか、リソースがこうのとか全く考えてません)

-- 1.処理用テーブル定義(カーソル定義に相当)
SELECT col1,col2,col3 INTO #w_tab1 FROM tab1;

-- 2.ワークテーブルなくなるまで順番に処理
WHILE EXISTS(SELECT * FROM #w_tab1)
BEGIN
DECLARE @c_key INT;
SET @c_key=select TOP 1 key from #w_tab1;

--処理で使いたい項目は上記と同様に#w_tab1から取得

-- 3.処理済データは削除(ここはWHILEの条件と合わせて更新でもなんでもよい)
DELETE FROM #w_tab1 WHERE key=@c_key;
END



ああ、すっきり。(自己満足)

ソースコードの翻訳

ひと昔前はソースの翻訳っていうと=「コンパイラを使って実行環境向けのコードを生成する」ってことだと思います。

しかし、現代社会においてプログラム言語が氾濫しまっくっており、
前にxxプロジェクトで作ったライブラリを△△プロジェクトで使おうかなってときに
言語が違うなんてこともよくあります。
そこで「ソース」→「ソース」の翻訳が必要になってきます。

現時点でありがちなケースはVB.NETC#の相互変換です。
SharpDevelopというフリーのIDEにはコード変換するコンバーターが付属しており、
ちょっとしたコード変換なら容易に利用できます。

手順としては
1)SharpDevelopを起動し
2)変換元のソースをテキストベースでコピペ
3)ツールの「コードの変換」で好きな言語を選択
これで完成です。

プロジェクト丸ごとってケースはあまりないかと思いますが、このクラスだけってときはとても重宝します。

ログ出力はlog4netで

一昔前は

#if DEBUG
    Console.WriteLine("なんたらかんたら");
#endif

みたいにしてデバッグ用のログをはいてたりしましたが、
最近はみんな.NET開発でログ出力=log4net利用は当然の選択肢となっております。

log4net利用で一番の利点は後から出力レベル、出力先を簡単に制御できることです。
開発時は全てのメッセージをテキストへ出力
運用時はinfo,warnningレベルはテキストへ、Error以上はイベントログへ
など柔軟に対応ができます。

あとは5つある出力レベルをルールを決めて使い分けられれば
開発時のみならず運用・保守時に非常に有効に活用できます。

出力レベル(メソッド)は以下5つあります。
メソッドの名前そのままなのであえてルールとかいらないかもしれませんが、
チーム開発する場合など、折角ある機能をムダにしないように周知すべきかと
思います。
デバッグのことだけ考えてなんでもdebugで出力されると運用時に困ったりします。


debug:デバッグ用なので開発用のメッセージのみを含み、運用時は有事の際以外は出力しない。
--これ以降は運用時に出力する想定でムダなものや間違っても顧客情報、パスワードなど出力しない--
info:開始、終了、処理件数みたいな正常時に出力する最低限の内容のみを出力。テキストでサイズ上限つけて出力するくらい?多分誰もみない。
warn:これが一番悩む。使わないって選択肢もある。
error:このへんからはイベントログへ出力してもいいかも。zabbixみたいな外部の監視ソフトで監視するケースもありうる。
fatal:システム運用上致命的なものはこれ。機能によっては担当者へメール通知レベル。ってすると開発者はだれも使わないかも。




以下はよく忘れる自分への備忘のためのメモ

                                                                  • -

log4net.dllを参照設定する
・利用するためのおまじない(とりあえずclassの先頭あたりにいれとこう)

 private static readonly log4net.ILog log4 = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

・出力したい場所で

 log4.debug("エラーだよ");

・AssemblyInfo.csに以下追記

 [assembly: log4net.Config.XmlConfigurator(Watch=true)]

・app.config内で出力先とかの設定(おこのみで変えましょう)

  <configSections>
    <section name="log4net" type=" log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
  </configSections>
  <log4net>
    <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender,log4net">
      <param name="File" value="App_Data/log4net/logfile.txt"/>
      <param name="AppendToFile" value="true"/>
      <param name="MaxSizeRollBackups" value="100"/>
      <param name="MaximumFileSize" value="1024000"/>
      <param name="RollingStyle" value="Size"/>
      <param name="StaticLogFileName" value="true"/>
      <Encoding value="UTF-8"/>
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date %-5level %logger %message%newline" />
      </layout>
    </appender>
    <root>
      <level value="ALL" />
      <appender-ref ref="RollingLogFileAppender" />
    </root>
  </log4net>
                                                                  • -

SQLServerでSPLIT関数

1つの文字列に対して任意の区切り文字で分割し、テーブル型で返すテーブルファンクションです。

CREATE FUNCTION [CF_TAB_SPLIT_DELIMITER]
(
@nvcInputData NVARCHAR(MAX)
, @nvcDlm NVARCHAR(3)
)
RETURNS @RESULT_TAB TABLE (INDEX_NO INT, VALUE VARCHAR(MAX))
BEGIN
DECLARE @nvcWork NVARCHAR(MAX) = '';
DECLARE @DlmLen INT = LEN(@nvcDlm);
DECLARE @Query NVARCHAR(MAX);

WITH Split(strPos,endPos)
AS(
SELECT 0 AS strPos, CHARINDEX(@nvcDlm,@nvcInputData) AS endPos
UNION ALL
SELECT CAST(endPos AS INT)+@DlmLen AS strPos, CHARINDEX(@nvcDlm,@nvcInputData,endPos+@DlmLen) AS endPos
FROM Split
WHERE endPos > 0
)

INSERT INTO @RESULT_TAB
SELECT ROW_NUMBER() OVER (ORDER BY strPos)
, SUBSTRING(@nvcInputData,strPos,(CASE endPos WHEN 0 THEN LEN(@nvcInputData)+1 ELSE endPos END)-strPos) 
FROM Split
OPTION (MAXRECURSION 100) --MAX100まで

RETURN 
END

使用例

select * from dbo.CF_TAB_SPLIT_DELIMITER('aaa@bbb@@@ccc@ddd@@eee','@@');

結果

INDEX_NO VALUE
1 aaa@bbb
2 @ccc@ddd
3 eee

こんな感じ。

参考(っていうかほぼパクリです):
Split a string to a table using T-SQL | Ole Michelsen

EntityFrameworkのモデルファースト利用について

既存システムに対して新たなサービスを構築する際にはモデルファーストでEntityFrameworkを利用することになる。

面倒なSQL文字列組立やマッピングの為のコードから解放されるが既存のモデルから単純に取り込むといろいろ弊害が起きる。

・エンティティがモデル上に反映されない

 レコード識別のためのキーを自動的に判別するが、

  複合キー

  View

 などEntityFramework的に認識できず、無視される場合がある。

 

・認識されたとしても、いざ更新しようとしても例外が発生して更新できない

 PKが認識できない場合、読み取り専用扱いとなり更新処理で例外発生となる。

 VisualStudio上のソリューションエクスプローラーでedmxファイルを右クリックしXMLエディターで見てみると

 

<!--生成中に見つかったエラー:
警告 6002: テーブル/ビュー 'XXXXXX' には主キーが定義されていません。主キーは推論され、定義は読み取り専用のテーブル/ビューとして作成されました。-->

 

  などと書かれている。。。

プライマリキーを設定し忘れたみたいです。。

 

ちなみに今時のWebフレームワークはサロゲートキーが前提になってるみたいですね。

複合主キーとかあまり気にせず実務で使ってましたが、これからは何も考えず「ID」って主キーをつけることにします。

ナチュラルキーにユニーク制約つけとけばいいかな?

って思ったけど、ER図をリバース生成して設計書完成!ってやりたいときにIDだらけでわけわからん感じになりそう。。