2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

LINQのIQueryableは必ずしもメソッドチェーンにしなくてもOK!

Last updated at Posted at 2023-07-23

前置き:LINQ登場が15年前ってマジですか・・・

こんにちは。LINQ大好きC#erのKoba-yuです。LINQが初登場したのは2008年(15年前!)でもはや新しい技術とは言えず、すでに多くの解説記事があります。今さら記事を書くのもどうなのか、ともちょっと思うところもあります。

一方で、15年も経つと中には改良された文法やライブラリが出て「今はこういう書き方はしないよね」となってくるものもある一方で、LINQはいまだ現役バリバリです。15年前にリリースされたものなのになんてすごい完成度だといまだに本当に感心します。

昨今は新たにエンジニアになられる方も多いので、まだあまりLINQ(特にLINQ to Entities)の活用方法に慣れてない方向けに、基本的な使い方は知っているものとして、ちょっとしたTIPSを絡めながら、深いところを学ぶ入り口になるような記事になればと思います。

目次

LINQはメソッドチェーンを分割して書くこともできる!

お題のコード

LINQPad1付属のサンプルDBの例を元にご説明します。サンプルDBで下記のようなコードを実行してみます。

// ArtistがBlack Label SocietyでTrack名「Like A Bird」があるアルバムだけ取得する
var albums = Albums.Where(a => a.Artist.Name == "Black Label Society")
    .Where(a => a.Tracks.Any(t => t.Name == "Like A Bird"))
    .ToArray().Dump();

※AlbumsはDbSet型です。LINQPadでは接続しているDBのDataContextのDbSetが自分でコードを書いていなくてもアクセスできます

実行結果は下記のようになります。

image.png

条件を指定できる取得メソッドを作ろう!

先述のとおりLINQはもはや古参の技術なので、C#初心者の方でも上記のような使い方を普通にしているよという方も多いのではないかと思います。
さて、あなたが共通ロジック用に条件を指定できる取得メソッドを実装していて、上記の条件を指定したりしなかったりができるメソッドを作りたいと思っているとします。
メソッドチェーンだけで作ろうとすると、下記のようになるかもしれません。

public IEnumerable<Album> GetAlbums(string artistName = "", string trackName = "")
{
	return Albums.Where(a => artistName == "" || a.Artist.Name == artistName)
		.Where(a => a.Tracks.Any(t => trackName == "" || t.Name == trackName))
		.ToArray();
}

これでも動作としてはOKなのですが、artistNameやtrackNameが空で渡されたときに若干微妙なSQLが発行されます。2また、他にも色々条件にする項目が増えてくると、いろいろと煩雑になりがちな実装になったりもします。実は条件があったりなかったりするロジックは、以下のように書くこともできます。

public IEnumerable<Album> GetAlbums(string artistName = "", string trackName = "")
{
	IQueryable<Album> albumQuery = Albums;
	
	// artistNameが指定されている場合の条件追加
	if (!string.IsNullOrEmpty(artistName))
		albumQuery = albumQuery.Where(q => q.Artist.Name == artistName);
	
	// trackNameが指定されている場合の条件追加
	if (!string.IsNullOrEmpty(trackName))
		albumQuery = albumQuery.Where(q => q.Tracks.Any(t => t.Name == trackName));
		
	return albumQuery.ToArray();
}

この書き方をすることで、複数の色々な条件があったりなかったりする場合でも見やすく実装でき、SQLもなるべく簡潔なものが発行されるようにすることができます。
もしこのような書き方ができることを知らなかったよ!という方は試してみていただければと思います。

補足:SQL発行されるタイミングについて

「SQLを発行する!」と明らかな名称のメソッドがLINQにはないので若干分かりにくいところがありますが、上記のように書いてもこのメソッドでSQLが発行されるのは最終行のToArrayを実行したときだけになります。

SQL発行されるメソッドの目安

LINQ to Entitiesでは呼び出したメソッドの戻り値がIQueryable型の間はSQL発行されません。戻り値がIEnumerableの場合はSQL発行されますので注意してください。
というと逐一戻り値を確認するのが大変な印象を持つかもしれませんが(実際問題確認はした方がよいのですが)、目安として「To」で始まるメソッドやFirstOrDefaultのような値を1つしか返さないメソッドは基本的にはその時点でSQLが発行されます。それらのメソッドを実行した以降はLINQ to EntitiesではなくてLINQ to Objectの世界になり、若干話が変わりますので気をつけてください。

LINQは便利ですが、いかんせん戻り値やライブラリ側の挙動の仕様が複雑なので、よくわからないところで負荷が高い動きをしていた!ということがあります。
ご興味を持った方はぜひ公開されている情報を読んでみてください(前述のとおり、すでにたくさん記事があります)

余談
LINQ黎明期は上記のようなコードサンプルもよく見かけた気がしますが、最近はもはや当たり前すぎるのか捻った書き方のサンプルが
あまりない?ような気がしましたので、今さらながら記事にしてみました。お役に立ちましたら幸いです。

おまけ:カジュアルトークを受け付けています!

Musubiteというプラットフォームで、エンジニアのカジュアルトークを受け付けております。

プラットフォーム自体、エンジニアの方にとってとても魅力的なものだと感じていますので、よろしければぜひのぞいてみてください!

  1. LINQPad未導入の方は無償ライセンスでも今回のサンプルは動かせますのでぜびインストールしてみてください!私に限らずこのツールをずっと愛用しているC#erの方はたくさんいらっしゃいます。

  2. LINQPadの実行結果ウィンドウの「SQL」タブで発行SQLを見ることができますので確認してみてください

2
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?