はじめに
ジェネリックリポジトリに興味が湧いたので、なんとなく作ってみようと思いました。
やってみる
前提
今回は以下の環境で行いました。
- Windows11
- .NET Core 5.0
- SQLite
EFCoreを使用するので、事前にMicrosoft.EntityFrameworkCore
をプロジェクトに追加しておきます。
DbContext
今回はEmployee
テーブルとDepartment
テーブルを作成しました。
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;
}
}
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; }
}
}
namespace TodoApp.Type
{
public class Department
{
/// <summary>
/// 部署ID
/// </summary>
public int Id { get; set; }
/// <summary>
/// 部署名
/// </summary>
public string DepartmentName { get; set; }
}
}
GenericRepository
本題のジェネリックリポジトリは、こちらを参考に以下のコードで実装しました。
ジェネリック型で定義することで、テーブルの型を引数としてとることができます。
今回は「作成」、「全件取得」、「IDで取得」、「削除」のメソッドをジェネリックリポジトリとして実装しました。またSaveChangesAsync
を行うためのメソッドも実装しています。
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();
}
}
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();
}
}
}
リポジトリを使用する
実装したリポジトリを使用するには以下のようにします。
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
には以下のような記述を追加します。
// ...中略
public void ConfigureServices(IServiceCollection services)
{
// ConfigureServices内に以下のように追加する
services.AddScoped<IGenericRepository<Department>, GenericRepository<Department>>();
services.AddDbContext<EmployeeDbContext>(opt => opt.UseSqlite(".dbファイルのパス"));
// ...
マイグレーションする
実際にエンドポイント化する前に、マイグレーションを行わないとDBにテーブルが作成されていないので、以下のコマンドで先にマイグレーションを行います。
Add-Migration "マイグレーション名"
Update-Database "(マイグレーション名)"
updateでDBに反映する際、マイグレーション名を指定しなくても自動的に最新のマイグレーションファイルを読み込んでくれます。
あとはコントローラを作成し、ビルドすればジェネリックリポジトリとして機能するはずです!
テーブル固有のリポジトリを実装したい、、こんなときどうする?
リポジトリを実装する中で、テーブル固有の操作(名前が一致するものを取得するetc...)を行いたくなる時があると思います。
そんな時は、ジェネリックリポジトリを継承したリポジトリを作成すれば解決です!
(参考)
今回は例として、IDが一致するレコードを更新するメソッドを実装します。
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を独自に実装したインターフェースに変えるだけです!
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
には以下のコードを追加します。
services.AddScoped<IGenericRepository<Department>, GenericRepository<Department>>();
// 先程のコードに以下を追加する
services.AddScoped<IEmployeeRepository,EmployeeRepository>();
おわりに
意外とすんなりできたのでうれしかったです