Default Isolation Level

Entity Framework+SQLiteで、IsolationLevelを設定する方法が分かりました。

接続のプロパティ、または設定ファイルを直接編集して、
connectionStringのdata sourceに、default isolation level=ReadCommittedを追加。

やっぱり単純なことだった。

しかし、実際の挙動を見た感じでは、テーブル単位でロックがかかるみたいです。
同じテーブルで読み込みを実行している間は、database is lockedエラーが発生しました。

別のテーブルであれば、保存に成功します。

まあ、それなら今までやってきた次善策も全部が無駄になったわけではない。とはいえ、複雑な気分。
結局のところ、読み込み中の保存は別途キャッシュしないといけないということか。

IsolationLevelとしては、他も一応選べるようだったので、もしかしたらSerializable/ReadComitted以外も使えるものがあるかもしれません。

dotConnect for SQLiteのお試し版を試用中。設定のオプションがSQLite.NETより多く、ReadUncomitted=falseがデフォルトなことから、ReadComittedがデフォルトということだろうか。しかし、SaveChangesでエラーが出て使えない。値段が高いわりにはいろいろと面倒な感じ。

Entity Frameworkを使ったプロジェクトを一通り終えて

しょっちゅう更新しようと考えていたブログでしたが、途中からそんな余裕もなく、
3ヶ月休みなしで出勤しつつ、極めつけは客先(宿泊施設)に3日間滞在しつつようやく終えることができました。

さて、ほとんどの問題は解決することができたものの、いまだ2つだけ心残りがあります。

ひとつは、MVC的な方法を適用したとき、バリデーションがモデル内で完結できない点です。
NOT NULLな属性の入力値がNULLであることの検証は、コントローラにしかできません。
まあ、全部Nullableにすればいいのかもしれませんが。

もうひとつは、トランザクションがフレームワークまかせになっている点です。

LINQ to Entities + SQLiteでの話なので、他のサーバクライアント型のDBを使った場合は問題ないのかもしれません。

SQLiteの場合、IsolationLevelはSerializableかReadCommittedが使えるらしいですが、デフォルトはSerializableになっています。

マルチスレッドアプリケーションで、バックグラウンドで時間のかかる帳票作成などしていると、場合によっては並行して書き込み処理が発生することがあります。
というか、今回のアプリではいろんなところからパケットを受信してはDBに保存するので、読み込みと書き込みがバッティングするのは普通に起こっていました。

その場合に読み込み処理の方が、Serializableでトランザクションを実行していると、SaveChanges()の方がタイムアウト待ちになって、しかも読み込み処理の方も影響を受けてどちらも無意味に遅くなります。

かといって、ReadCommittedを使ってみると、なぜかSaveChanges()メソッドに失敗して変更が保存されません。

結局、並行処理はあきらめて、読み込み処理中には保存処理は全て拒否して、
別途ファイルに保存しておいたイベントデータを暇なときに読み込むという方法をとるしかありませんでした。

それから、これは解決することができたんですが、メソッドチェーンでWhereの条件を追加しながら結果を絞り込む方法を使うときは、クエリの遅延実行が働くようになっているようなんですが、これを下手に使うとループや属性へのアクセスごとに接続が実行されて、非常に遅くなります。

条件が少ない最初の絞込みのときに、ToArray()かToList()などを使って、メモリに展開しておくのがよいようです。
クエリの複雑さにもよりますが、私のLet’s Note Y2 PentiumM1.5GHz+768MBのPCでは30倍以上の差が出ました。

これで、帳票作成のクエリにかかる時間が最大1分程度になったため、書き込み処理をブロックしてもそれほど業務に影響はないかな、と思ってとりあえず妥協しました。ただ、たまたま今回のプロジェクトではそれでよかったですが、読み込み中はぜんぜん書き込めないというのでは問題になることが多いのではないかなあ。

もっと直接的にコマンドを発行する方法もあるようなので、今度はそれで検討してみようか。

時期バージョンのVisual StudioではIronRubyが使えるらしいから、そのときはRailsのActiveRecordが使えるといいなあ。

LINQ to Entities + SQLiteでCAST(expression AS type)

前回のエントリにて、アルファベットと数字の混在する部屋番号というのを扱ったけれど、結局、文字部分を削除しても問題ないということになったので、それはまあ、良くなった。実はいろいろ調べてAlphanumeric sortというのがあると知ったのだが、まあ、いろいろ役にたつことだろう。

さて、それならわざわざ文字列にしなくてもよいだろうということで、INTEGERに変更した。それでずいぶん無駄なロジックやキャストなんかが減って、すっきりした。

しかし、そこに新たな要求が来た。

  1. 部屋番号の範囲を指定して、複数の居住者を検索する。
  2. 部屋番号を文字列とみなして前方一致検索する。

1. の場合、INTEGERなので問題ない。

2. の場合、WHERE句で文字列にキャストしないとLIKEが使えない。クライアントサイドでの絞込みは馬鹿らしい。

正直こんなもん、SQLiteなら文字列も数値も同じだから、LIKEが使えていいんじゃないのかと思うけど、LINQ to Entitiesではそうもいかない。ObjectQuery<T>のWhereは条件に文字列とプレースホルダが使えるから、その線で探してみた。CASTを使えばよいらしい。Googleで調べるのに2時間もかかったよ。はー疲れた。

ObjectQuery<User> userQuery = entities.User.Include("Room")
  .Where("CAST(it.Room.room_no AS System.String) LIKE @keyRoom",
  new ObjectParameter("keyRoom", keyRoom + "%"));

CAST (expr AS type) というのがSQLiteにもあって、それは結構すぐ見つかったけど、typeのところがC#の型になるとは思い浮かばず、TEXTか?VARCHAR(4)とかか?と意味不明なエラーに苦しんでいました。System.Data.SQLiteのせいじゃないかと思って、dotConnect for SQLiteを自腹で買おうか真剣に悩んで見つけたサンプルをCASTでgrepしたら見つかりました。

Download dotConnect for SQLite

LINQ to Entitiesでソート

部屋番号でのソートが必要になった。この部屋番号というやつが曲者で、VARCHAR(4)のカラムになっていて、頭にアルファベットのプレフィックスがつく場合とつかない場合がある。できればこうしたい。

101
102
205
1500
2020
A103
A104
A1501
A2021

普通にソートするとこうはならない。

entities.Room
.OrderBy(r => r.room_no)
.Select(r => r);

101
102
1500
2020
205
A103
A104
A1501
A2021

プレフィックスがないもので考えてみれば、

entities.Room
.OrderBy(r => int.Parse(r.room_no))
.Select(r => r);

とかやりたいところだが、このようにエラーになってしまう。

Where句にはstringで条件を記述できたが、OrderByではできないようなので、SQLiteの関数を直接使うこともできない。

LINQ to Entitiesの問題なのか、SQLite.NETの問題なのかというと、両方の問題な気がする。

LINQ Method cannot be translated into a store expression. | Arnold Matusz's Blog

解決法はToList()を使ってリストに変換した後でOrderByすればクライアントサイドでのソートになるので、上記の問題は発生しないようである。

entities.Room.ToList()
	.OrderBy(r => int.Parse(r.room_no))
	.Select(r => r.room_no);

次期バージョンではできるようになるのだろうか。

ソースポートを固定するには

時間ないのでメモだけ。

int lPort = 60000;
localEP = (EndPoint)(new IPEndPoint(IPAddress.Any, lPort));
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
if (lPort != 0) clientSocket.Bind(localEP);

ソースポート(送信元ポート)は送信時にOSにランダムに割り当ててもらうのが普通だと思うけど、あるハードウェア通信をするときに固定する必要があった。

もし固定しないと、送信したソケットと別にサーバーソケットを用意しなければならなくて面倒なことになる。

Rubyではできたけど、C#についてはずいぶん探すのに苦労した。ソケットについてはあまりよく知らんので、経験則のみに頼ってる。

ローカルエンドポイントのポートを0で指定すると、ランダムソースポートになり、それ以外だと固定ソースポートになるということらしい。

上のソースではUDPの再送を実装したくて、System.Net.Sockets.UdpClientを使わなかった。UdpClientの場合はBindメソッドないので、多分コンストラクタでlocalEP渡せばよいと思う。

LINQ to Entities + SQLite.NET に TransactionScope が・・・・使えない???

こちらの方の資料を参考にしてやってみた。

ADO.NET Entity Framework vNext

ソースコード:

SaveChangesをどうしても越えられない。

自分自身をロックしてしまうとは・・・

やっぱ、SQL Serverしか対応してないのかな。

デリゲートって型推論あるの?

例えばボタンクリックにイベントハンドラを追加するには

btnHello.Click += new EventHandler(btnHello_Click);

などとやるわけだけど、いつの間にか無意識に、こんな風にやってしまっていることに気づいた。

btnHello.Click += btnHello_Click;

あれ、まずいかな?と思ったけど、なんら支障なく動いているように見える。

これってどういう仕組み?型推論?

調べても匿名デリゲートやラムダ式のことは出てくるけど暗黙の型付けについては余り出てこないので結局よく分からない。

これで正しければこの上なく楽なのだが。

 

追記:

分かったわけではないけど、普通みたい。

eventキーワードで宣言した場合に限り、こういうシンタックスシュガーみたいなことができるのだと、今のところは解釈している。

SQLite3にDateTime.Ticksでタイムスタンプを保存したら

ぱっと見ただけじゃ時刻が分からない。ものすごい数字に強い人ならともかくも。

しかし、多分integer(C#ではlong)で保存した方がインデックスが有効になるし、速いと思うので、Ticksで保存したいと思う。

そんな場合は、こうすればよい。

select datetime(created_at/pow(10,7) - 62135888400, 'unixepoch', 'localtime') as d, * from events;

長い。unixepochに変換するためにこうなってしまう。もっと直接的に計算できる項を見つければいいんだろうけど。SQLiteではCREATE FUNCTIONできないようだし。

Visual Studio 2008のエディションごとの機能比較

Microsoft Visual Studio 2008 製品ラインの概要

よく知らないうちはStandardとProfessionalの違いがよく分からなかったんだけど、いろいろな機能を使えるようになってくると、無いと困るものも出てくる。次期バージョンを買う際には参考にしたい。

これから使う予定の機能が多いので、使って公開するかもしれないけれど。

  • Crystal Reports(いるでしょ?)
  • 単体テスト(うーん。使いたいけど。当分使えないかも。)
  • Officeアプリケーション開発(いる?かもなあ。。。)
  • モバイルデバイスサポート(そんのに手出してる余裕ないわ。)
  • SQLデバッグ(そんな機能あったっけ?あるとしてもSQL Serverしか対応してないよね。)

ObjectQueryのStartsWith/Contains/EndsWithはLIKEと違う。

LINQ to Entitiesは落とし穴の宝庫。

DataGridViewで顧客リストの前方一致による絞込みをするため、LIKEを使いたかった。調べてみると、StartsWithでできるらしい。

.Where(u => u.name_kana.StartsWith(tbxKeyNameKana.Text))

実際にやってみると、ASCII文字列の場合は使えるが、マルチバイト文字列の場合は常にどれにもマッチしないという結果になる。

ObjectQuery.ToTraceString()を使ってSQLを見ると、こんなWHERE句になっていた。

WHERE (CHARINDEX(@p__linq__4, [Extent3].[name_kana])) = 1

よくわからんが、素直にLIKEを出してくれ。

SQLに近い書き方もできるようだ。それでこうした。

.Where("it.name_kana LIKE @keyNameKana", new ObjectParameter("keyNameKana", tbxKeyNameKana.Text.Replace("%", @"%")+"%"));

ところで、SQL Serverならばこういう選択肢もあるのかもしれない(未確認)。

.Where(u => u.name_kana.Substring(0, tbxKeyNameKana.Text.Length) == tbxKeyNameKana.Text)

SQLiteではSubstringなんてないと言われてできなかった。

現状のEFには不満がつのる。.NET Framework 4.0では大幅に強化されるというが、VS2008のままで対応できるのかなー。

参考:

Anyway to get at ToTraceString form LINQ to Entities query?