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?

FTPサーバへのログインが遅い

C#でNAS(LinkStation)のFTPにアクセスしまくるソフトを作るのにTKFP.DLLを使ってみることにした。接続が切れたのを検知できないので、毎回ログインする方法を取ったのだけど、ログインするごとに10秒間待たされる。毎回一定時間待たされるので、多分何らかのタイムアウトが発生しているのだろうと考えた。

  1. IPアドレスで指定するとDNS逆引きが発生してタイムアウト。: バグ。ありえないことじゃない。[FIX] WebRequest クラスを使用して Web 要求を行う際の接続の問題
  2. LinkStationはProFTPdを使っていて、こいつのオプションUseReverseDNS off, IdentLookups offが設定されていないのかもしれない。: 不明。分かったところでどうしようもない。多分ログに名前を記録するためにデフォルトではONになっていると思われる。
  3. WindowsXPのファイアウォールのせい。: よくあること。なんかよく分からないネットワークがらみのことはこれを無効にすればたいてい解決。

Wiresharkでディスプレイフィルタ「tcp.port eq 21 or ftp」かけて見ていると、「220 server ready」が返って来るのが10秒かかっていた。「ftp 220 server ready slow」でぐぐってみると、てがかりとなる議論が。

ftp login very slow [Archive] - HowtoForge Forums | HowtoForge - Linux Howtos and Tutorials

どうやらファイアウォールに引っかかっているためのようだ。ためしにファイアウォールを無効にすると待たされなくなった。しかし、無効にしとくわけにもいかないので、開くポートを限定したい。Wiresharkのフィルタを外してよく見てみると、ident 113ポートへのアクセスが3回記録されていた。どうやらこれのようだ。

「ftp 113」でぐぐってみると答えにたどり着いた。

LunaTear: XP SP2でFTPが遅くなる

参考:

DisplayFilters - The Wireshark Wiki

Windows Serviceアプリケーションの開発

最初にC#に触れてから1年近くが経とうとしているが、3ヶ月ほど前から本格的なシステムの開発を始めた。そういうわけでいろいろなところでつまづきまくっている。今回はWindowsサービスアプリケーションの開発。何とか道が見えたところで、記憶にとどめておくべきことをメモしておきたい。

クリティカルな部分をサービスに切り出して、必要なときだけGUIを立ち上げればよいような構造にしている。ゼロからの開発になるので、サービス・GUI・クラスライブラリを全部1つのソリューションの中で管理している。

サービスアプリ自体の開発はいったんやり方を見つければ簡単なんだけど、そこに行き着くまでが大変だった。ウェブ上にも古いやり方しか載っていない。installutil.exeとかコマンドラインツールを使う方法は古い。普通のアプリと同じ方法でセットアッププロジェクトを作ったあと、カスタム動作を設定するだけだ。このカスタム動作の設定の仕方は次のとおり。

1. ソリューションエクスプローラから、セットアッププロジェクトを右クリック->表示->カスタム動作

2. 開いた画面で、カスタム動作アイコンを右クリック->カスタム動作の追加

3. 開いたダイアログで、アプリケーションフォルダをダブルクリック。
 

4. アプリケーションのプライマリ出力を選択してOK。

5. 2の画面の各アクションにプライマリ出力が追加される。各アクション(イベント)のコールバックはProjectInstaller.csに自動的に実装される。
 

私は、これにインストール時に起動、アンインストール時に停止するアクションを加えた。ProjectInstallerのデザイナのイベントプロパティから、AfterInstall, BeforeUninstallイベントハンドラを追加する。ソース全体は以下のようになった。

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration.Install;
using System.Linq;
using System.ServiceProcess;
using System.Windows.Forms;


namespace SignalService
{
    [RunInstaller(true)]
    public partial class ProjectInstaller : Installer
    {
        public ProjectInstaller()
        {
            InitializeComponent();
        }

        public override void Install(IDictionary stateSaver)
        {
            base.Install(stateSaver);
        }

        public override void Commit(IDictionary savedState)
        {
            base.Commit(savedState);
        }

        public override void Rollback(IDictionary savedState)
        {
            base.Rollback(savedState);
        }

        public override void Uninstall(IDictionary savedState)
        {
            base.Uninstall(savedState);
        }

        private void ProjectInstaller_AfterInstall(object sender, InstallEventArgs e)
        {
            ServiceController controller = new ServiceController(serviceInstaller1.ServiceName);
            controller.Start();
        }

        private void ProjectInstaller_BeforeUninstall(object sender, InstallEventArgs e)
        {
            ServiceController controller = new ServiceController(serviceInstaller1.ServiceName);
            try
            {
                controller.Stop();
            }
            catch (Exception)
            {
            }
        }
    }
}

過去にうちの先輩がC++で開発した、リモートデバッグ機能付きのロギングDLLを使おうとしたのだが、実はサービスアプリケーションで使うのは初めてだった。DllImportして使うわけだが、サービスが起動しているときに、GUIからもこのDLLを呼ぶと、System.DllNotFoundExceptionが発生する。サービスでなければ2つのアプリケーションから同時に使うことは問題なかったので、多分ユーザ権限の問題なのだろう。サービスはLocalSystemで動作させていた。このDLL特有の問題なのかもしれない。

また、ログファイルの出力先をカレントディレクトリのlogフォルダ(“.log”)としていたのだが、なぜかファイルが書かれない。絶対パスを指定するとちゃんと書かれるので、Program Filesに書くには特別な権限が必要なんだろうかと想像したりもしたが、デバッガをアタッチしてみると、実は単にカレントディレクトリが C:WINDOWSsystem32 になっていることが分かった。そこには普通にログが書き出されていた。以下のようにカレントディレクトリを移動することで解決した。

        private void MoveCurrentDirectory()
        {
            Assembly exe = Assembly.GetEntryAssembly();
            FileInfo exeFileInfo = new FileInfo(exe.Location);
            Directory.SetCurrentDirectory(exeFileInfo.DirectoryName);
        }

結局このDLLはサービスで使うと他で使えないようだったので、サービス用のロガーはC#で書いた。たいした機能はないが、キューイングして別スレッドで遅延書き込みみたいなことを実装した。リモートデバッグはとりあえず、Debug.Printで我慢する。

SQL Server 2008 Express Editionのインストールではまる

VS2008についてきたSQL Server 2005 Expressからアップデートした。しかし、Management Studioは2005のままだったので、なんとなく気持ち悪くてこいつも2008にしようとしたところ、なぜかはまった。

それは、インストールルールのチェックで、Visual Studio 2008 SP1を要求されるという問題だった。すでにSP1はインストールされているんだけど・・・。

これがなかなかクリアできず、結局SQL Server 2008ごとアンインストールしてインストールしなおした。

しかし、同じ問題が出る。異様に時間のかかるインストール・アンインストールに発狂しそうになりながら、待っている間いろいろ調べていると、ようやく答えにたどり着いた。

CSS SQL Server Engineers  SQL Server 2008, Visual Studio 2008 SP1, and .Net Framework 3.5 SP1 explained....

結局、以前F#のインタラクティブシェルを使ってみたくて入れた、Visual Studio 2008 Shell (integrated mode) というやつが原因だった。これをアンインストールするとすんなり進んだ。

なお、僕の環境では入っていなかったが、Visual Studio Tools for Applications 2.0というやつも削除する必要があるらしい。こちらは再頒布してる場所が見つからなかった。どうやって戻すのかは不明。