Pandora Pocket

IT系と日常系の備忘録。三日坊主。

ListからSQL検索条件を生成するLINQ

.NET Core 3.1対応をしているのですが、それに伴ってEntity Framework Coreもv3にあげたところ、LINQのクライアントサイド評価が行われなくなったことでいくつかのクエリが使えなくなったため、いろいろ調べながら対応しています。

対応が必要だったうちの一件が下記のようなもの。

class TBL {
string Data1;
string Data2;
}

このようなクラスを保持するリスト(List)に対して、

.Where(x => list.Any(l => l.Data1 == x.Data1 && l.Data2 = x.Data2))

のように、含まれている値と一致するレコードだけを取得するというものです。
SQLにしてみれば

WHERE (TBL.Data1 == [listの値1-1] && TBL.Data2 == [listの値1-2])
OR (TBL.Data1 == [listの値2-1] && TBL.Data2 == [listの値2-2])
・・・

みたいな感じ。
LINQ to SQLだとこのクエリ、SQLに変換してサーバーサイドで実行してくれないんですね。
式木をうまいことこねこねして動的に.Where内の値を生成するようにしようと思ったのですが、今の力量だとちょっと手間取りそうで、それに時間をかけてられないこともあり、とりあえずLINQを使わずFromSqlRawで直接SQLを実行することにしました。
SQLで実行するにしてもWHERE句に指定する条件はリストから動的に生成する必要があります。

そんな時に便利なのが下記のLINQ

var param = list
.Aggregate(
string.Empty,
(current, tbl) => current + $" OR ([Data1] = '{tbl.Data1}' AND [Data2] = {tbl.Data2}) ");

これならlistに格納されている値分のOR条件式が一つの文字列に吐き出されるので、あとは

var sql = $@"SELECT * FROM TBL
WHERE (1=0)
{param}";

こんな感じでSQLにぶち込んで、EFなりDapperなりに渡してやればOK。

SQLのWHERE 1=1と WHERE 1=0について

たまに使うのにどう使うのかを度忘れすることがあるので備忘録。

前提

テーブルから値を取得する際の条件としてWHERE句を設定するが、検索対象によってWHERE句に指定する検索条件が増減しうる。

例えば生徒の情報(氏名、性別等)を保持するテーブル STUDENT があったとして、全件取得したければ条件を付ける必要がないので

SELECT * FROM STUDENT

で済む。
これに対し氏名の姓だけを検索条件に加えるなら

SELECT * FROM STUDENT WHERE 氏名 LIKE '(姓)%'

となる。
プログラムでデータを取得する場合、条件の組み合わせごとにSQLをそれぞれ用意するような非効率なことはしたくないので、WHERE 以降の検索条件を動的につなげたい。
SQLは検索条件がない場合は WHERE があるとエラーになるため、検索条件がある場合と検索条件がない場合で条件分岐が必要になる。
この時に便利なのが(1=1)、(1=0)となる。

WHERE (1=1)

1=1は当然正しいのでtrue。

SELECT * FROM STUDENT WHERE (1=1)

この場合、(1=1)が検索条件となり、必ずtrueであるため、STUDENTテーブル上のすべてのレコードを取得することになる。

検索条件が存在する場合、

SELECT * FROM STUDENT WHERE (1=1)
AND 氏名 LIKE '(姓)%'

のように(1=1)以降にANDで条件を追加していけば良い。

なおMySQLではWHERE 1 でも同じことになる模様。

WHERE (1=0)

(1=1)の場合、以降に検索条件がなければ全件取得されるが、条件がない場合は一件も値を取得してほしくない場合もある。
その場合は(1=0)を用いる。

SELECT * FROM STUDENT WHERE (1=0)

この場合、(1=0)はfalseであるため、STUDENTテーブル上のレコードを1件も取得しない。

SELECT * FROM STUDENT WHERE (1=0)
OR 氏名 LIKE '(姓)%'

のように、OR (検索条件) として条件を付与することで、検索条件に適合するレコードがあればそれだけを取得する。

なお、bool値としてfalseになればいいわけなので、別に(2=0)でもなんでもよい。MySQLなら0でもOK。が、ややこしいので(1=0)にするのが無難。

Postmanを使い始めて躓いた点の備忘録

以前使っていたAPIをたたくツールが公開終了してしまい、最近はPowerShellで下記のように叩いていたんですが、

$body = @{hoge="fuga"; foo="bart"} | ConvertTo-Json -Compress
Invoke-RestMethod -Uri "https://localhost:5001/api/RESTfulApi" -Method POST -Body $body -ContentType application/json

やっぱり専用のクライアントを使ったほうが楽だなと思い直してPostmanを使い始めました。

乗り換えた際に躓いた点の備忘録。

続きを読む

.NET CoreでLinux環境を用いる場合、ファイルパス生成はPath.Combineを使うべき

タイトルが結論。

Linux環境で動く.NET Coreアプリを書いていた時、ファイル出力処理でファイルパスを指定する際、下記のようにとりあえず記載してました。

var fileName = AppDomain.CurrentDomain.BaseDirectory + @"\FolderName\fileName";

想定としてはプログラムがあるフォルダにFolderNameというフォルダがあり、その中にfileNameというファイルが生成されるというもの。
Windows環境ではこれで問題なく動くものの、Linux環境に持っていくとファイルが出力されていなかったので確認してみると、プログラムがあるフォルダに FolderName\fileName という名前のファイルが。

Windows環境ではパスにバックスラッシュを使うのに対し、Linux環境ではスラッシュを使うため、上記の指定だとうまくいかないと。なるほど。

というわけで、Linux環境で動かす場合はスラッシュに置き換えればいいんですが、Windows環境でも動かすことを想定すると、Path.Combineを利用してやるのが最適解のようです。

こんな感じ。

var fileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FolderName", "fileName");

そもそもパスの指定方法が適当ってのは気にしないで・・・。

参考