絵文字プログラム言語 chu 構想はじめ

実は構想だけは10年前ぐらいからあるのだけど(言語名自体は一発ネタだし)、今朝方、円城塔「文字渦」を読んで絵文字≒表意文字1文字という縛りでこだわってみてもいいのでは?と思って思考実験してみる。

もともとの発想として関数言語を絵文字であらわすというのある。

let x = 10 ;

これを

😇🍎👈10

のようにする。

print x ;

💋🍎

のように変換する。

if x = 10 then "ok" else "ng"

🤔🍎🤝10🙆ok🙅ng

になる。

単語や数字は面倒なので、英数字をそのまま使って、文法はすべて絵文字の1文字であらわす。1文字しかないので、単語の区切りとかで半角スペースはいらない。print あるいは console.log が💋なのは、chu 言語だから。

プログラム言語の意味論を考え直す

絵文字1文字縛りにするのは、構文解析が楽というのもあるけど、制御文なので使われる「if」や「for」などの単語は、ひとつの単語がひとつの意味を成している(この場合は何らかの制御をするという意味で)と考えられる。例外としては、for … in のように2つの単語でひとつの制御を示すこともあるが「繰り返し処理」という制御の拡張(inが補助的なもの)と考えることもできる。

となれば、ひとつの制御そのものは、ひとつの単語すなわちひとつの表意文字=絵文字に対応させることが可能で、いわゆる予約語といわれるものは、すべてひとつの絵文字に置き換えられるのではないか?というのがある。

さらに、変数とは何を指している(特に変更できない immutable な変数)というかというと、文字列や数字、長い文章などのひとつのまとまりを、ひとつの単語(変数)としてあらわしていることになる。これは、構文解析の制限にもよるのだろうが、変数はひとつの単語としてあらわすようになっている。例えば「hello」という変数は作れるが「hello world」という空白を含んだ変数を作ることはできず「hello_world」のようにアンダーバーをつけることでひとつの単語にする。例外としては「’hello world’」のようにシングルクォートなどを使いひとまとまりにすることも可能ではあるのだが、これはシングルクォートに囲まれた範囲が、ひとつの単語に相当する、と考えられる。

また、ラムダ式を変数に保存するとき(C# だけどこんな感じ)

let func = () => {} ;

これは func という変数に、ラムダ式を割り当てている。つまりラムダ式(あるいは関数、あるいは一連の処理)をひとつの単語に集約されているといえる。たとえば

let greet = () => { return "Good morning" }

このようにすれば、greet() によって、Good morning という文字列が返される、あるいは一連の処理を記述できる。これは、一連の処理を関数名に集約している「構造化」という仕組みになる。そう、プログラムの構造化というものは、複数の処理をひとつの単語に集約させるという意味論がある。

さらに拡張すれば、オブジェクト指向のクラスやインスタンス(オブジェクト)も、複数の変数や処理群をひとつの単語にまとめるという意味論がある。

となれば、あらゆるプログラムは、無限に絵文字があるとすれば、ひとつの単語に集約できるのではないか?という思考実験が可能になる。まあ、実際、ターミナルの「コマンド」がそれを担っているわけで、できるわけですが。絵文字を無限に作ることは可能だけど、無限の絵文字を覚えることは不可能なので、「実現可能ではありますが現実では不可能ですね」というやつです。

シェークスピアの「ハムレット」の長い文章は「ハムレット」というタイトルを表す、ひとつの絵文字に集約できるだろう。

ということです :)

カテゴリー: 開発, chu | 絵文字プログラム言語 chu 構想はじめ はコメントを受け付けていません

新人教育のための Good Code, Bad code のメモ書き

一読してみて、これならば新人研修に使えるかもしれないと思いつつ、同時に研修に使うときには「元ネタ」を明確にしないとミスリードっぽくなるだろう。と思われたので、そのメモ書き。

参考文献のほうに

  • リファクタリング
  • デザインパターン
  • Unit Test

が書いてあるので、XP のプラクティスに合わせてもいいと思われる。

コードの品質とは何か?

日本語でいうとこの「品質とは」に関係してくるので、「高品質なコード」と「低品質なコード」の比較や、「1.2 コードの品質にゴール」に書かれている4つの箇条書きのように漠然としてくると、新人(特にコードを書く前の新人)にはちょっと困った自体に陥るんじゃないかな、と思う。

ここでいう「正しく動くコード」というのは、動くべき仕様書に対応するコード、という隠れた意味がある。細かく言えば、動くべきことを記述した「仕様書」と、実際に顧客が想定している「動作」とは乖離があるわけで(あの例の顧客が求めていたものの図)漠然と、「正しく動くコード」あるいは「正しく動作し続けるコード」が先にでてくると、混乱するのではないか?

たとえば、建築で言えば、「正しく動くコード」というのは、家の屋根に相当する。

雨が降っても部屋が濡れないように屋根を付ける、という要望を出したとしよう。屋根があればいいので、実は柱が1本でもいい。傘のようなものでもいい。雨の量はここでは問わないので小雨をしのげるだけでもいいかもしれないし、台風に耐えるような屋根にする必要もあるかもしれない。

だから「雨に濡れないように屋根をつける」ようにコードを書くこともできる。エラー処理をつけないようなコードや、例外を受け付けないようなコード。ある程度の範囲は許容するけど、極値では落ちてしまうかもしれない。でも99%のデータは、中央値にまとまっているから、たいていの場合は「正しく動くコード」として扱うことができる。たまに、極値が来ると正しく動かないコードに陥ってしまう。

だから「正しく動く」の部分は範囲が広い。もう少し修飾するならば、「おおむね正しく意図的に動くコード」というべきだろうか。

「おおむね」というのは、画面からの入力はおおむね大丈夫な感じとか、マイナンバーカードを入れたときに氏名に外字が使われていない限りは大丈夫とか、半角カナが入ってない場合は大丈夫とか、そんな感じで、「おおむね」正しく動いている状態。

プログラマから言えば、この「おおむね」正しく動いている状態というのは、到底「正しく動く」の範疇に入らないように見えるのだけど、少なくとも、想定している数字やひらがなを入れたときにデータベースにきちんと保存されているね、ぐらいの気持ち、いわゆる「正常系」がきっちりと動いているのを「正しく動くコード」と緩い形で決めてしまってもいい気がする。

でも、プログラムというものは正常系だけで済むものではない。コードのほとんどは異常系や例外をうまく処理するためのものが多い。その部分が「正しく動き続けるコード」にあたる、と思われる。

正常系でうごいていても、何かの異常系で動かないことがある。これは正しいコードと言えるか問えわれれば、言える。だって、正常系が動いていて、顧客の要望もおおむね動いているわけだから、入力するときにちょっと注意を払えばだいたい正常に動くわけだから「正しく動くコード」といってもいいだろう。しかし、「正しく動き続けるコード」とは言えない。

そう、雨をしのぐための傘のような屋根を付けて、正しく雨がしのげる住宅、と言っていいようなきもするし駄目のような気もするのだが、少なくとも「雨をしのぎ続ける」ことはできない。そういう意味では、ちょっとした小雨のような雨ならばしのげる正常系かもしないがい、横から吹き込むような雨には無力な屋根だ。少なくとも壁をつけないと雨をしのぐという住宅の意味をなさない。これが「正しく雨をしのぎつづける」住宅と言えるだろう。

だから「正しく動くコード」でキーポイントになるのは、顧客の要求を正常に処理ができるか、緩い基準になる。コードという視点から言うと発散してしまいそうだが、仕様書とか要件定義という意味においては、まず第一に正常系の要求をきちんと処理できること、ひとまず例外などを除いた機能要件が満たされることが正しく動くコードというよりも「正しく動くソフトウェア」あたると思う。

ただし、このままだと品質工学的な意味での「品質」に達しない。低品質ということになる。

では、正しく動き続けるコードあるいはソフトウェアは何に気を付ければいいのか?ということになる。少なくとも、ちょっとした例外処理(外字を使うとか、半角カナとか)は対処しないと品質が高いとは言えないだろう。それは、ある程度の例外を許容して正常に動くというものがある。「ある程度の例外」というのがキーポイントになると思われる。

カテゴリー: 開発 | 新人教育のための Good Code, Bad code のメモ書き はコメントを受け付けていません

EntityFramework Core 7.0 を使ったときにサーバー証明書エラーが発生する場合の対処

.NET 6 で利用していた EF Core 6.0 を .NET 7 の EF Core 7.0 にアップデートしたときに「信頼されていない機関によって証明書チェーンが発行されました」のエラーが出てきて SQL Server に接続できないことがあります。これは、開発用に立てたローカルの SQL Server に接続するときに、サーバーの証明書が正しくない(自己証明書を含む)ときに発生するようです。正しくは SQL Server が動いているサーバー証明書をローカルPCにインストールするか、正しいサーバー証明書をいれればいいのですが、本運用ならともかくとして、開発用サーバーに正式な証明書をいれることはありません。

EF Core 6.0 ではサーバー証明書を無視するらしいのですが、EF Core 7.0 できっちりとチェックするようになったための問題のようです。

開発中の場合は、サーバーにある自己証明書(開発機で動いていれば自分自身のPC)をスルーするようにします。

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    base.OnConfiguring(optionsBuilder);
    if (optionsBuilder.IsConfigured == false )
    {
        var builder = new SqlConnectionStringBuilder();
        builder.DataSource = "(local)";
        builder.InitialCatalog = "mvcdb";
        builder.IntegratedSecurity = true;
        builder.TrustServerCertificate= true; // サーバー証明書を常に信頼する
        optionsBuilder.UseSqlServer(builder.ConnectionString);
    }
}

DbContext クラスで、TrustServerCertificate の値を true にしておくと接続できるようになります。

config の場合は「TrustServerCertificate=Yes」を追加しておきます。

参考先

ssl – Entity Framework Core 7 connection certificate trust exception – Stack Overflow https://stackoverflow.com/questions/74467642/entity-framework-core-7-connection-certificate-trust-exception

カテゴリー: 開発 | EntityFramework Core 7.0 を使ったときにサーバー証明書エラーが発生する場合の対処 はコメントを受け付けていません

週40時間勤務と20%ルールでプロジェクト納期を厳守する

まだ会社にいたときに「なぜ、週40時間にこだわるのか?」と何度か上司に聞かれたことがあります。そのころはXP(エクストリーム・プログラミング)が始まったころで、当時はそのプラクティスのひとつとして「週40時間勤務」が重要だったのと、来日したケント・ベック氏に勢いで「ケント・ベック氏自身は勤務時間40時間を守れているのでしょうか?」と質問したときに、「文章書きのときは守れていない。けど、プログラミングをするときには週40時間が必須!」と強調されたから、という理由があったわけですが、実はもうちょっと別な根拠があります。

その後、日本のIT業界は少し不況に入ったので、仕事が減り少しずつ勤務時間も改善されてきたので、週の勤務時間にこだわる必要もなくなったのですが、たまに「炎上プロジェクト」をみるので、勤務時間の話を書き残しておきます。

正確な事前見積もりをあきらめる

大抵のプロジェクトで「炎上プロジェクト」(あるいはデスマーチプロジェクト)となるのは、事前のスケジュールミスが問題です。昨今の炎上プロジェクトはITプロジェクト自体が短期化しているところもあって、短い時間の中に開発を詰め込みすぎ、人数が足りなくて「炎上」ということが多いです。炎上とは言え、プロジェクトは完了します。たまに、大規模プロジェクト(IBMの特許プロジェクトのような)で炎上後にプロジェクトが崩壊するパターンもありますが、数か月から半年以内のWebプロジェクトやシステム開発のようなものは、炎上しつつも、なんとか納品するという(スケジュールは伸びるかもしれませんが)という結末を得ることが多いです。

半年以下のプロジェクトの場合、週で言えば、24週間になります。契約がうまく移動できるスクラムや顧客を含めたチケット駆動で行っている場合は、金額やスケジュールの調節でうまくできるかもしれませんが、たいていのプロジェクトでは、事前に金額やスケジュール(納品日)が決まっていることが多いです。

納品日がずれると、検収日がずれるので会社としては集金日がずれます。と同時に、伸びた分だけ社員に給与を払うことになります(派遣ならば、派遣先に延長を申し込むことになる)。この増加分や遅延分を顧客が支払ってくればよいのですが、そうもいきません。

その昔、ファンクションポイント法(FP法)などで見積もりの正確さを求めいた時期があるのですが、最近のアジャイル開発を含めて計画駆動でさえ、正確な事前の見積もりはうまくいきません。本来ならば概要設計まで行って機能別に見積もりをしたいところですが、事前の要件定義すらぐらぐらなところがあるので、そのぐらぐらの事前条件に使って概要設計をして精密な見積もりを出しても意味はありません。

アジャイル開発においては正確な事前見積もりを清く諦めます。諦めて、プロジェクトが進む中で機能の増減やスケジュールの遅延などを調節していきます。が、果たして、日本のIT業界の現状としてはそれはできないことが多いのです。

概算であれ事前の見積もりが求められ、ざっくりとした見積金額がそのまま正式な金額になりがちで、さらにサービスリリース日は顧客の営業の都合で決められることが多く、開発側の都合だけで金額も開発スケジュールも決められるものではありません。

プロジェクト予算とスケジュールを固定化する

以前にも何度か話していますが、プロジェクトの予算については二重帳簿方式を使います。二重帳簿というと聞こえは悪いですが、まあ、仕方がないです。

  • 顧客に提示するプロジェクト予算
  • 社内で開発するときのプロジェクト予算と実績

顧客や営業先に用意する「プロジェクト予算」は、見積もりを提示した突端に固定化されます。割引交渉で減額されることはあっても、増額されることはまずありえません。機能はそのままで、割引を要求されることも普通にあります。これは、営業の値段交渉のうちなので、実装するときの機能とは切り離しておくとよいです。

つまり、切り離したところに、実績としてのプロジェクトの内部予算があります。実際に社員や派遣さんなどが働いた工数を積んで、具体的に払う金額つまりプロジェクトで利用した金額を積み重ねていきます。

同時に、顧客に提示するスケジュールも提示した途端の固定化されます。納品日あるいはリリース日が決定されると、プロジェクト開発において前に倒すことはあっても後ろに倒れることはあり得ません。ごめんなさいするしかない場合は、後ろに倒れますが。

ここで「プロジェクトバッファ」を取って、プロジェクトのスケジュールを立てたいところですが、もう少し泥臭い方法があります。プロジェクトのバッファの方式は、バッファ自体を食いつぶさないの注意しないといけません。これは、チケット駆動やスクラムとの併用も可能(PMBOKにも指針があります)なのですが、常に「学生症候群」(夏休みの宿題を後ろに残すあれ)を気にかけてチームマネジメントをしないといけないので、途中で機能が増減する場合は、なかなかハンドリングが大変です。

プロジェクト計画時に月160hで計算する

小規模のプロジェクト(5,6名程度)で期間が半年以内のプロジェクトに限定するものですが、プロジェクトメンバの勤務時間を柔軟にすること(残業も含めてという意味で)を前提にして、プロジェクトのスケジュールを立てます。

このとき、週40時間、月160時間で見積もりをします。稼働日数を22日にしたり、忙しい月だからといって稼働時間自体を月200時間で計算してはいけません。あくまで、週40時間、月160時間で統一的に計算してしまうのがミソです。

プロジェクトバッファ(「保険」とも言う)の仕組みは簡単です。

  • 通常は、週5日で8時間勤務で行う
  • 少し忙しいときは、週5日で10時間勤務(2時間残業)で行う
  • 炎上間近のときは、週6日で10時間勤務、土曜出勤日曜日は休みで行う

こうすうことによって、週40時間が、少し忙しめのときは2割増し、炎上ぎりぎりのときは1.5倍のときの開発時間を確保できます。休日を外さないのは、継続的に1か月以上働けるのはこれが限界だからです。その後に燃え尽きてしまいますからね。

このように、月160時間でプロジェクトの期間見積をしておくと、機能追加や見込み違いなどでも同じメンバーで1.5倍までは開発期間を確保できます。

逆に言えば、1.5倍を超える場合は、人員を確保しないと駄目≒予算が増える、ということです。本来ならば残業代が増えるのですが、それは会社によりけりなので。

また、あらかじめ Google の 20% ルールを採用しておいて、繁忙期には20%ルールを外してもよいというルールにしておきます。フリーランスでだらだらとやっている身ですが、本の執筆や開発が混ざったときはこのパターンを採用していますね。お試しあれ。

カテゴリー: 開発 | 週40時間勤務と20%ルールでプロジェクト納期を厳守する はコメントを受け付けていません

計画駆動とSHIROBAKOの進行係の関係

SHIROBAKO http://shirobako-anime.com/ は、アニメ制作会社のアニメな訳ですが、主人公の宮森あおいはアニメーションを描けません。テレビアニメーションを作るのに、絵を描くアニメーターの存在は不可避なわけですが(キャラデザとか作監とか監督とか企画とかも)、このなかで制作会社として「進行」係に焦点を当てたアニメ作品です。

以前、ブログで書いた後に http://www.moonmile.net/blog/archives/10049 に続きを書こうと思ったら、京都アニメーションに放火事件があって、その後を続ける気分にならなかったのですが、ちょっと思ったことを書き下しておきます。

by SHIROBAKO

アニメーション制作と計画駆動の類似点

SHIROBAKO はアニメ制作会社の話なので、ある意味でソフトウェア開発の計画駆動(ウォーターフォール開発)に近いところがあります。いわば、

  • 最終締め切り(納品)が明確に決められていて、動かせない
  • プロジェクト進行の手順がほぼ決定されていて、前後しない
  • 前のタスクが遅れると後ろのタスクに影響がでる

というところです。一般的なアジャイル開発では、予算の変更やリリーススケジュールの変更が可能であるのですが(ここができないと、あまり「アジャイル開発」とは言えません)、従来型の計画駆動の場合はリリース日が決まっていたり、途中のマイルストーンが決まっていたりします。例えば、アプリのリリース日がプレスリリースされている場合は、その納品日を動かすことはまずできません。システム運用の切り替えや、引継ぎなどの関係、法律の関係など、納品日が絶対であるソフトウェア開発は結構あります。
この場合、余裕のある納品日と予算とスケジュールを立てればよいのですが、ソフトウェア開発において不可避な状態に陥ると、この「納品日」を守るが、かなり大変になりいわばデスマーチ化します。

ソフトウェア開発において、設計→製造(コーディング)→試験の順番は、XPやスクラムなどのアジャイル開発、イテレーション開発などによって一定ではないことが多くなってきました。しかし、多数の会社が参加していたり、ハードウェアとソフトウェアの両輪が回っていたりすると、設計→製造の順番が変えられないことが多くあります。この場合、従来型の計画駆動を踏襲して、概要設計や外部設計を重視したりします。

SHIROBAKOのアニメーション制作においても監督のシナリオ作りからラフ絵、作監、動画、撮影などの順番は変わりません。さすがにこれを前後することはできません。この順番をうまく回していくために、アニメ制作会社ではマネジメント業務として「進行」係が割り当てられます。昔のアニメーションを見ると「進行」の担当者が必ずいます。

進行係が何をするかと言うと、上司的な「マネージャー」役をやるわけではありません。SHIROBAKOを見るとわかるのですが、主に雑用係っぽいところが多いです。しかし、制作会社の立場から監督、作監、動画などのフリーランスあるいは外注を含めて、とりまとめをしていく重要な役割です。ある意味で、各フリーランスや外注の会社がきちんと〆切を守るのであれば、進行係の仕事は必要なく、非常に暇ですよね。でも、そんなことはありえません。確率的にゼロに等しいのです。なので、不確定要素を含みつつ、うまくアニメーションが出来る上がるまで(放映日まで)なんとかするのが「進行係」の重要な仕事です。

ソフトウェア開発においても、マネージメントという業務があり、権限の強いマネージャーという組織化をすることもあるのですが、果たしてそれがうまく廻るかどうかはわかりません。従来のウォーターフォール開発からアジャイル開発に移行しつつあるように、単なる工場的な上司部下的な分け方でソフトウェア開発ができなくなっています。そういう場所でマネージャーの行う「マネジメント」とい業務は何をするべきなのか?という疑問がある常々おこるのですが、これの答えのひとつが「進行係」です。

進行係はプレイングマネージャーではない

SHIROBAKO の主人公宮森あおいは、絵が描けません。高校時代のアニメ部?の仲間はアニメーターや声優になるわけですが、宮森だけは直接アニメーションを作る作業ができるという訳ではないのです。しかし、本作品で重要なのは、実際にアニメーションを作る(ソフトウェア開発ではコーディングをする、設計をするなど)訳ではなく、全くそれ以外の場所でもアニメ制作には重要である、という処に焦点があります。

ソフトウェア開発における「プレイングマネージャー」という役割は、アジャイル開発スクラムのリーダーやスクラムマスターっぽい位置に属します。開発グループにおいて、ある程度コードが書ける=尊敬に値するというカリスマ性は一種重要なものを含んでいるのですが、これがプロジェクト成功において必須条件という訳ではありません。プロジェクト成功の基準を

  • スケジュール通りに納品すること
  • 予算内で終わらせること
  • プロジェクトメンバが脱落しないこと(退職など)

のように、ヒト・モノ・カネの軸で言えば、プロジェクトマネージャのカリスマ性はあまり必要ではないことがわかります。たまに、三番目を外して「プロジェクトを成功」させるマネージャが多いのですが、会社としては継続し得ないプロジェクトメンバ=社員を作ってしまうことは大きな損失なので、プロジェクトが成功したとは言えないでしょう。

ゆえに、ソフトウェア開発におけるマネジメント業務を「進行」という雑用っぽいところまでに落とし込んでいくと、プレイング=コード書ける、必要がないことがわかります。また、ドラッガーがいう処のマネジメント=あれこれ調節する役割も必要ありません。

進行係においては、進行係自身には権限がないのですが、それぞれの権限のある人(監督や作監、動画など)のパフォーマンスが十分に保たれるように動きます。ソフトウェア開発で言えば、プログラマ、営業、テスター、システムエンジニアのように別々の特殊技能を持っている人のパフォーマンスをあげるように調節する役柄です。これを「マネジメント」と言うのですが、一般的な「マネージャー」と区別するために「進行係」という名称を流用します。

進行係は何に注力するのか?

計画駆動における進行係の存在は、現在のところ(といはいえ既に思いついてから20年以上たっているけど)目立った形で見えるわけではありません。本来ならば、マネジメント業務のひとつとして「進行」の役割を演じればいいのですが、

  • 顧客交渉の強い立場としてのマネジメント
  • 進行を円滑するするための下支えのマネジメント

とが、なかなか共有できるとは思えません。とは言え、私の個人的な経験では2名ほど上記の2つが同時実行できるマネージャを知ってはいるのですが、どうも個人的な素質の部分が大きく、体系化できそうにないです。

なので、計画駆動においては、強い立場のマネジメントを従来型のマネージャーが担い、プロジェクト進行を滞りなく進めるマネジメントを進行係として別に割りあてるのがよいでしょう。
とは、いえ、SHIROBAKO のような大所帯(アニメーション制作には100人以上に人がかかわっています)とは違い、昨今のソフトウェア開発のような小規模(5人程度)のプロジェクトにおいて「進行係」を別に立てることができるかといえば、そうはならないでしょう。30人規模のプロジェクトならば、進行係がいたほうがいいかなと思うのですが、現実問題として、独立したひとに「進行」を任せるのは小規模の開発プロジェクトでは無理です。なので、アジャイル開発のように混在した形になりがちです。

締め切り厳守するための進行の役割

となると、ソフトウェア開発において「進行」の役割は、単一の人に割り当てるのは無理そうです。適用範囲が狭すぎるので、あまり現実的ではありません。

ただし、先に書いたようにアジャイル開発が締め切りを動かすことを前提としているように(場合によっては、スクラム開発で強引に締め切りを守る方法もありますが、そこは「機能」を調節しますよね)、計画駆動のように締め切りを前提とした開発スタイルにもメリットは大きいのです。

アジャイル開発において、顧客の要求の変動により仕事量が上下することを前提としていますが、ソフトウェア開発においてはある程度までこの変動を抑えられることがあります。いわば、商品開発的な受託開発や提案型の受託開発です。この場合は、アジャイル開発が使われることが多いのですが(それを売りにする場合もあるし)、敢えて変動要素を少なくするようにすれば、進行的な役割を据えて無理なく締め切りを迎えることが可能になるでしょう。

そこは予測、と変動を許容するプロジェクトバッファという形になるので、これはアジャイル開発自身にも使われているところです。

  • バーンダウンチャートとアーンドバリューの組みあわせ
  • 工事進行基準の応用
  • チケット駆動におけるトータルチケットの予測値

古くは CMMI のレベル4と5の中間あたりですね。CMMI 自体が消え去ってしまっていますが、参考にできるところは多いです。あと、稲森会長の「アメーバ経営」を参考に。

参考

アニメ制作について|TVアニメ「SHIROBAKO」公式サイト http://shirobako-anime.com/about.html

カテゴリー: 開発 | 計画駆動とSHIROBAKOの進行係の関係 はコメントを受け付けていません

.NET MAUI から Firebase を利用する

.NET MAUI が Visual Studio 2022 の正式版にアップデートされたので、適度にツールを作ってみます。のテストです。ちょっと、手元で入用な Firebase 接続確認のアプリを作る必要があってので、.NET MAUI を使ってアプリを作っていきます。

と、結構すんなりいく筈だったのですが、意外と難関だったので、備忘録として残しておきます。

Visual Studio 2022 で .NET MAUI プロジェクトを作る

ツールの対象として iPhone アプリになるので、iOS のみを対象としています。

.NET MAUI は Xamarin.Forms とは異なり、

  • コードが .NET 6 ベースになる(内部的には mono だけど、NuGet のライブラリが .net 6 で揃えられる)
  • 各プラットフォームは #if で切り替える
  • 各プラットフォームはひとつのプロジェクトになる

となります。

ちょっと、ややこしいのは各プラットフォーム(iOS, Android、MacCtalyst、UWP)のコードがひとまとまりになって、ビルドをするたびに4つのプラットフォームを全部ビルドします。なので、

  • ビルドに少々時間が掛かる
  • 別プラットフォームでビルドできないコードを含めてしまうと、エラーがうっとおしい。

という状態に陥ります。

たとえば、モバイルアプリの場合は、iOS用とAndroid用しかいらないわけで Windows(UWP)のほうビルドエラーがでると面倒なことになる、のですが、どうするのかは微妙なところです。多分、*.csproj で TargetFrameworks を絞ればいいと思うのですが。

ひとまず、ビルドして実機で動かす

実は、「逆引き大全2022」と「.NET6本」の .NET MAUI の章は Visual Studio 2022 のプレビュー版を使っています。概ね今回の正式版と変わらないのですが、プレビュー版ではiOS シミュレータの動作が不安定であったり、iPhone 実機で動作できなかったりしました。

なので、さっそく実機で動かしておきます。

画面のほうは、実機ではなくシミュレータのものですが、Visual Studio 2022 からデバッグモードで問題なく動いています。

iOS 版を作るときは、Mac とのペアリング等もろもろの設定が必要のですが、これは Xamarin.Forms のときと同じです。

.NET MAUI と Xamarin.iOS が共存できない?

.NET MAUI プロジェクトを作るときの注意点ですが、Mac に .net 6 ベースの mono(かな?)を入れてしまうらしく、従来の Xamarin.iOS との共存ができません。正確には、

  • Windows 上の Visual Studio から .NET MAUI のプロジェクトを作成し、Mac に接続する
  • Windows 上の Visual Studio から Xamarin.iOS のプロジェクトを作成、Mac に接続する

ことができません。mac 上の mono のバージョンが異なるようで、Xamarin.iOS pうろジェクトが動きません。

ただし、

  • Windows 上の Visual Studio から .NET MAUI のプロジェクトを作成し、Mac に接続する
  • Mac 上の Visual Studio から Xamarin.iOS のプロジェクトを作成し、Mac 上で動かす。

ことは可能です。プロジェクトに設定されている mono のバージョン違いなのかもしれません。

Firebase に接続するための NuGet をインストール

Xamarin.Forms のときにどうだったか忘れてしまったのですが、Xamarin から Firebase に接続するための NuGet パッケージは、Android と iOS では別々のものが提供されています。コマンド自体は HTTP 接続だろうから、共通なのでは?と思ったけど暫くハマりました。

iOS 版の NuGet パッケージは以下になります。

https://github.com/xamarin/GoogleApisForiOSComponents

  • Xamarin.Firebase.iOS.Core
  • Xamarin.Firebase.iOS.CloudFirestore
  • Xamarin.Firebase.iOS.Auth

の3つ入れておけば大丈夫です。

ちなみに Android 版のほうは「Xamarin.Firebase.Common」のように、「iOS」がないものを使うので注意が必要です。これ、なんらかの形でまとめてくれませんかね。Windows/UWPの場合は不明です。。。

サンプルコードは

https://github.com/xamarin/GoogleApisForiOSComponents/tree/main/samples/Firebase/CloudFirestore/CloudFirestoreSample

にあるので、参考にしてください。

Xamarin.Firebase.iOS.CloudFirestore パッケージを入れた後でビルドに失敗する

Xamarin.Firebase.iOS.CloudFirestore パッケージを NuGet で入れると、.nuget フォルダーにパッケージがダウンロードされます。が、これがビルド時に失敗してしまいます。

パッケージには *.h ファイルが含まれてい、なんらかの形で参照をしているのですが、Windows の PATH の制限があって、これがエラーになってしまうのです。

ひとつの解決先は

https://github.com/xamarin/GoogleApisForiOSComponents/issues/555

にある通り、環境変数 NUGET_PACKAGES を設定して「c:\Nugets」のように PATH が短くなるように工夫します。

ですが、これは根本的な解決策にはなりません。おそらく Visual Studio 2022 がビルドに使っているターミナルが cmd ベースなのが問題でしょう。試しに、Powershell を立ち上げて、独自に「dotnet build」とすると無事に長い PATH でエラーになる問題は解決されます。

Xamarin.Firebase.iOS.* パッケージが設定しようといている *.h ファイルは最初の1回だけで、あとはキャッシュが使われるようなので、NuGet パッケージの設置した後に一回だけ「dotnet build」あるいは「dotnet restore」しておくとよいです。

ちなみ、Visual Studio 2022 上でビルドエラーになる現象は、何度か発生します(不定期です)。この場合も、Powershell 上で dotnet build すると直ります。

Firebase に登録するコードを書く

  • Firebase.Core.App.Configure で初期化
  • Document を作成して、SetData で登録

すれば OK です。この部分は、Xamarin.iOS の頃と同じ筈なのですが、Xamarin.iOS と Firebase の組み合わせを使っている人が少なくて、探すのに苦労しました。Android のほうはそれなりにあるので、大丈夫だと思います。

private void MainPage_Loaded(object sender, EventArgs e)
{
#if IOS
		Firebase.Core.App.Configure();
#endif
}
/// <summary>
/// Firebaseに接続
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnClickConnect(object sender, EventArgs e)
{
#if IOS
		message.Text = "登録開始";
        var store = Firebase.CloudFirestore.Firestore.SharedInstance;
		var coll = store.GetCollection("contacts");
		var doc = coll.CreateDocument();
        var dic = new Dictionary<object, object>();
        dic.Add("device_id", "0000");
        dic.Add("name", "test");
		var time = Firebase.CloudFirestore.Timestamp.Create(
			(long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds, 0);
        dic.Add("update_at", time);
        doc.SetData(dic);
		message.Text = "登録しました " + DateTime.Now.ToString();
#endif
}

#if で囲ってあるのは、コードビハイドの MainPage.xaml.cs で記述しているので、iOS しかビルドが通らないためです。この部分は適宜 Android/iOS 共通のクラスを作っておいて、内部で #if で分けるのがベターです。Android と iOS で名前空間が違うので、Xamarin.Forms のときのようにインターフェースを駆使するよりも、#if でビルド時に切り分けてしまったほうが楽です。

時刻は、Firebase 側で Timestamp 型を扱うため、Firebase.CloudFirestore.Timestamp に変換して使います。

GoogleService-Info.plist を配置する

Firebase からダウンロードした「GoogleService-Info.plist」をプロジェクト内に配置させます。

これが苦労して2時間ぐらいかかりました。

GoogleService-Info.plist は、プロジェクトのルート(Plaftforms/iOS の下ではない!)において、手作業で、BundleResource を記述します。

	<ItemGroup>
		<BundleResource Include="GoogleService-Info.plist" Condition="Exists('GoogleService-Info.plist')" />
	</ItemGroup>

Visual Studio 2022 のプロパティでは、「BundleResource」を設定できません。日本語の「埋め込みリソース」は EmbeddedResource に変換されているので、多分 .NET MAUI の *.target あたりのバグじゃないかなと。

Android の場合は「GoogleService-Info.json」なので、拡張子が違ってダブらないのですが、Windows 版はどうだったかな、と。ファイル名がダブル場合は困ることになるので、今後問題になりそうです。

ただし、コードでファイル名を指定したりパラメーターを独自に設定したりする方法があります。

Cloud Firestore のルールを設定しておく

動作させた最初では、Firebase に登録ができなくて、.NET MAUI & Firebase の不具合では?と悩んでいたのですが、ルールがきつい設定になっていました。

ひとまず、動作確認をしたいときは、以下のようにルールを緩く設定しておくと便利です。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /contacts/{id} {
      allow read, write
    }
  }
}

実運用になると、request.auth でユーザー権限を調べて uid のチェックをしたほうがいいですよね。実際のアプリのほうはそうなっています。

実行する

アプリを実行するとこんな感じ。

Firebase のドキュメントに無事データが入っています。

カテゴリー: 開発 | .NET MAUI から Firebase を利用する はコメントを受け付けていません

.NET Framework 4.7 から .NET6 への移行ポイント

Windows 上の .NET Framework から .NET 6 へ移行するときに、障害となりそうなポイントを挙げておきます。ちょっとした Windows フォームや WPF アプリならばすんなり以降できるのですが、それなりの大きさの C/S 方式で Entity Framework を使っていると(使っているんだが)、移行時に躓きがありそうです。

移行対象

  • WPF アプリで約70画面ある。
  • テーブル数は約20テーブルある。
  • MVVMパターンを使い、View と ViewModel を分離させている
  • Model は .NET Framework の Entity Framework を使っている。コードファースト以前の、*.edmx ファイルを使う方式(SQL Server のテーブルからコードを自動生成するので当時は便利だった)
  • テーブル検索はほぼ LINQ を使っているが、移行前の複雑な SQL を使うため SqlQuery で直接呼び出している部分がる。
  • View にバインドさせるため、Entity クラス(*.edmx による自動生成)に、適宜プロパティをはやしている。
  • Excel のコントロールを埋め込むため、WindowsFormsHost を使っている
  • Excel の帳票をつかうため、Microsoft.Office.Interop.Excel を使っている

ステップ数は数えてはいませんが、大体1万行を超える位です。もともと、ACCESS 版を C# 版に書き直したもので、これを試しに .NET6 に移そうと試しています。

全体の構造

.NET Framework の WPF を .NET6 の WPF に移行するので、全体のフォルダー構造は変えません。 中のコード(特にロジック)には手をいれたくないので、namespace はそのまま使うようにします。

Entity Framework の移行

.NET Framework の System.Data.Entity.DbContext を .NET6 の Microsoft.EntityFrameworkCore. DbContext に移行します。同じ「 DbContext 」となっていますが、中身は別物です。.NET6 のほうは、NuGet で “Microsoft.EntityFrameworkCore.SqlServer” をインストールします。

.NET Framework の EF とは違い、*.edmx のようなテーブルの構造を定義したファイルがありません。コードファースト的にクラスを定義しておくか、データベースファースト的に SQL Server から dotnet ef コマンドを使いテーブルクラスを生成させます。テーブルクラス = Entity クラスは、以前の .NET Framework と同じものを使うので(プロパティ名などが変わるとやっかい).NET Framework 版のものをコピーして Models フォルダに入れておきます。

DbSet クラスは、

  • .NET Framework では System.Data.Entity.DbSet
  • .NET6 では Microsoft.EntityFrameworkCore.DbSet

となり同じ「DbSet」という名前になっていますが、似て非なるものです。

SQL Server に接続する文字列は、.NET6の場合(EFCoreの場合)は OnConfiguring を override して記述します。場合によっては、複数の DbContext を切り替える必要があるので、外部から切り替えができるようにしておきます。

public static 会議室Entities CreateEnt(string dbname, string servername = "(local)")
{
    var builder = new SqlConnectionStringBuilder();
    builder.DataSource = servername;
    builder.InitialCatalog = dbname;
    builder.IntegratedSecurity = true;
    var cnstr = builder.ConnectionString;
    var context = new 会議室Entities();
    context.Database.SetConnectionString(cnstr);
    return context;
}

主キーを付加する

.NET Framework の EF が出力する Entity クラスは主キー(Key属性)がありません。このため、この Entity クラス .NET6 の EFCore で使うと実行エラーが発生します。検索時ではあっても、デフォルトでは主キーが必要になっているためです。.NET Framework の場合はでは、*.edmx ファイルに主キーなどのテーブル構造の情報がはいっているため、Entity ファイルには Key属性等がついていません。

public partial class 会社基本情報
{
    public int id { get; set; }
    public string 会社名 { get; set; }
    public string 肩書き { get; set; }
    public string TEL { get; set; }
    public string FAX { get; set; }
    public string 郵便番号 { get; set; }
    public string 住所1 { get; set; }
    public string 住所2 { get; set; }
    public string 期名前 { get; set; }
    public Nullable<double> 消費税 { get; set; }
    public Nullable<int> 端数処理コード { get; set; }
    public Nullable<int> 年 { get; set; }
    public Nullable<System.DateTime> 期首日 { get; set; }
    public Nullable<System.DateTime> 期末日 { get; set; }
    public string 振込先A { get; set; }
    public string 振込先B { get; set; }
    public string FA { get; set; }
    public string FB { get; set; }
    public string GA { get; set; }
    public string GB { get; set; }
    public string HA { get; set; }
    public string HB { get; set; }
}

ここの id 部分に Key属性をつけてもよいのですが、このファイル自体はさわりたくないので、DbContext の OnModelCreating メソッドで調節します。

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<ホームページ>().HasKey(x => x.ID);
        modelBuilder.Entity<確認>().HasKey(x => x.日付);
        modelBuilder.Entity<期首>().HasKey(x => x.ID);
        modelBuilder.Entity<県>().HasKey(x => x.ID);
        modelBuilder.Entity<顧客>().HasKey(x => x.ID);
        modelBuilder.Entity<顧客SUB>().HasKey(x => x.ID);
        modelBuilder.Entity<予約>().HasKey(x => x.ID);
        modelBuilder.Entity<予約SUB>().HasKey(x => x.ID);
        modelBuilder.Entity<予約確認>().HasKey(x => x.ID);
        modelBuilder.Entity<予約状況>().HasKey(x => x.ID);
        modelBuilder.Entity<会社基本情報>().HasKey(x => x.id);

まめに HasKey で主キーを設定してやれば ok です。

生 SQL を呼び出したときの戻り値のクラスでは、主キーがないことを設定する

実テーブルを参照する場合には主キーがあるのですが、生 SQL を使って検索結果を返すときには主キーはありません。なので、.NET6 の EFCore では、HasNoKey を使って明示的に設定しないといけません。

全て LINQ で済めばよいのですが、複雑な join が発生する場合、生 SQL を使ったようが良い場合があります。例えば、以下のような SQL を LINQ に書き直すのはかなり困難ですし、移行時に間違いが発生します。なので、そのまま SQL を使います。

        var SQL = $@"
select
予約.ID,
顧客.会社名,
予約.日付,
部屋.名称 as 会議室,
予約状況.開始時刻 as 開始,
予約状況.終了時刻 as 終了,
顧客sub.部署名 as 部署名,
顧客sub.担当者A as 担当者,
顧客sub.TEL as TEL,
顧客sub.FAX as FAX,
予約.予約 as 状態,
予約.契約日 as 契約日,
予約.確認票最終処理日 as 確認日,
請求.日付 as 請求日,
X.利用料,
isnull(入金.入金額,0) as 入金額,
予約.備考B as 注意事項,
予約.案内名称 as 案内板名称
from 
( 
select
予約.ID as ID,
cast(ISNULL(sum(予約sub.単価*予約sub.数量),0) as money) as 利用料
from 予約
left join 予約sub on 予約sub.予約ID = 予約.id
where 予約.顧客id = {顧客ID}
group by 
予約.ID 
) X
inner join 予約 on 予約.id = X.ID
inner join 顧客 on 顧客.ID = 予約.顧客ID
inner join 顧客sub on 顧客sub.ID = 予約.顧客SUBID
left join 請求 on 請求.id = 予約.請NO
left join 入金 on 入金.請求ID = 請求.ID
inner join 部屋 on 部屋.id = 予約.会議室
inner join 予約状況 on 予約状況.予約ID = 予約.ID
order by 予約.日付 desc 
";
this.Items = App.ent.Database.SqlQuery<結果>(SQL).ToList();

この時、戻り値として使っている「結果」クラスは、.NET Framework の場合は適当に値クラスを作って検索結果を受け取ればいいのです(いちいち値をクラスを作るのが面倒ではあるのですが)。

しかし、.NET6 の EFCore には Database.SqlQuery に相当するものがなく、DbSet<T>.FromSqlRaw を使うことになっています。使い方は、以下のようにいったん DbContext に「結果」コレクションをはやすことになります。

            var SQL = $@"
select
...
";
this.Items = App.ent.結果.FromSqlRaw(SQL).ToList();

これに伴い、DbContext クラスには、戻り値のクラスも DbSet<T> で定義しないといけません。

        // 戻り値用
        public virtual DbSet<ViewModels.VM請求一覧.請求一覧> 請求一覧 { get; set; }
        public virtual DbSet<ViewModels.VM顧客履歴.結果> 結果 { get; set; }
        public virtual DbSet<ViewModels.VM予約一覧.Data> 予約一覧Data { get; set; }
        public virtual DbSet<ViewModels.VM予約詳細入力.引合> 予約詳細入力引合 { get; set; }
        public virtual DbSet<ViewModels.VM属性別集計.属性> 属性別集計属性 { get; set; }
        public virtual DbSet<ViewModels.VM年間グラフ.年間売上> 年間売上 { get; set; }
        public virtual DbSet<ViewModels.VM月別売上.月別売上> 月別売上 { get; set; }
        public virtual DbSet<ViewModels.VM請求集計.請求> 請求集計請求 { get; set; }
        public virtual DbSet<ViewModels.VM過去予約状況.結果> 過去予約状況結果 { get; set; }
        public virtual DbSet<ViewModels.VM顧客別利用状況集計.結果> 顧客別利用状況集計結果 { get; set; }
        public virtual DbSet<ViewModels.VM顧客利用回数一覧.利用回数> 顧客利用回数一覧利用回数 { get; set; }
        public virtual DbSet<ViewModels.VM顧客利用状況.利用明細> 顧客利用状況利用明細 { get; set; }

実は、戻り値クラスはそれぞれの ViewModel クラス内でしか使わないので private になっているのですが、.NET6 の場合には DbContext から参照できるように public(あるいはinternal)の変更が必要になります。はっきり言って、この FromSqlRaw の仕組みはよくないです。もともと内部クラスになるので、名前の重複が激しくなっています。

この戻り値専用のクラスは、主キーがありません。これを明示的に示すために OnModelCreating 内で HasNoKey を指定します。

            modelBuilder.Entity<ViewModels.VM請求一覧.請求一覧>().HasNoKey();
            modelBuilder.Entity<ViewModels.VM顧客履歴.結果>().HasNoKey();
            modelBuilder.Entity<ViewModels.VM予約一覧.Data>().HasNoKey();
            modelBuilder.Entity<ViewModels.VM予約詳細入力.引合>().HasNoKey();
            modelBuilder.Entity<ViewModels.VM属性別集計.属性>().HasNoKey();
            modelBuilder.Entity<ViewModels.VM年間グラフ.年間売上>().HasNoKey();
            modelBuilder.Entity<ViewModels.VM月別売上.月別売上>().HasNoKey();
            modelBuilder.Entity<ViewModels.VM請求集計.請求>().HasNoKey();
            modelBuilder.Entity<ViewModels.VM過去予約状況.結果>().HasNoKey();
            modelBuilder.Entity<ViewModels.VM顧客別利用状況集計.結果>().HasNoKey();
            modelBuilder.Entity<ViewModels.VM顧客利用回数一覧.利用回数>().HasNoKey();
            modelBuilder.Entity<ViewModels.VM顧客利用状況.利用明細>().HasNoKey();

Entity クラスに NoKey 属性のようなものがあればよいのですが、どうも無いみたいですね。

データベース上のカラム名と一致させる

.NET6 の EFCore では、データベース上もカラム名と Entity クラスのカラム名が完全に一定していないといけません。よって、次のようなデータベース上では、正式な名前の場合、ちょっと困ることがおこります。

これは前回の移植上、予約テーブルに「予約」という名前のカラムがついています。

実は、Entity Framework では、テーブル名とカラム名が同じのは使えません。以前から問題だと思っているのですが、テーブル名もカラム名も同じ名前空間で混在させてしまっているからなのです。

なので、.NET Framework の EF では、以下のように「予約1」という名前が勝手に振られます。

    public partial class 予約
    {
        public int ID { get; set; }
        public int 顧客ID { get; set; }
        public int 顧客SUBID { get; set; }
        public Nullable<int> 予約者 { get; set; }
        public string 予約会社 { get; set; }
        public string 予約担当 { get; set; }
        public string 予約TEL { get; set; }
        public int 担当者ID { get; set; }
        public Nullable<System.DateTime> 日付 { get; set; }
        public Nullable<System.DateTime> 記録日 { get; set; }
        public Nullable<System.DateTime> 契約日 { get; set; }
        public Nullable<System.DateTime> M期限 { get; set; }
        public Nullable<System.DateTime> K期限 { get; set; }
        public Nullable<int> 予約1 { get; set; }
        public Nullable<int> 会議室 { get; set; }
        public Nullable<int> TF { get; set; }
        public Nullable<int> TE { get; set; }

ところが、.NET6 の EFCore では、データベースのカラム名と Entity クラスのプロパティ名は同一であるという前提があるので、以下のように Column 属性で変更しておきます。

    public partial class 予約
    {
        public int ID { get; set; }
        public int 顧客ID { get; set; }
        public int 顧客SUBID { get; set; }
        public Nullable<int> 予約者 { get; set; }
        public string 予約会社 { get; set; }
        public string 予約担当 { get; set; }
        public string 予約TEL { get; set; }
        public int 担当者ID { get; set; }
        public Nullable<System.DateTime> 日付 { get; set; }
        public Nullable<System.DateTime> 記録日 { get; set; }
        public Nullable<System.DateTime> 契約日 { get; set; }
        public Nullable<System.DateTime> M期限 { get; set; }
        public Nullable<System.DateTime> K期限 { get; set; }
        [Column("予約")]
        public Nullable<int> 予約1 { get; set; }
        public Nullable<int> 会議室 { get; set; }

この部分は、実は「予約」プロパティに変更してもよいのですが、既に各種のコードから「予約1」で参照しているので、参照先のコードは変更したくありません。よって、Column 属性でのがれておきます。

拡張したプロパティを DbSet に無視させる

.NET Framework の Entity クラスを MVVM パターンで View にバインドさせる場合、表示上フォーマットを変更させることがよくあります。Converter を作るのもよいのですが、どうせ partial になっているので、適当なプロパティを作って拡張させておくと便利、だったのです。

    public partial class 単発アラーム
    {
        public int ID { get; set; }
        public Nullable<System.DateTime> 日時 { get; set; }
        public string 対象種別 { get; set; }
        public string 対象名 { get; set; }
        public string 表示内容 { get; set; }
        public System.DateTime CreateAt { get; set; }
        public System.DateTime UpdateAt { get; set; }
    }

このような自動生成された Entity クラスとは別に、patial で拡張しておきます。

    /// <summary>
    /// 日時を年月日と時分に分ける拡張
    /// </summary>
    public partial class 単発アラーム
    {
        public Nullable<System.DateTime> 日時_年月日
        {
            get { return this.日時; }
            set
            {
                if ( this.日時.HasValue )
                {
                    DateTime dt = new DateTime(
                        value.Value.Year,
                        value.Value.Month,
                        value.Value.Day,
                        日時.Value.Hour,
                        日時.Value.Minute,
                        0);
                    this.日時 = dt;
                }
            }
        }
        public Nullable<System.DateTime> 日時_時分
        {
            get { return this.日時; }
            set
            {
                if (this.日時.HasValue)
                {
                    DateTime dt = new DateTime(
                        日時.Value.Year,
                        日時.Value.Month,
                        日時.Value.Day,
                        value.Value.Hour,
                        value.Value.Minute,
                        0);
                    this.日時 = dt;
                }
            }
        }
    }

ここで .NET6 への移植時に問題が発生します。

.NET Framework の EF では、テーブル構造が *.edmx ファイルに分離されているので、拡張した日時_年月日プロパティや日時_時分プロパティは更新時に無視されるのですが、.NET6 の EFCoreでは更新対象がEntityクラスの全プロパティとなるため、この拡張したプロパティを「無視」させるようにしなければなりません。

DbContext の OnModelCreating 内で、

            modelBuilder.Entity<単発アラーム>()
                .Ignore("日時_年月日")
                .Ignore("日時_時分");
            modelBuilder.Entity<周期アラーム>()
                .Ignore("周期月一週")
                .Ignore("周期毎年")
                .Ignore("周期毎月")
                .Ignore("周期毎週")
                .Ignore("周期月一曜日")
                ;

のように、拡張したプロパティを ignore するようにしていきます。この部分は、拡張したプロパティのほうに Ignore属性が付けられればよいのですが、無いようです。不便ですね。

問題はこのエラーの発生は、コンパイル時ではなく実行時にしか発生しないので、いちいち確かめないといけません。元の Entity クラスを拡張していると結構やっかいな問題です。

WindowsFormsHost を使う

.NET Framework の WPF アプリケーションでは WindowsFormsHost を使って、Windows フォームの各種コントロールを埋め込むことができます。

これは客先の「カレンダーのフォーマット/色合いを従来のものと同じにしてほしい」という要望を実現したものです。従来は ACCESS のカレンダーを使っているので、WPF カレンダーのものは使えません。

しかたがないので、ユーザーコントロールを作って WindowsFormsHost で埋め込みます。ユーザーコントロールにしたのは、あちこちの画面でこのカレンダーが出てくるので共通化するためです。

<Canvas Grid.Row="1" Grid.Column="0" x:Name="cv">
    <WindowsFormsHost FontSize="20" x:Name="host" >
        <wf:MonthCalendar 
            x:Name="cal" CalendarDimensions="3,1" DateSelected="MonthCalendar_DateSelected" />
    </WindowsFormsHost>
</Canvas>

実は、.NET6 では WindowsFormsHost の部分が使えなくて暫く悩んでいたのですが、

<UseWindowsForms>true</UseWindowsForms>

プロジェクトファイルに UseWindowsForms を設定して解決します。おそらく、.NET6 の WPF アプリケーションでツールボックスから WindowsFormsHost をドロップすると自動的に、UseWindowsForms が true になるはずです。

office.dll への参照を追加する

かなり悩んだのが次のところです。

MsoTriState が存在しないというエラーがでています。これは Excel の Shape を設定している部分なのですが、Excel COM を参照させてはいても、このエラーは取り除けません。おそらく Excel/Word に共通化されている Shape クラスは「office, Version=15.0.0.0」にあるようです。

さて、 「office, Version=15.0.0.0」 は一体どこにあるのでしょうか?

実は、.NET Framework のプロジェクトを見ると「アセンブリ」のタブのほうに「office 15.0.0.0」を参照しているところがあります。これは Office の COM ではなくて、.NET Framework が提供しているアセンブリのほうなんですね。

.NET6 では各種のアセンブリを個別で参照することはできない(自動的にアセンブリ参照が解決されるため)ので、明示的に office.dll を参照させます。

プロジェクトファイルに以下を記述しても ok です。

  <ItemGroup>
    <Reference Include="office">
      <HintPath>c:\Program Files (x86)\Microsoft Visual Studio\Shared\Visual Studio Tools for Office\PIA\Office15\Office.dll</HintPath>
    </Reference>
  </ItemGroup>

その他

その他 Excel COM の参照や Newtonsoft.Json、ClosedXML の参照は問題なく動いています。

EFCore の拡張プロパティの問題は、実際に動かしてみないとエラーが発生しないのでもう少し対処が必要です。

作業時間はおおむね1日がかりなので、移行としては十分可能なレベルですが、問題は FromSqlRaw への書き換えですかね。外部結合を使う場合、LINQ ではかなり手間なので生 SQL で組んでしまうのですが、プロジェクトによって数が多いと大変かと思います。

.NET6 にしておくと何が便利かと言うと TargetFramework を「net6.0-windows10.0.17763.0」にしておいて Windows Runtime(UWP や Windows App SDKの機能)が手軽に使えるようになります。まあ、業務用のデスクトップアプリなので、公開とかしないわけでそれほど利用価値があるわけではないのですが。ふりがな機能とか通知機能、連絡帳などが WinRT のほうに入っていたりします。

カテゴリー: 開発 | .NET Framework 4.7 から .NET6 への移行ポイント はコメントを受け付けていません

Exposure Notifications/COCOAのビーコンを収集して解析する

Google/Apple の提供する Exposure Notifications では BLE でビーコンを発信しています。内部的には 32 バイトなデータを Advertising/Broadcasting ってことになります。

コード自体は、openCACAO/CacaoBeacon: https://github.com/openCACAO/CacaoBeacon でビーコンの受信&収集コードを公開しています。CacaoBeaconMonitor が Android アプリです。iOS 版はありません。ENs では、ビーコンにある ID を受信するのですが、iOS の場合はこれが OS 側で塞がれているためです。なので、ビーコンのモニタリングとしては Android あるいは Windows を使います。

ノートパソコンを持ってうろうろするのは大変なので(定点観測ならばノートPCでも十分ですが)、Android のスマホを使います。

感染が拡大したので蒐集してみる

コード作ったのが4か月前で、去年の秋には東京都の感染者がぐんぐん減っている時期で、実験的なデータがとりずらい状態でした。で、まあ、とりあえず放置状態にしてあったのですが、第6波の勢いは尋常ではなく、現時点で東京都では2万/日をカウントしています。実に去年の秋の1000倍近い数字になってしまったわけですが。

COCOA自体の稼働率は、おそらく人口比で3割を超えています。通勤のような社会人に限っていれば、半分以上はあるっぽいです。 CacaoBeaconMonitor の場合は、COCOA から発信するビーコンを受けて RPI を保持していく Google/Apple の Exposure Notifications のクローンのような動作をさせているので、多分正確に収集ができます。

陽性者とのマッチングは別途、probetek で zip をダウンロードしてきて、SQL Server 上でクエリを書いています。

実測データ

2022年2月3日に、池袋を2時間ほど徘徊したデータ

https://1drv.ms/u/s!AmXmBbuizQkXgrlPo98iECzyoSgy8w?e=nr7QM0

上記から SQLite のデータがダウンロードできます。東武東上線で池袋まで行って、東武デパートで食事をして、帰ってくるまでの2時間のデータになります。

収集データの解説

テーブル構造は、以下の通り

CREATE TABLE RPI (
    Id INTEGER NOT NULL CONSTRAINT PK_RPI PRIMARY KEY AUTOINCREMENT,
    Key BLOB NULL,
    Metadata BLOB NULL,
    StartTime TEXT NOT NULL,
    EndTime TEXT NOT NULL,
    RssiMin INTEGER NOT NULL,
    RssiMax INTEGER NOT NULL,
    MAC INTEGER NOT NULL
)

ビーコンの受信開始(StartTime)と受信終了(EndTime)で接触時間が計測できます。ただし、ビーコンの受信データだけだと、発信のほうが10分で切り替わるので10分以内しか判別できません。別途、陽性者のTEKをダウンロードしてきて、受信したRPIを照合させると10分以上の接触が確認できます。実際の ENs が「接触の可能性あり」で通知しているのはこの部分です。

電波強度(RssiMax)を見ると、どれだけ近接しているかがわかります。私自身 iPhone に COCOA を入れているので、ひとつだけはかなり近いデータになっています。それ以外は、電車やホームでのすれ違い、エスカレーターで並んでいたときと考えられます。データの収集は一瞬でも近づけばデータとして保持されるので、きちんと解析するときは、近接している時間(EndTime – StartTime)と、電波強度(RssiMax)を見るとよいでしょう。

実際に実験をしてみると、バスが通ったり、電車が通過したときでも同時接続数が40程度の爆上がりします。いわゆる一瞬のすれ違いでも計測されるということです。

COCOA が使う ENs の仕組みは完全に公開されているとは言えませんが、おおまかなところはコードで示されています。実際の照合のところは数式でしか示されていないのが残念なところですが、公式で言われている通り、電波強度と近接時間の両方をみて判断して1mと15分以上という値を割り出しています。

TEK データのダウンロード

COCOA を通して陽性登録された TEK のデータを probetek コマンドでダウンロードします。

dotnet run --all

2/4時点で、50万件のTEKがあります。TEK は日単位で変わるので、おおむね7で割ると登録数になります。おおむね7万件というところでしょう。このデータは全国なので、2,3割の陽性者が登録している計算になります。実際のところは、厚生労働省のページ https://www.mhlw.go.jp/stf/seisakunitsuite/bunya/cocoa_00138.html で確認ができます。

この TEK データを SQL Server にアップロードします。

dotnet run --update-tek

TEK から、その日の 144個(10分単位)の RPI を生成して EXRPI テーブルに挿入しています。

と思ったのですが、データ量が 50万 x 144 で 7200万件になるので尋常ではありません。そのまま動かすとPCのメモリのデータベースもいっぱいになってしまうので、照合の部分は考え直しましょう。

実際の Google/Apple の ENs では C++ でバイナリ照合しているのでメモリは枯渇しません。どうやら照合用のツールを別途作る必要がありそうです。

カテゴリー: 開発 | Exposure Notifications/COCOAのビーコンを収集して解析する はコメントを受け付けていません

Redmine の docker を作る

Hyper-V + WLS2 な環境で Redmine を構築してもよいのですが、いろいろ面倒(特に Ruby on Rails が面倒)なので、docker を使って構築します。

Windows 上で構築すると MySQL が相乗りになったり、Ruby のパスを設定したりといろいろ面倒で、仮想環境の Linux に入れていたのですが、.NET6 + gRPC の実験用の構築です。

docker-compose を使え

その昔、ひとつのコンテナに ruby + mysql を入れてあれこれと構築していた頃があったのですが、結果的にひとつのコンテナにひとつのアプリを入れてしまったほうが楽ですね。

Redmine – Official Image | Docker Hub https://hub.docker.com/_/redmine/

なので、素直に docker-composer を使います。以前の Docker Desktop ではうまく動かないことが多かったのですが、現在は結構スムースっぽい。

以下を、docker-compose.yml というファイル名で保存します。

公式との違いは、

  • MySQL を外から参照できるようにポートフォワードしている(後で gRPC で使う)
  • command で絵文字が使えるように utf8mb4 を指定する。
    これがないと、Redmine で日本語を使ったときに落ちる。
version: '3.1'

services:

  redmine:
    image: redmine
    restart: always
    ports:
      - 8081:3000
    environment:
      REDMINE_DB_MYSQL: db
      REDMINE_DB_PASSWORD: example
      REDMINE_SECRET_KEY_BASE: supersecretkey

  db:
    image: mysql:5.7
    restart: always
    ports:
      - 33061:3306
    environment:
      MYSQL_ROOT_PASSWORD: example
      MYSQL_DATABASE: redmine
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci

パスワードは面倒なので「example」のままで使っています。

この例では、

  • Redmine に localhost:8081 で接続
  • MySQL にポート番号 33061 で接続
  • mysql -u root -p で、パスワード example で接続できる

このあとで、docker-compose.yml と同じディレクトリで、以下を行えば ok

# コンテナの作成
docker-compose create
# コンテナの実行
docker-compose start

名前を間違ったりポートを間違ったりうまく作れなかったときは、Docker Desktop でコンテナを削除して何度も作り直します。

この例では、

こんな感じの2つのコンテナがひとつにまとめられます。

ブラウザで確認

localhost:8081 で確認します。初期パスワードは、admin/admin です。

初期データ(ロールやトラッカーなど)が入っていないので、デフォルト値のロードをします。このとき、文字コードが、utf8mb4 あるいは utf8 になっていないと、redmine 側でエラーになります。このときは、docker-compose の戻って、再構築します。

MySQL Workbench で確認

docker 内の mysql に接続します。

無事、テーブル構造が取得できています。

これを使って、.NET6 から CURD 用のエンティティクラスを作って、gRPC の *.proto をコード生成してやれば、適当なフロントエンドが作れるかも、ってところまで。

ちなみに、Oracle 19c の場合は 8GB 位メモリを持っていかれるのですが、redmine の場合は、512MB 位で済みます。これだとローカルPC内で動かしても大丈夫そう。oracle 19c のほうは、別のマシンに移動しました。

カテゴリー: 開発 | Redmine の docker を作る はコメントを受け付けていません

Oracle Database 19c な docker 環境の構築

基本的に Linux な環境で構築する場合 [Oracle Database] 公式Docker Imageを利用してOracle Database 19c環境を構築してみた | | IT Edge Blog https://itedge.stars.ne.jp/docker_image_oracle_database_19c/ と同じなのですが、Windows 10 の場合 image をどのように作るのか?の問題があります。

で、WLS2 で作ったらあっさりできたので、メモ代わりの残しておきます。

19c な Docker Image を作る

Oracle 公式では、12c https://hub.docker.com/_/oracle-database-enterprise-edition までしか用意されていません。結構古いものがのっかったままなので、どうするのかというと、Github にある Oracle の公式手順で作成していきます。

docker-images/OracleDatabase/SingleInstance at main · oracle/docker-images https://github.com/oracle/docker-images/tree/main/OracleDatabase/SingleInstance

ここの手順にしたがって、

$ ./buildContainerImage.sh

してから、

$ docker run –name

すればよいのです。

Linux 環境だと sh があるので、 buildContainerImage.sh が動くのですが、Windows 環境の場合はどうするのか?と言えば、WLS2 を使います。本当は Docker Image をバイナリで出力してくれればいいのですが、このスクリプトはそのまま docker に乗せてしまうので、さて、どうしたものかという感じではあるのですが。

大丈夫。WLS2 とホストな Windows 10(あるいは Windows 11)は内部的にはつながっているので、WLS2 から docker に乗せたもは Windows のほうの docker に乗せられます。というか、Windows のほうが WLS2 の docker を借用するようになっているのかもしれません。

まずは、git clone で oracle/docker-images を丸ごと取得

git clone git@github.com:oracle/docker-images.git

WLS2 を開いて、docker-images/OracleDatabase/SingleInstance/dockerfiles フォルダーへ移動

Oracle 本家から LINUX.X64_193000_db_home.zip をダウンロードして、19.3.0 に置く。zip は解凍せずにそのままでok。

例えば 19.c(19.3.0)で、Enterprise Edition を入れる場合は以下のように指定。

./buildDockerImage.sh -v 19.3.0 -e

これを実行すると、oracle/database の docker image が Windows のほうに追加されます。

コンテナを作る

コンテナを作るのは、Windows のほうで可能です。コマンドラインで次を実行します。

docker run --name oracle19c -p 1521:1521 -p 5500:5500 oracle/database:19.3.0-ee

ホストとなる Windows のほうでは Oracle が入っていないので、ポート番号は 1521 のままにしておきます。ホストとなる Windows のポート番号を変えておきたいときは -p 15210:1521 のようにします。Windows 側で localhost:15210 につなげたときに docker 側で 1521 につながるようになります。

docker run に渡すパラメータは以下の通りです。

docker run --name <container name> \
-p <host port>:1521 -p <host port>:5500 \
-e ORACLE_SID=<your SID> \
-e ORACLE_PDB=<your PDB name> \
-e ORACLE_PWD=<your database passwords> \
-e INIT_SGA_SIZE=<your database SGA memory in MB> \
-e INIT_PGA_SIZE=<your database PGA memory in MB> \
-e ORACLE_EDITION=<your database edition> \
-e ORACLE_CHARACTERSET=<your character set> \
-e ENABLE_ARCHIVELOG=true \
-v [<host mount point>:]/opt/oracle/oradata \
oracle/database:19.3.0-ee

面倒なので、コンテナを作成したのですが、気を付けるポイントとしては、

  • ORACLE_SID のデフォルトが「ORCLCDB」
  • ORACLE_PDBのデフォルトが「ORCLPDB1」
  • ORACLE_PWDは、コンテナの実行時に自動的に決まります。

sys で接続確認

Windows のほうに sqlplus のクライアントだけを入れておくと、接続確認ができます。

sqlplus sys/siosWOA6SKw=1@localhost:1521/ORCLCDB as sysdba

ログインユーザーと表領域の作成

ログインユーザーを作ってためしておきます。

あとで、.NET から接続確認したいので redmine ユーザーを作ります。

alter session set container=ORCLPDB1;

create tablespace "redminets" datafile '/opt/oracle/oradata/ORCLCDB/redminets.dbf' size 500M
 AUTOEXTEND ON NEXT 100M MAXSIZE 1G LOGGING EXTENT MANAGEMENT
 LOCAL SEGMENT SPACE MANAGEMENT AUTO;

create user redmine
 identified by redmine
 default tablespace "redminets"
 account unlock ;

GRANT connect TO "REDMINE";
GRANT CREATE SESSION TO "REDMINE";
GRANT "RESOURCE" TO "REDMINE";
ALTER USER "REDMINE" DEFAULT ROLE ALL;

あとは、sqlplus での接続の仕方が違うので、注意しておきましょう。SID とサービス名が初期値で少しややこしくなています。

□docker内で
sqlplus redmine/redmine@orclpdb1
sqlplus redmine/redmine@localhost:1521/ORCLCDB

□windows側から
sqlplus sys/siosWOA6SKw=1@localhost:1521/ORCLCDB as sysdba
sqlplus redmine/redmine@localhost:1521/orclpdb1

lsnrctl status の結果がこちら

メモリに注意

と、ここまで書きましたが、実は docker に oracle database 19c を入れるのはあまりお勧めできません。Oracle 公式が提供している docker images 作成用のスクリプトが非常に多くのメモリを利用する設定しく、コンテナを動かすと 16 GB ほど使われます。

docker stats で調べると 8GB 位つかっています。

こんなに使われると、ホスト側の Windows が死んでしまうので、.wslconfig で制限をします。この加減がよくわからないのですが、まあ、Hyper-V の仮想環境で Windows Server + Oracle を作ったときと同じくらい喰うのはどうなの?って感じです。環境的には、可搬性があるからいいけど。

Docker+WSL2の環境でVmmemのメモリ量が巨大になるのを制限する – いろいろ備忘録日記 https://devlights.hatenablog.com/entry/2021/10/28/073000

カテゴリー: 開発 | Oracle Database 19c な docker 環境の構築 はコメントを受け付けていません