ORマッパー(EFCore)で出身DBやUpdatable/ReadOnlyを変数型で表現する
EntityFrameworkCoreを使ったときに色々心配になったので、変数型と構文チェックの力を借りて工夫してみたという話。
例えばEFCoreで普通にDbContextを作ると
public class MyDbContext : DbContext { public DbSet<Account> Account { get; set; } }
AccountテーブルにアクセスできるDbContextはこんな感じになるかと思います。一般的な使い方なのですが
というのが気になっていました。そこでDbContextの記述を工夫することにしました。
まずはインターフェース。DIを使ってインターフェースを通してDBにアクセスすることを想定してます。
// プライマリーDBアクセス用 public interface IMyDbContext: IMyNoTrackingDbContext<MainDb>, IDisposable { DbSet<Account> Account { get; } } // リードレプリカアクセス用 public interface IMyReadDbContext: IMyNoTrackingDbContext<ReadDb>, IDisposable { } public interface IMyNoTrackingDbContext<TDb> : IDisposable where TDb: MyDbType { // 更新不要の場合はこちらから取得する IQueryable<IAccount<TDb>> NoTrackingAccount { get; } } public interface IAccount<TDb> where TDb: MyDbType { public string AccountId { get; } }
プライマリーDBにアクセスするインターフェースとリードレプリカにアクセスするインターフェースを別々に用意しています。 ジェネリクス引数にあるMainDbがプライマリーDB、ReadDbがリードレプリカを表します。 ジェネリクスの指定なども考えてMainDb、ReadDbは次のように宣言します。
public class MyDbType {} public class MainDb : MyDbType {} public class ReadDb: MyDbType {}
クラスの定義はインターフェースを継承してこんな感じ。
public class MyDbContext : DbContext, IMyDbContext { public DbSet<Account> Account { get; set; } IQueryable<IAccount<MainDb>> IMyNoTrackingDbContext<MainDb>.NoTrackingAccount => this.Account.AsNoTracking(); } public class MyReadDbContext: DbContext, IMyReadDbContext { public DbSet<ReadOnlyAccount> Account { get; set; } IQueryable<IAccount<ReadDb>> IMyNoTrackingDbContext<ReadDb>.NoTrackingAccount => this.Account.AsNoTracking(); } public class Account : IAccount<MainDb> { public string AccountId { get; set; } } public class ReadOnlyAccount : IAccount<ReadDb> { public string AccountId { get; protected set; } }
ここまで準備すると、プログラム中の型を見るだけで色々わかるようになります。 AccountならプライマリーDB出身でアップデート可能、IAccount<ReadDb>ならリードレプリカ出身、といった感じです。
記述量の多さに関しては自動生成で何とかします。私の場合はDB定義をJSONで書いて、T4を使って自動生成しています。
AsNoTrackingはDB定義の記述以外では使用禁止にします。アップデート不可能なAccoutインスタンスが登場するのを防ぎます。全文検索で定期的に探してNoTrackingAccountから取得するように置き換えます。
失敗パターン
上記の形になる前に一度失敗をしています。そのときは
public class MyDbContext : DbContext, IMyDbContext { public DbSet<Account> Account { get; set; } } public class MyReadDbContext: DbContext, IMyReadDbContext { public DbSet<ReadOnlyAccount> Account { get; set; } IQueryable<ReadOnlyAccount> IMyReadDbContext.ReadOnlyAccount => this.Account.AsNoTracking(); }
のように出身DBを表すジェネリクス変数なしで定義していました。しばらく運用した結果、
- AccountインスタンスがAsNoTrackingされてて、値を上書きしても更新されないかも
- プライマリーDB用とリードレプリカ用で同じ内容を2回書く必要があってBad
という問題が発生しました。
AccountインスタンスがAsNoTrackingされてて、値を上書きしても更新されないかも
当初はReadOnlyでNoTrackingに呼び出したいなら全部リードレプリカから取得すれば良い、と考えてました。 プライマリーDBからNoTrackingで取得することは想定していなかったのです。
ところが、プライマリーDBからもNoTrackingで呼び出す必要が出てきました。 具体的には、変更をセーブした直後にその値を取得する、という場面では全部プライマリーDBで実施する必要があります。 (プライマリーDBに)変更をセーブした直後に、(リードレプリカから)その値を取得する、ということをすると 低確率で変更後の値を取得できなかったのです(リードレプリカへのコピーが間に合ってない)。
結果としてプライマリーDBからAsNoTracking でデータを取得するようになり、 Account型を見ても更新可能なAccountインスタンスなのかAsNoTrackingされたAccountインスタンスなのか一見ではわからなくなってしましました。
プライマリーDB用とリードレプリカ用で同じ内容を2回書く必要があってBad
例えばAccountIdでAccountインスタンスを取得する関数を作成した場合
public static class MyDbContextExtensions { public static Account GetAccountFromPrimaryDb( this IMyDbContext db, string accountId) { return db.Account.FirstOrDefault(e => e.AccountId == accountId); } public static ReadOnlyAccount GetAccountFromReadDb( this IMyReadDbContext db, string accountId) { return db.ReadOnlyAccount.FirstOrDefault(e => e.AccountId == accountId); } }
こんな感じで同じような記述を2回書く必要が出てきてしまいました。 上記くらい単純な例ならまだ良いのですが、実際は多数のJoinを行う関数が存在し、複雑な記述の同期を取る必要がでてきてしまいました。
改善後は
public static class MyDbContextExtensions { public static IAccount<TDb> GetAccount<TDb>( this IMyNoTrackingDbContext<TDb> db, string accountId) where TDb: MyDbType { return db.NoTrackingAccount.FirstOrDefault(e => e.AccountId == accountId); } }
だけでOKになります。
何とかしたいけど何とかなってない点
上記の例は、NoTrackingなAccountインスタンスを取得するだけならOKです。 しかし、Update可能なAccountインスタンスも取得できるようにしようとすると
public static class MyDbContextExtensions { public static T GetAccount<T, TDb>( this IQueryable<T> accountTable, string accountId) where T: IAccount<TDb> where TDb: MyDbType { return accountTable.FirstOrDefault(e => e.AccountId == accountId); } }
こんな感じでジェネリクス変数2つを用いた記述になります。こうすると
Account wriableAccount = db.Account.GetAccount<Account, MainDb>(accountId); IAccount<MainDb> readOnlyAccountFromMainDb = db.NoTrackingAccount.GetAccount<IAccount<MainDb>, MainDb>(accountId); IAccount<ReadDb> readOnlyAccountFromReadDb = readDb.NoTrackingAccount.GetAccount<IAccount<ReadDb>, ReadDb>(accountId);
のように全パターン対応できます。ただジェネリクス変数を書くのが手間です。 このジェネリクス変数、省略可能にできる気がするのですが、ジェネリクスを2段階経てしまうと推測できないようでコンパイルエラーとなります。
カロリーゲーム
健康の話。ダイエットの話。今年の3月から始めて上手く結果が出たので記録。やったことは単純で「一日に摂取するカロリー量を一定以下に抑える」だけ。ちなみにダイエットを始める前のBMI値は22付近で成人としては普通の範囲内です。体脂肪率は15%付近。
一日の総カロリー
元々カロリーは気にすることはありましたが、食品単体の値だけを見ていました。一日の総量という視点が欠けていたのです。そこで一日1500kcalに抑えるようにしてみました。1500kcalというのは成人男性の基礎代謝量だから選んだ数値で、そこまで根拠はないです。
体に良さそうな栄養素
この制限だけでは「お菓子100%で1500kcal」がセーフになってしまうので、「必要な栄養素が入っている食品で1500kcal」という制限を付けました。必要な栄養素とは、タンパク質、食物繊維、カルシウム、ビタミン類など、自分が体に良さそうと思ったものです。これもあまり根拠はないです。
断食など食事制限系のダイエットについて気になっていたことがあります。それは、脂肪ではなく筋力が落ちているのではないか、ということです。ダイエットの結果、健康を害しては意味がありません。今回、少し栄養について考えたのもそこが理由です。
緩和ルール
制限ばかりでは持続性に問題があるので、2つ緩和ルールを設けました。
- 他人との食事など自分のコントロール外な食事が発生した日はあまり気にしない
- 金曜夜から日曜まではルールをあまり気にしない
職場でラーメンを食べに行っただけで制限突破してしまうので、そこは気にしないことにしました。また、 休日まで完全実践すると息が詰まるので、1500kcalは気にしておくが栄養は無視してお菓子、焼き肉などでしっかり栄養を摂って1500kcalは無視、といったことをやってました。
結果
上が体重で、下が体脂肪です。体重が61.9kg→57.2kg、体脂肪が9.6kg→5.2kgですね。体重計が測定した体脂肪率はあくまで参考値ですが、それでも良い結果が出ているのではないでしょうか。
体重はWithingsのスマート体重計で測定しています。体重を測定すると無線LAN経由でサーバに自動データ送信。体重履歴はスマートフォンで確認できます。自分が使っている体重計は古い型なので、Amazonリンクは新型のものです。
今後
このダイエット手法は一区切り。一つのダイエット手法の有効期限は3ヶ月だと思っています。大体、3ヶ月もやってると効果が薄くなってきますし、何より飽きます。実際に体重・体脂肪は下げ止まっています。
今後は今回のダイエットで失われた筋力を取り戻す方にシフトすると思います(それもどれくらい続くかわかりませんが)。ちょうど筋トレアニメも始まりましたからね。
余談
記事の最初にダイエット前のBMI値を書きました。これはダイエット法の前提・対象者を明示することが大事だからですね。検索するとダイエット記事が沢山出てきますが、どんな人に有効か書いてないことが多い印象があります。
実際「1日2食!2食はいくら食べてもOK!」というのをやってみて逆に太りました。普段少食の人間が無制限に食べたら太る、という当たり前の話。おそらくこのダイエットは間食をよくする食べ過ぎの方が対象だったのでしょう。
そんなわけで、世のダイエット記事には体験者のステータスを載せて欲しいですね。
■
スタンフォード式最高の睡眠という書物を読みました。睡眠負債を返そう、とかそういう書物です。睡眠負債が流行語大賞にノミネートされたのは2017年末の話なので、実に1年遅れの読書になります。
- 作者: 西野精治
- 出版社/メーカー: サンマーク出版
- 発売日: 2017/02/28
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (7件) を見る
これは通常版の他に漫画版が出ていて、自分が読んだのは漫画版の方です。最近、書物を途中で放置することが多かったので、一気に最後まで読めそうな漫画版を選びました。
- 作者: 西野精治,四方山哲,星野卓也
- 出版社/メーカー: サンマーク出版
- 発売日: 2018/08/27
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
ITの仕事に就く前に3年ほど不規則な労働時間だった時期があり、睡眠不足に現在も悩まされています。不規則な労働時間のあとに、艦これで5時起きが日課となり、睡眠不足な状態はそのまま定着してしまいました。
自分に取り込めそうなところを箇条書き。
寝る前に足を温める
起きたら冷水で手を洗う
朝に固いもの(ガムなど)を噛む
朝起きたら陽の光を浴びる(意外と難易度高い)
マーケティング22の法則
- 作者: アルライズ,ジャックトラウト,Al Ries,Jack Trout,新井喜美夫
- 出版社/メーカー: 東急エージェンシー出版部
- 発売日: 1994/01/01
- メディア: 単行本
- 購入: 17人 クリック: 250回
- この商品を含むブログ (61件) を見る
マーケティングのカバー範囲が想像以上に幅広かった。開発完了した商品を売るための方策かと思っていたが、この本の中では商品戦略の話であった。関係する業務の範囲が広いので「経営者自身がマーケティングしろ」という法則が出てくるのもわかる。
各カテゴリの1位2位までしか生き残れない、模倣では2位にすらなれない、など、法則が言語化されるのは大きい。本書を読む前までは「何となくそんな気がする」くらいの感覚だった。
事例はかなり古い。スティーブ・ジョブスはアップルにいないし、ロータスは表計算ソフト市場でナンバーワンだし、トランプ大統領は破産している。それでも現在も通用する理論であるように思う。
T4TextTemplatesEngineをUnityで使う
省力化のお供テンプレートエンジンをUnityでも使ってみました。テンプレートエンジンを使って設定ファイルからコードを自動生成して楽をしよう!というわけです。今回使ったテンプレートエンジンはT4TextTemplatesEngineです。テンプレートエンジン用のプロジェクトをUnityプロジェクトとは別に作る必要がある、というのがポイントです。
コード自動生成のメリット
コード自動生成により、Apiやデータベースのテーブルの追加の手間が大幅に削減できます。1つApiを増やすたびに関連コードをキッチリ増やすのはなかなかの手間ですし、コード規模が大きくなってくると何を増やせばいいのかを忘れてしまうでしょう。追加手順をドキュメント整備しても、いずれ整備が面倒くさくなってコードとドキュメントが乖離するのは目に見えています。
また、コード自動生成はTypoを疑わなくても良いのが強みです。Apiの追加をクライアント側・サーバー側でそれぞれ行ったがうまく動かない→片方がTypoしてた、という典型的なミスはよくあるもの。このミスが発生しない+このミスを調査する時間が省ける、というのは大きなメリットです。
T4TextTemplatesEngine
Code Generation and T4 Text Templates
今回は表題の通り、コード自動生成にT4TextTemplateEngineを使いました。自力で自動生成スクリプトを組む方法もありますが、テンプレートエンジンを使う方が汎用性が高いだろうと思いました。詰まったときにお手製スクリプトを読む必要があるよりは、検索エンジンで解決策が拾える方が良いでしょう。
T4にしたのはVisualStudioに付属してたから、という消極的な理由です。色々な環境で広く使われるテンプレートエンジンが出てくれば、そちらを選択した方が良いのかな、と思います。
テンプレートエンジン用のプロジェクトを別途作る
Unityのプロジェクトの他に、テンプレートエンジン用にプロジェクトを作成する必要があります。Unityのプロジェクトにテンプレートを追加しても、Unityのプロジェクト自動生成に巻き込まれてプロジェクトからテンプレートが外されてしまうからです。
テンプレートエンジン用プロジェクトを作成する場合のポイントは「リンクとして追加」です。下図のように追加ボタンの▼の中に隠れています。
リンクとして追加することにより、Unityプロジェクト内で置いてある場所を気にせずに、T4側のプロジェクトに認識させることができます。
CameraDirector
SceneDirector・CanvasDirectorを作成する過程でCameraに関する制御が必要になったのでCameraDirectorを作成しました。Cameraを使って実現したかった機能は以下の2つです。
- Popupの背景にブラーエフェクト(ぼかし)を付ける
- UI上に3Dオブジェクトを表示する
今エントリではCameraを使って実現した機能をメモしておこうと思います(CameraDirectorの機能自体ではないですが)。
Popupの背景にブラーエフェクト(ぼかし)を付ける
UIにブラーエフェクトを取り入れるのにチャレンジしました。アズールレーンがPopup表示時にブラーをかけてるのを見て、スマホゲームでも可能なのだなと思ったので。
ブラーの実現には無料公式AssetのPostProcessingStackを利用することにしました。現時点ではUnity標準には付いていないのでAssetStoreからダウンロードする必要があります。標準バンドル希望。
これのDepthOfFieldを使うとブラーの効果を得られました。
Unity - Manual: Depth of Field
結果はこちら。
ただブラーは重たい処理なのでエフェクトを設定するCamera数は抑えるようにしていきたいところです。現Projectでは最大2つを想定しています(3Dゲーム部分用のCameraとUI用のCamera)。
UI上に3Dオブジェクトを表示する
前回のエントリに記述したとおり、3DオブジェクトはuGUIの制御を受けないので表示の前後関係を制御するにはCameraを使う必要があります。CameraなしだとUI→3Dオブジェクト→UIという表示順序の実現は難しいです。
3Dオブジェクトの上にUIを表示するときは、そのUI用に新しいCameraを作成します。そしてCameraのdepthを、3Dオブジェクトを表示しているCameraのdepthよりも大きくすることで、3Dオブジェクトよりも手前に描画します。