SQLAlchemyのEager Loadingをいい感じにやる
SQLAlchemy==1.4.37
N+1のlazy fetch
をしたくない場合、eager load
というJOIN時に予めデータを持ってくる方式があります。SQLAlchemyでも、Railsでも、この辺は一緒。Alchemyの場合、孫のテーブルをJOINした時もそれらが出来る。
数時間苦戦したので、Eager Loadingの設定をメモる。
filterする対象のテーブルが自分だけ
この場合は、joinedload
でいけた。
session.query(UserTable) .options( joinedload(UserTable.attention), joinedload(UserTable.friend_type), joinedload(UserTable.bookmark), ) .filter(UserTable.user_id == user_id)
UserTableを軸に、Where句で引いてくるのがUserTableのカラムのみ。
filterする対象のテーブルが複数ある
この場合は、以下のようなjoinedload
を使うと、えらいことになった。
session.query(UserTable) .options( joinedload(UserTable.attention), joinedload(UserTable.friend_type), joinedload(UserTable.bookmark), ) .filter(UserTable.user_id != user_id) .filter(UserFriendTypeTable.friend_type.in_(["AAA","BBB"])
えらいことになるというのは、filterで指定したテーブルが OUTER JOINではなくCROSS JOINされていたため。FROM user_table, user_friend_type_table というSQLになるので、激重になった。
複雑な検索条件に基づいてフェッチする場合は、join
とcontains_eager
の合わせ技が良いみたい。
session.query(UserTable) .outerjoin(UserBookmarkTable, UserBookmarkTable.user_id == UserTable.id) .join(UserFriendTypeTable, UserFriendTypeTable.user_id == UserTable.id) .options( contains_eager(UserTable.bookmark), contains_eager(UserTable.friend_type), ) .filter(UserTable.user_id != user_id) .filter(UserFriendTypeTable.friend_type.in_(["AAA","BBB"])
自分はいつもwith_entities
でカラムを指定してSELECT文を投げることが多かった。一覧画面に表示するためのSQLが多いので、JOIN先がN件でも親に合わせるためにカラムを指定してた。ただ、今回は、要件が特殊でカラムを指定するのではなく、N+1せずに結合先のレコードをリストで保持して置く必要があったので、苦戦したのだった。
これでだいぶRelationshipには慣れてきたな。ドキュメント、じっくり読もう。どこかで。
Asynchronous Gap on BuildContext
Flutter2.x系からこの問題がフィーチャーされるようになった気がする。1.xの時はそこまで。
どんな問題?
- 非同期でサーバーにデータを送った後、ダイアログを閉じるようなケース。
- 非同期で
await
を入れた所で、Flutterのウィジェットツリーの再構築が同期されたかは、全くもって保証されない。 - そのため、非同期処理後に
Navigator.of(context).pop()
とかやると例外が発生する。
TextButton( child: const Text("いいえ(エラーになる)"), onPressed: () async { await doSomething(); Navigator.pop(context); //このcontextが同期されない } )
At this point the state of the widget's element tree is no longer stable. To safely refer to a widget's ancestor in its dispose() method, save a reference to the ancestor by calling dependOnInheritedWidgetOfExactType() in the widget's didChangeDependencies() method
どーするか
公式の推奨は、StatefulWidget
でWidgetのライフサイクルを管理する方式。
void onButtonTapped() async { await Future.delayed(const Duration(seconds: 1)); if (!mounted) return; Navigator.of(context).pop(); }
StatefulWidgetを使わない場合、await
する前の Context
をキャッシュすると良いみたいなStackOverflowがあったけど、そもそもキャッシュするもんじゃないだろこれっていう思いが強い。
onPressed: () async { final navigator = Navigator.of(context); // store the Navigator await showDialog( context: context, builder: (_) => AlertDialog( title: Text('Dialog Title'), ), ); navigator.pop(); // use the Navigator, not the BuildContext },
WPFでコンボボックスの選択値が共有できない
印刷プリンタのコンボボックスを作っていて、その選択値を様々な画面で使い回したいのだが、非常にハマった。今も解決できない。
ViewModelBase
というクラスを作り、そのプロパティを継承したら行けるかと思ったが、ダメだった。ViewModelBaseのコンストラクタのログを見ると、親クラスの値がそのままセットされているけど、画面が表示されるタイミングでSetProperty
を叩かないと無駄っぽい。
まぁそれはいいやで、A画面でプリンタを選び直して、B画面を表示しても、A画面で選択されたプリンタの選択値の通り画面が切り替わらない。アプリケーション全体にまたがってデータをバインドするっていうことが、WPF/Prismでは出来ないのだろうか。解決の方向性が全く見えない。
・・・これ、どうしたらいいんだろうなぁ。
SQLAlchemy2.0スタイルメモ〜scalars〜
FastAPIならasync
で全部行きたいねってことで、プロダクトのコードをFlask→FastAPIに書き直している。
で、簡単なクエリの動作確認で、こんなコードを書く。
async def get_posts(db: AsyncSession) -> List[Post]: result: Result = await db.execute(select(Post).order_by(Post.postdate.desc()).limit(20)) return result.all()
この時、JSONになった文字列を見ると、こういう感じで返ってくる。
{ "Post": { "id": 1, "title": "aaa", } }
なんだこのPost
とかいうエンティティ名は。そんなのいらん。何だこれって思って公式見たら、こうやって書くらしい。
async def get_posts(db: AsyncSession) -> List[Post]: result: Result = await db.execute(select(Post).order_by(Post.postdate.desc()).limit(20)) return result.scalars().all()
こうすると、JSONにした時にエンティティ名のPost
が外されて、{"id":1,"title":"aaa"}
になった。select
関数の中で、配列でカラム名を指定する場合にscalars
を使うと、選んだカラムの1つしか返ってこない。まぁ、スカラー値だもんな。オブジェクトの場合はオブジェクトが返り、Selectした場合は最初の値が返ってくる・・・ってなんだこれ。でも、Alchemy使う以上は従うしか無いし。TypeScriptオンリーに早くなりたい。
EPSON VP-4300で「EJL 1284.4@EJL」という文字列が常に印字される
USBでドットインパクトプリンタとWindowsマシンがつながっている状態で、プリンタの共有をかけた。この共有プリンタをインストールしたクライアント側で、必ず EJL1284〜 とかいう文字列が出るようになってしまった。
ぐぐってみたら先駆者がいた。 okwave.jp
以下のような設定画面になるので、パケット通信設定をオフにしたら、この文字列は出なくなった。この文字列はプリンタ制御コードで、何のエラーかわからんが、通信時に発生し、その内容を人間様に教えるために印字されていたようだ。
EPSON的には仮想USBポートでプリンタ共有かけるのではなく、ローカルポートで ¥¥HogePrintServer¥EPSON-ESCP
とかやるのが推奨手順らしい。
以下、マニュアル。 https://www2.epson.jp/support/manual/data/dot/vp1900/BPS0141_00_JA.PDF
OKIのMICROLINEで同じ設定をした時は、このような現象は発生しなかった。や沖1。
FlutterでUnixTimeをDateTimeに変換する
Firebaseのライブラリが国際化対応しているため、日時をUnixtimeで持っていた。 それをDartでDateTimeに変換するときのコードで、癖があったのでメモ。
final timestamp1 = 1627510285; // timestamp in seconds final DateTime date1 = DateTime.fromMillisecondsSinceEpoch(timestamp1 * 1000);
fromMillisecondsSinceEpoch はミリ秒を期待しているが、unixtimeが秒までの時間しか持っていないので、1000倍しないとミリ秒が計算されず復元できないというだけの話。
'Timestamp isn't a type' により、flutter buildが通らない
Firestoreのタイムスタンプ型をDartのDateTimeに変換する時に、1時間ほどハマってしまったので。
freezed
を使っている人は多いと思いますが、DateTime⇔Timestampを変換するには、以下のようなコンバーターを噛ます必要があります。
class TimestampConverter implements JsonConverter<DateTime, Timestamp> { const TimestampConverter(); @override DateTime fromJson(Timestamp json) => json.toDate(); @override Timestamp toJson(DateTime object) => Timestamp.fromDate(object); }
flutter run
しようとすると、「Timestamp isn't a type」でコケてしまい、Dartのエラーが出ました。型を解決できないって話らしい。xxx.g.dart
でおなじみ、json_serializable
が自動生成したコードで、それが出た。
結論から言えば、xxx.g.dart
の元のクラスで、cloud_firestore.dart
をインポートする必要があった。
import 'package:cloud_firestore/cloud_firestore.dart'; //これがないと型解決できない part 'hoge.freezed.dart'; part 'hoge.g.dart'; @freezed class Hoge with _$Hoge { const factory Hoge({ @TimestampConverter() required DateTime joined, }) = _Hoge; factory Hoge.fromJson(Map<String, dynamic> json) => _$HogeFromJson(json); }
part
で分割されたコードにおいて、解決できない型があっても、Dartがコンパイルエラーにならなかった。そういうもんなのかな。dart:core
にない型なので参照が必要なのはそれはそう、って感じ。