NPocoの紹介 -機能編- ページング

# NPocoの紹介 -機能編- ページング
最初の記事は[こちら](https://qiita.com/epsilon_87_myo/items/1c0f75064b9b6bcc9e1e)

今回はページング機能について紹介します。
(「全115件中11～20件目を表示」とかの機能を実現するためのものです。)

公式ドキュメントだと[こちら](https://github.com/schotime/NPoco/wiki/Query-Paging)に記載されています。

データベースはPostgreSQLを使用しました。

## テーブルの用意

### emp
前回と同じものを流用


## 自動クエリ の場合
生SQLを使わない自動生成クエリの場合、終端操作で`ToPage` メソッドを呼ぶことでページングを実現できます。

```cs
Page<Emp> page = database.Query<Emp>()
                        .Where(x => x.DeptCode == "001")
                        .OrderBy(x => x.EmpId)
                        .ToPage(2, 10);
```

以下のクエリが実行されます。

```
SELECT COUNT(*) FROM "emp" "E"
WHERE ("E"."dept_code" = @p0)
         -> @p0 [String] = "001"


SELECT "E"."emp_id" as "EmpId", "E"."first_name" as "FirstName", "E"."family_name" as "FamilyName", "E"."dept_code" as "DeptCode", "E"."created_at" as "CreatedAt", "E"."updated_at" as "UpdatedAt"
FROM "emp" "E"
WHERE ("E"."dept_code" = @p0)
ORDER BY "EmpId" ASC
LIMIT @p1 OFFSET @p2
         -> @p0 [String] = "001"
         -> @p1 [Int64] = "10"
         -> @p2 [Int64] = "10"
```

全件数取得のクエリと、現在のページ部分のデータを取得するクエリの2つが実行されます。

`ToPage` の第１引数はページ番号(1スタート, 0じゃないよ)
第２引数は1ページあたりの件数です。

上記の引数だと「1ページ当たり10件で2ページ目を取得」なので
`LIMIT 10 OFFSET 10` というクエリが生成されています。

また`OrderBy` で常に同じ並び順になるように注意してください。

### `Page<>` について
ソースは[こちら](https://github.com/schotime/NPoco/blob/d707af2bf59f5c70c1d147bc6974d3cdd52bb05b/src/NPoco/Page.cs)

以下のプロパティが定義されています。

- `CurrentPage` 現在のページ(メソッドの第１引数と同じ)
- `TotalPages` 総ページ数
- `TotalItems` 総件数
- `ItemsPerPage` １ページ当たりの件数(メソッドの第２引数と同じ)
- `Items` 現在のページのレコード

### Limit メソッドについて
`Limit` メソッドを使っても 似たようなことが可能です。

```cs
List<Emp> list = database.Query<Emp>()
                        .Where(x => x.DeptCode == "001")
                        .OrderBy(x => x.EmpId)
                        .Limit(10, 10)
                        .ToList();
```

以下のクエリが実行されます。

```
SELECT "E"."emp_id" as "EmpId", "E"."first_name" as "FirstName", "E"."family_name" as "FamilyName", "E"."dept_code" as "DeptCode", "E"."created_at" as "CreatedAt", "E"."updated_at" as "UpdatedAt"
FROM "emp" "E"
WHERE ("E"."dept_code" = @p0)
ORDER BY "EmpId" ASC
LIMIT @p1 OFFSET @p2
         -> @p0 [String] = "001"
         -> @p1 [Int64] = "10"
         -> @p2 [Int64] = "10"
```

ただしこちらは、第１引数はスキップするレコード数となっており、戻り値も`List<>` となっています。


## 生SQLを記述する場合
生SQLを使う場合`Page` メソッドを呼ぶことでページングを実現できます。

```cs
string sql = @"
SELECT
  emp_id
FROM
  emp
WHERE
  dept_code = @deptCode
ORDER BY
  emp_id
";
var param = new
{
    deptCode = "001"
};

Page<Emp> page = database.Page<Emp>(2, 10, sql, param);
```

以下のクエリが実行されます。

```
SELECT COUNT(*) FROM (SELECT
  emp_id
FROM
  emp
WHERE
  dept_code = @p0
) npoco_tbl
         -> @p0 [String] = "001"

SELECT
  emp_id
FROM
  emp
WHERE
  dept_code = @p0
ORDER BY
  emp_id

LIMIT @p1 OFFSET @p2
         -> @p0 [String] = "001"
         -> @p1 [Int64] = "10"
         -> @p2 [Int64] = "10"
```

クエリ自動生成の場合と同じく全件数取得のクエリと、現在のページ部分のデータを取得するクエリの2つが実行されます。

また同様に `Page` の第１引数はページ番号、第２引数は1ページあたりの件数です。

このページングについては
- [Database.BuildPageQueries](https://github.com/schotime/NPoco/blob/d707af2bf59f5c70c1d147bc6974d3cdd52bb05b/src/NPoco/Database.cs#L865)
- [PagingHelper](https://github.com/schotime/NPoco/blob/d707af2bf59f5c70c1d147bc6974d3cdd52bb05b/src/NPoco/PagingHelper.cs)
- [DatabaseType.BuildPageQuery](https://github.com/schotime/NPoco/blob/d707af2bf59f5c70c1d147bc6974d3cdd52bb05b/src/NPoco/DatabaseType.cs#L147) (データベース別のオーバーライドあり)

によって実現されています。

クエリには必ず`ORDER BY`の記述を忘れないようにしてください。

### Fetch メソッドについて
実は `Fetch`メソッドもオーバーライドされており、ページングのクエリを発行できます。
ただしこちらの戻り値は `List<>` であり、件数取得は行われません。

```cs
string sql = @"
SELECT
  emp_id
FROM
  emp
WHERE
  dept_code = @deptCode
ORDER BY
  emp_id
";
var param = new
{
    deptCode = "001"
};

List<Emp> list = database.Fetch<Emp>(2, 10, sql, param);
```

以下のクエリが実行されます。

```
SELECT
  emp_id
FROM
  emp
WHERE
  dept_code = @p0
ORDER BY
  emp_id

LIMIT @p1 OFFSET @p2
         -> @p0 [String] = "001"
         -> @p1 [Int64] = "10"
         -> @p2 [Int64] = "10"
```

`Page` と同じく第１引数はページ番号です。


### SkipTake メソッドについて
`SkipTake` メソッドでも`Fetch`メソッドと似たようなことは可能です。
ただしこちらの第１引数は「スキップする件数」です。

```cs
string sql = @"
SELECT
  emp_id
FROM
  emp
WHERE
  dept_code = @deptCode
ORDER BY
  emp_id
";
var param = new
{
    deptCode = "001"
};

List<Emp> list = database.SkipTake<Emp>(10, 10, sql, param);
```

以下のクエリが実行されます。

```
SELECT
  emp_id
FROM
  emp
WHERE
  dept_code = @p0
ORDER BY
  emp_id

LIMIT @p1 OFFSET @p2
         -> @p0 [String] = "001"
         -> @p1 [Int64] = "10"
         -> @p2 [Int64] = "10"
```

### 注意点
非常に便利なページングの機能ですが注意点があります。

生SQLを使う場合 `with` 句を使うことができません。
(クエリ加工を正規表現で行っており、対応していない)

なので`with` を使わないクエリを書くか、`with` を使う場合は自前のページングをする、ビューを作成する などの対応が必要となります。