院長のメモ帖
2015年5月31日 日曜日
LINQのJoinメソッド
最近、データーベースに集まった数値データーの解析にはまっています。なんのデーターかはあれですから秘密ですが、平均とったり、順位付けしたり、ある条件下のデーターと違う条件下のデータを比較したり、偏差値換算したり、相関関係とったり、ソートしたりと、まああらゆることをして解析しております。
私はSQL文は触りしか理解してないので、これらは全部C#からLINQ To SQL経由で処理してます。Viewやらストアドプロシージャーをうまく使えばもっと幅が広がるのでしょうが、日曜プログラマーにはいくつもの言語に精通する時間と余裕がないので、せめてC#の変化にだけはついていこうと思っています。
それで本題なのですが、一対他のリレーションシップがあるEntity AとEntity Bのデーターがあるとします。
class A
{
int ID;
string Data;
EntitySet<B> ListOfB;
}
class B
{
int ID:
int A_ID;
A EntityA;
string Data;
}
Aの集合からそれらの各要素と結びついているすべてのBの集合を作ろうとする場合、どうすればよいか実はわかっていませんでした。力技で
IQuerable<A> AList = db.A.Where(r=> 条件式);
List<B> BList = new List<B>();
foreach (A a in AList)
BList.AddRage(a.ListOfB)
なんてことしてました。これ、AListがせっかくIQueryableなのに、BはLinqToObjectに成り下がってしまっています。Concatすれば一応IQueryableのままですが、コードがややこやしいし、多分かなり非効率なクエリーになってしまうので、AListもToListしちゃった方が速いでしょうね。
で、もしかしていつも無視しているメソッドになんかあるんじゃないかと思って調べてみたらまさしく、Joinを使えばよかったんですね。愛読している究極のC#プログラミングとかC#プログラムの効率的な書き方といった川俣晶さんの本を読み返したらちゃんと説明してあるんだもんな...
言い訳ですが、これらを読んでLINQガンガンに使い始めたんですが、何故かこのころJoinを使わなくてはいけないケースが存在しなくて頭の中に入らなかったようです...
で、Joinをつかえば
IQuerable<A> AList = db.A.Where(r=> 条件式);
IQuerable<B> BList = AList.Join(db.B, x=>x, y=>y.EntityA, (x,y)=> y);
でめでたしめでたしかと思いきや、このクエリーはリレーションしているエンティティそのもので比較していますが、試しに二行目を外部キーで比較したら、
IQuerable<B> BList = AList.Join(db.B, x=>x.ID, y=>y.A_ID, (x,y)=> y);
めちゃくちゃ早くなりました。こういうのってSQL Server をわかってない人間には盲点ですね。型の一致している者同士で引数渡すのが当たり前なC#プログラマーってつい前者の書き方のほうがバグが起こりにくいし参照の比較ならそれほど非効率じゃないじゃんって思いますが、SQL Server の内部ではおそらくそういうリンクの仕方しておらず、外部キー/主キーで比較するのが当たり前の世界なんでしょうね。
まあ、おかけで1件出力するのに1秒以上かかっていたクエリが0.5秒で済むようになりました。今まで書いたプログラムの中にも最初の例のような非効率な書き方をしているところがありそうなので、見直さなきゃならないでしょうね。
私はSQL文は触りしか理解してないので、これらは全部C#からLINQ To SQL経由で処理してます。Viewやらストアドプロシージャーをうまく使えばもっと幅が広がるのでしょうが、日曜プログラマーにはいくつもの言語に精通する時間と余裕がないので、せめてC#の変化にだけはついていこうと思っています。
それで本題なのですが、一対他のリレーションシップがあるEntity AとEntity Bのデーターがあるとします。
class A
{
int ID;
string Data;
EntitySet<B> ListOfB;
}
class B
{
int ID:
int A_ID;
A EntityA;
string Data;
}
Aの集合からそれらの各要素と結びついているすべてのBの集合を作ろうとする場合、どうすればよいか実はわかっていませんでした。力技で
IQuerable<A> AList = db.A.Where(r=> 条件式);
List<B> BList = new List<B>();
foreach (A a in AList)
BList.AddRage(a.ListOfB)
なんてことしてました。これ、AListがせっかくIQueryableなのに、BはLinqToObjectに成り下がってしまっています。Concatすれば一応IQueryableのままですが、コードがややこやしいし、多分かなり非効率なクエリーになってしまうので、AListもToListしちゃった方が速いでしょうね。
で、もしかしていつも無視しているメソッドになんかあるんじゃないかと思って調べてみたらまさしく、Joinを使えばよかったんですね。愛読している究極のC#プログラミングとかC#プログラムの効率的な書き方といった川俣晶さんの本を読み返したらちゃんと説明してあるんだもんな...
言い訳ですが、これらを読んでLINQガンガンに使い始めたんですが、何故かこのころJoinを使わなくてはいけないケースが存在しなくて頭の中に入らなかったようです...
で、Joinをつかえば
IQuerable<A> AList = db.A.Where(r=> 条件式);
IQuerable<B> BList = AList.Join(db.B, x=>x, y=>y.EntityA, (x,y)=> y);
でめでたしめでたしかと思いきや、このクエリーはリレーションしているエンティティそのもので比較していますが、試しに二行目を外部キーで比較したら、
IQuerable<B> BList = AList.Join(db.B, x=>x.ID, y=>y.A_ID, (x,y)=> y);
めちゃくちゃ早くなりました。こういうのってSQL Server をわかってない人間には盲点ですね。型の一致している者同士で引数渡すのが当たり前なC#プログラマーってつい前者の書き方のほうがバグが起こりにくいし参照の比較ならそれほど非効率じゃないじゃんって思いますが、SQL Server の内部ではおそらくそういうリンクの仕方しておらず、外部キー/主キーで比較するのが当たり前の世界なんでしょうね。
まあ、おかけで1件出力するのに1秒以上かかっていたクエリが0.5秒で済むようになりました。今まで書いたプログラムの中にも最初の例のような非効率な書き方をしているところがありそうなので、見直さなきゃならないでしょうね。