.NET Core でジェネリックリポジトリ作ってみた！

# はじめに
ジェネリックリポジトリに興味が湧いたので、なんとなく作ってみようと思いました。

# やってみる
## 前提
今回は以下の環境で行いました。
- Windows11
- .NET Core 5.0
- SQLite

EFCoreを使用するので、事前に`Microsoft.EntityFrameworkCore`をプロジェクトに追加しておきます。

## DbContext
今回は`Employee`テーブルと`Department`テーブルを作成しました。
```EmployeeDbContext.cs
using Microsoft.EntityFrameworkCore;
using TodoApp.Type;

namespace TodoApp.Models
{
    public class EmployeeDbContext : DbContext
    {

        public EmployeeDbContext()
        {

        }
        public EmployeeDbContext(DbContextOptions<EmployeeDbContext> options):base(options)
        {

        }

        public DbSet<Employee> Employees { get; set; } = null;

        public DbSet<Department> Departments { get; set; } = null;
    }
}

```

```cs:Employee.cs
namespace TodoApp.Type
{
    public class Employee
    {
        /// <summary>
        /// 従業員ID
        /// </summary>
        public int Id { get; set; }

        /// <summary>
        /// 氏名
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 部署ID
        /// </summary>
        public int DepartmentId { get; set; }
    }
}
```

```cs:Department.cs
namespace TodoApp.Type
{
    public class Department
    {
        /// <summary>
        /// 部署ID
        /// </summary>
        public int Id { get; set; }

        /// <summary>
        /// 部署名
        /// </summary>
        public string DepartmentName { get; set; }
    }
}
```

## GenericRepository
本題のジェネリックリポジトリは、<a href="https://dotnettutorials.net/lesson/generic-repository-pattern-csharp-mvc/">こちら</a>を参考に以下のコードで実装しました。
ジェネリック型で定義することで、テーブルの型を引数としてとることができます。

今回は「作成」、「全件取得」、「IDで取得」、「削除」のメソッドをジェネリックリポジトリとして実装しました。また`SaveChangesAsync`を行うためのメソッドも実装しています。

```cs:IGenericRepository.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace TodoApp.Repositories
{
    /// <summary>
    /// genericリポジトリのインターフェース
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface IGenericRepository<T> where T : class
    {
        /// <summary>
        /// 全件取得
        /// </summary>
        /// <returns></returns>
        Task<List<T>> GetAll();

        /// <summary>
        /// IDで取得
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        Task<T> GetById(object id);

        /// <summary>
        /// 登録
        /// </summary>
        /// <param name="obj"></param>
        void Post(T obj);

        /// <summary>
        /// 削除
        /// </summary>
        /// <param name="id"></param>
        void Delete(object id);

        /// <summary>
        /// 保存
        /// </summary>
        /// <returns></returns>
        Task Save();
    }
}
```

```cs:GenericRepository.cs
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using TodoApp.Models;

namespace TodoApp.Repositories
{
    public class GenericRepository<T> : IGenericRepository<T> where T : class
    {
        public EmployeeDbContext _employeeDbContext = null;
        public DbSet<T> _table = null;

        public GenericRepository(EmployeeDbContext employeeDbContext)
        {
            _employeeDbContext = employeeDbContext;
            _table = _employeeDbContext.Set<T>();
        }

        /// <summary>
        /// 全件取得
        /// </summary>
        /// <returns></returns>
        public async Task<List<T>> GetAll()
        {
            var res = await _table.ToListAsync();
            return res;
        }

        /// <summary>
        /// IDで取得
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public async Task<T> GetById(object id)
        {
            var res = await _table.FindAsync(id);
            return res;
        }

        /// <summary>
        /// 登録
        /// </summary>
        /// <param name="obj"></param>
        public void Post(T obj)
        {
            _table.Add(obj);
        }

        /// <summary>
        /// 削除
        /// </summary>
        /// <param name="id"></param>
        public void Delete(object id)
        {
            T exist = _table.Find(id);
            _table.Remove(exist);
        }

        /// <summary>
        /// 保存
        /// </summary>
        /// <returns></returns>
        public async Task Save()
        {
            await _employeeDbContext.SaveChangesAsync();
        }
    }
}
```

## リポジトリを使用する
実装したリポジトリを使用するには以下のようにします。

```cs:DepartmentService.cs
namespace TodoApp.Services
{
    public class DepartmentService : IDepartmentService
    {
        private IGenericRepository<Department> _repository;
        public DepartmentService(IGenericRepository<Department> repository)
        {
            _repository = repository;
        }

        /// <summary>
        /// 部署を作成
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public async Task<int> Create(string name)
        {
            var req = new Department
            {
                DepartmentName = name
            };
            _repository.Post(req);
            await _repository.Save();
            return req.Id;
        }
// ...
```

`Startup.cs`には以下のような記述を追加します。
```cs:Startup.cs
// ...中略

 public void ConfigureServices(IServiceCollection services)
 {
    // ConfigureServices内に以下のように追加する
    services.AddScoped<IGenericRepository<Department>, GenericRepository<Department>>();
    services.AddDbContext<EmployeeDbContext>(opt => opt.UseSqlite(".dbファイルのパス"));
// ...
```

## マイグレーションする
実際にエンドポイント化する前に、マイグレーションを行わないとDBにテーブルが作成されていないので、以下のコマンドで先にマイグレーションを行います。

```powershell
Add-Migration "マイグレーション名"
Update-Database "（マイグレーション名）"
```
updateでDBに反映する際、マイグレーション名を指定しなくても自動的に最新のマイグレーションファイルを読み込んでくれます。

あとはコントローラを作成し、ビルドすればジェネリックリポジトリとして機能するはずです！

# テーブル固有のリポジトリを実装したい、、こんなときどうする？
リポジトリを実装する中で、テーブル固有の操作（名前が一致するものを取得するetc...）を行いたくなる時があると思います。
そんな時は、***ジェネリックリポジトリを継承したリポジトリ***を作成すれば解決です！
（<a href="https://dotnettutorials.net/lesson/repository-pattern-implementation-guidelines-csharp/">参考</a>）

今回は例として、IDが一致するレコードを更新するメソッドを実装します。
```cs:EmployeeRepository.cs
using TodoApp.Models;
using TodoApp.Type;

namespace TodoApp.Repositories
{
    public class EmployeeRepository : GenericRepository<Employee>,IEmployeeRepository
    {
        public EmployeeRepository(EmployeeDbContext employeeDbContext):base(employeeDbContext)
        {
            _employeeDbContext = employeeDbContext;
        }
        public void Put(Employee req)
        {
            var employee = _employeeDbContext.Employees.Find(req.Id);
            employee.Name = req.Name;
            employee.DepartmentId = req.DepartmentId;
        }   
    }
}
```

操作を行うクラス側には、ジェネリックリポジトリと独自のインターフェースの両方を継承させます。

リポジトリの呼び出し方は先程とほとんど同じで、IGenericRepositoryを独自に実装したインターフェースに変えるだけです！
```cs:EmployeeService.cs
namespace TodoApp.Services
{
    public class EmployeeService : IEmployeeService
    {
        private IEmployeeRepository _repository;
        private IGenericRepository<Department> _depRepository;
        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="repository"></param>
        public EmployeeService(IEmployeeRepository repository,IGenericRepository<Department> depRepository)
        {
            _repository = repository;
            _depRepository = depRepository;
        }

        /// <summary>
        /// ユーザを作成する
        /// </summary>
        /// <param name="req"></param>
        /// <returns></returns>
        public async Task<int> Create(Employee req)
        {
            var dep = await _depRepository.GetById(req.DepartmentId);
            if(dep == null)
            {
                throw new Exception("存在しない部署です");
            }
            _repository.Post(req);
            await _repository.Save();
            return req.Id;
        }

        /// <summary>
        /// 従業員情報を更新する
        /// </summary>
        /// <param name="req"></param>
        /// <returns></returns>
        public async Task<Employee> Update(Employee req)
        {
            _repository.Put(req);
            await _repository.Save();
            return req;

        }


```

ジェネリックリポジトリで作成したPostも、独自に実装したPutも使えるようになっています！

`Startup.cs`には以下のコードを追加します。

```cs:Startup.cs
services.AddScoped<IGenericRepository<Department>, GenericRepository<Department>>();
// 先程のコードに以下を追加する
services.AddScoped<IEmployeeRepository,EmployeeRepository>();
```

# おわりに
意外とすんなりできたのでうれしかったです:kissing_closed_eyes:

# 参考
- <a href="https://dotnettutorials.net/lesson/generic-repository-pattern-csharp-mvc/">Generic Repository Pattern in C#</a>
- <a href="https://dotnettutorials.net/lesson/repository-pattern-implementation-guidelines-csharp/">Using Both Generic and Non-Generic Repository Pattern in c#</a>
- <a href="https://medium.com/@niteshsinghal85/reducing-code-with-generic-repository-pattern-in-asp-net-core-api-ba611f7c4ab2">Reducing code with Generic Repository pattern in ASP.Net Core API</a>