読者です 読者をやめる 読者になる 読者になる

ぽにょろん

思いついたこととメモ

C#でFirebirdのストアドを書く

本エントリは、Firebird AdventCalender 2016 の14日目です。

今回は、Firebirdの.NET ProviderのコミッターであるJiri Cincura氏がブログで公開している、FbNetExternalEngineについて書きます。

目次

FbNetExternalEngineってなに?

簡単に言うと、C#Firebirdのストアドがかけるようなpluginです。

Firebird3では新しいplugin機構が追加され、ストアドや関数、トリガー等がFirebirdの内部エンジン以外で動作できるようになりました。
基本的にはC++でpluginを書くことが想定されているようです。
そんな中Jiri Cincura氏が、C#用のplugin機構を仮実装したのが「FbNetExternalEngine」です。
ちなみに、procedureだけの対応です。
もっともお試し実装なので、Productionでは使えません。

Early (very) preview of stored procedures (functions, triggers) in .NET in Firebird | Jiří {x2} Činčura

簡単な例

実際にどんな風になるかというと。
こんなC#の関数を用意して、

public static IEnumerator<Tuple<string>> HellowWorld(string name)
{
    yield return new Tuple<string>($"Hello World!!, Hello {name}!!");
}

SQL投げると、書いた処理が返ってくる。

f:id:kowill:20161211015954p:plain

実装

Firebird Embeddedで実際に試してみた。

検証環境

  • C# (.NET 4.6.1)
  • Firebird .NET Data Provider 5.6.0
  • Firebird 3.0.0 (Embedded)
    Firebirdのバージョンが古いですが、手元にあったのでそのままw

Pluginの作成

ポイントは以下の通り。

  • static クラス
  • static メソッド
  • 戻り値はIEnumerator
    IEnumerableではありません!私はよく間違えて動かない (゚A゚;)ってなってます。
  • 引数は必ず用意する
    引数無しだと上手く動かせなかった...
  • 引数/戻り値がPrimitive&Value Typeの場合死ぬので、Nullableにする

こう見ると、割と制限が多い。

サンプル

namespace FbExternalSample
{
    public static class Procedures
    {
        public static IEnumerator<Tuple<string>> HellowWorld(string name)
        {
            yield return new Tuple<string>($"Hello World!!, Hello {name}!!");
        }

        public static IEnumerator<Tuple<int?>> GetNumbers(int? count)
        {
            return Enumerable.Range(0, count.Value).Select(x => Tuple.Create((int?)x)).GetEnumerator();
        }

        public static IEnumerator<Tuple<int?, string>> GetDemo(string txt)
        {
            yield return new Tuple<int?, string>(1, "sample1");
            yield return new Tuple<int?, string>(2, "sample2");
            yield return new Tuple<int?, string>(3, $"{txt}");
            yield return new Tuple<int?, string>(4, $"Done!!");
        }
    }
}

Pluginを呼ぶほう

まずは普通にEmbeddedが動く環境整備

適当に必要なDLLを配置する準備だけしておきます。
f:id:kowill:20161211015957p:plain
よくわからなければ、過去記事を参照。

ponyoth.hateblo.jp

FbNetExternalEngineを配置

下記ブログ記事から、必要なアセンブリを取得します。

External procedures in Firebird in .NET done(-ish) | Jiří {x2} Činčura

ちなみに、7zで圧縮されてます。解凍して、中身をpluginsフォルダに突っ込みます。 f:id:kowill:20161211015959p:plain

plugins.confの作成

Firebirdのplugin設定を変更するために、plugins.confを配置又は作成します。 f:id:kowill:20161211020001p:plain
中身はこんな感じで。

Plugin = FBNETEXTERNALENGINE {
    Module = $(dir_plugins)/FbNetExternalEnginePlugin
    Config = FBNETEXTERNALENGINE_config
}

Config = FBNETEXTERNALENGINE_config {
}

DBにprocedure作成しておく

FirebirdのDBファイルを適当に作成して、そこにprocedureのcreate文を流し込みます。

recreate procedure HellowWorld (in_name varchar(300))  
  returns (out_0 varchar(300))   
  external name 'FbExternalSample!FbExternalSample.Procedures.HellowWorld'  
  engine FbNetExternalEngine;  
recreate procedure GetNumbers (in_count integer)  
  returns (out_0 integer)  
  external name 'FbExternalSample!FbExternalSample.Procedures.GetNumbers'  
  engine FbNetExternalEngine; 
recreate procedure GetDemo (in_txt varchar(300))  
  returns (out_0 integer,out_1 varchar(300))   
  external name 'FbExternalSample!FbExternalSample.Procedures.GetDemo'  
  engine FbNetExternalEngine; 

作成したPluginを配置する

上記で作成した、PluginのDLLもFirebirdアセンブリがあるPluginsフォルダにぶち込みます。
f:id:kowill:20161211021008p:plain

実行

あとは、SQLを実行するだけ。
結構雑なコードですけど、こんな感じで確認すると、

var sqls = new[] { "SELECT * FROM HellowWorld('Taro')",
                   "SELECT * FROM GetNumbers(5)",
                   "SELECT * FROM GetDemo('やったぜ!')" };

using (var con = new FbConnection(builder.ConnectionString))
using (var command = con.CreateCommand())
{
    con.Open();
    foreach (var sql in sqls)
    {
        Console.WriteLine("- SQL -");
        Console.WriteLine(sql);
        Console.WriteLine("- 実行結果 -");

        command.CommandText = sql;
        var reader = command.ExecuteReader();
        while (reader.Read())
        {
            var result = "";
            for (var i = 0; i < reader.FieldCount; i++)
            {
                result += $" {reader[i]}";
            }
            Console.WriteLine(result);
        }
        Console.WriteLine("------------------------------------");
    }
}

こんな結果になります。
f:id:kowill:20161211021715p:plain

最後に

Jiri Cincura氏のブログ読んで貰えればわかりますが、実際はまだまだ試作段階です。
制限も多いし手順も割と面倒ではありますが、まぁ、とりあえずできるよ!という状況です。
とはいえ、どれくらい存在するのかわからない、Firebird + C#erには嬉しいので、色々と試してみてはいかがでしょうか。

記事を書くのに殴り書いたサンプルも置いておきます。
Sample/FbExternalSample at master · kowill/Sample · GitHub