C#でUnityで使えるサーバーを作ってみる DB実装編

## はじめに
この記事は前回の「C#でUnityで使えるサーバーを作ってみる」(https://qiita.com/komugikoShimizu/items/111a2e62ec455a7e1ddc )の続きです。
この続きからの内容となりますので是非こちらを見てからご覧になってください。

## DBを実装する意味について
前回の内容まではそこまでサーバーを利用せずとも実装することができました。しかしここでサーバーを利用することで、複数の接続に対し同じ振る舞いをすることができるようになったり、データの管理をサーバーに任せることができるようになります。こうしたことができるサーバー、ぜひ使いたいということで実装をしていきましょう。

## 実装方法
今回は
1.DB準備
2.利用方法

と分けて記述していきます。

### DB準備
まずはコマンド入力から行っていきましょう。
cdコマンドでサーバーを開いておきましょう。
``` bash
dotnet tool install --global dotnet-ef
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package Microsoft.EntityFrameworkCore.Design
```

こちらの流れを踏むことでDBを実装する準備が整います。

ここまで行うと準備はできているので仕込みを始めます。

サーバーフォルダにModelsフォルダを作成し、その下にUser.csを作成します。

``` User.cs
public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Pass { get; set; }
}
```

簡単に言ってしまえば
どのUserを表すId
ユーザー名を表すName
ログインパスワードを表すPass
で構成されています。

今回はこれでユーザーログインの仕組みを簡単に作成します。

さらにDatabaseフォルダを作成し、AppDBContext.csを作成します。
```AppDBContext.cs
using Microsoft.EntityFrameworkCore;

public class AppDbContext : DbContext
{
    public DbSet<User> Users { get; set; }

    public AppDbContext(DbContextOptions<AppDbContext> options)
        : base(options)
    {
    }
}
```
DB利用する上で必要なファイルですね。

そしてこれを利用可能にするためにProgram.csを改良していきましょう。
``` Program.cs
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlite("Data Source=app.db"));

// Add services to the container.
builder.Services.AddMagicOnion();

var app = builder.Build();

// Configure the HTTP request pipeline.
app.MapMagicOnionService();
app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");

app.Run();
```


モデルクラスを作成していざ開発!と行きたいところですが、実は足りません。
このモデルクラスの作成が必要なんですね。インスタンス作成のようなものです。

このモデルクラスの作成はマイグレーションを活用します。

``` bash
dotnet ef migrations add InitialCreate
dotnet ef database update
```

この過程を踏むことでテーブルの作成を行うことができます。

※ちょっと踏み込んだ話
このマイグレーションファイル、Laravelとか使用されているとちょっと想像とは異なるかと思います。
これの仕様としては「前回との差分をマイグレーションとして書き込む」という感じになっており、一つのテーブルを作成するためのものではありません。
なので今後複数のファイルを作成しても、マイグレーションを増やすだけでいいです。(まだ勉強し始めなので間違ってるかも...)

ここまで例外がなければテーブルが追加できているはずです。目に見えないけどね。

### 利用方法
では利用環境を作成していきましょう。
まずは前回の要領でインターフェースとサービスクラスを作成します。
今回はユーザー追加とログイン用で二つ作成していきましょう。

``` IAddUserService.cs
using MagicOnion;

namespace MagicOnionFolder.Shared
{
    public interface IAddUserService : IService<IAddUserService>
    {
        UnaryResult<bool> AddUserAsync(string name, string pass);
    }
}
```
``` ILoginService.cs
using MagicOnion;

namespace MagicOnionFolder.Shared
{
    public interface ILoginService : IService<ILoginService>
    {
        UnaryResult<bool> LoginAsync(string name, string pass);
    }
}
```
(全然変わらん...)

そしてサービスクラスを作成していきましょう。

```AddUserService.cs
using MagicOnion;
using MagicOnion.Server;
using MagicOnionFolder.Shared;
using Microsoft.EntityFrameworkCore; // ← FirstOrDefaultAsyncを使うために必要

namespace MagicOnionProject.Server.Services;

// Implements RPC service in the server project.
// The implementation class must inherit `ServiceBase<IMyFirstService>` and `IMyFirstService`
public class AddUserService : ServiceBase<IAddUserService>, IAddUserService
{
    private readonly AppDbContext _db;

    public AddUserService(AppDbContext db)
    {
        _db = db;
    }

    // `UnaryResult<T>` allows the method to be treated as `async` method.
    public async UnaryResult<bool> AddUserAsync(string name, string pass)
    {
        _db.Users.Add(new User { Name = name, Pass = pass });
        await _db.SaveChangesAsync();
        return true;
    }
}
```

``` LoginService.cs
using MagicOnion;
using MagicOnion.Server;
using MagicOnionFolder.Shared;
using Microsoft.EntityFrameworkCore; // ← FirstOrDefaultAsyncを使うために必要

namespace MagicOnionProject.Server.Services;

// Implements RPC service in the server project.
// The implementation class must inherit `ServiceBase<IMyFirstService>` and `IMyFirstService`
public class LoginService : ServiceBase<ILoginService>, ILoginService
{
    private readonly AppDbContext _db;

    public LoginService(AppDbContext db)
    {
        _db = db;
    }

    // `UnaryResult<T>` allows the method to be treated as `async` method.
    public async UnaryResult<bool> LoginAsync(string name, string pass)
    {
        // 名前に一致するユーザーを探す
        var existingUser = await _db.Users.FirstOrDefaultAsync(u => u.Name == name);

        return existingUser.Pass == pass;
    }
}
```

このような処理で実装ができます。
また応用を行う場合、# はじめに
この記事は前回の「C#でUnityで使えるサーバーを作ってみる」(https://qiita.com/komugikoShimizu/items/111a2e62ec455a7e1ddc )の続きです。
この続きからの内容となりますので是非こちらを見てからご覧になってください。

## DBを実装する意味について
前回の内容まではそこまでサーバーを利用せずとも実装することができました。しかしここでサーバーを利用することで、複数の接続に対し同じ振る舞いをすることができるようになったり、データの管理をサーバーに任せることができるようになります。こうしたことができるサーバー、ぜひ使いたいということで実装をしていきましょう。

## 実装方法
今回は
1.DB準備
2.利用方法

と分けて記述していきます。

### DB準備
まずはコマンド入力から行っていきましょう。
cdコマンドでサーバーを開いておきましょう。
``` bash
dotnet tool install --global dotnet-ef
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package Microsoft.EntityFrameworkCore.Design
```

こちらの流れを踏むことでDBを実装する準備が整います。

ここまで行うと準備はできているので仕込みを始めます。

サーバーフォルダにModelsフォルダを作成し、その下にUser.csを作成します。

``` User.cs
public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Pass { get; set; }
}
```

簡単に言ってしまえば
どのUserを表すId
ユーザー名を表すName
ログインパスワードを表すPass
で構成されています。

今回はこれでユーザーログインの仕組みを簡単に作成します。

さらにDatabaseフォルダを作成し、AppDBContext.csを作成します。
```AppDBContext.cs
using Microsoft.EntityFrameworkCore;

public class AppDbContext : DbContext
{
    public DbSet<User> Users { get; set; }

    public AppDbContext(DbContextOptions<AppDbContext> options)
        : base(options)
    {
    }
}
```
DB利用する上で必要なファイルですね。

そしてこれを利用可能にするためにProgram.csを改良していきましょう。
``` Program.cs
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlite("Data Source=app.db"));

// Add services to the container.
builder.Services.AddMagicOnion();

var app = builder.Build();

// Configure the HTTP request pipeline.
app.MapMagicOnionService();
app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");

app.Run();
```


モデルクラスを作成していざ開発!と行きたいところですが、実は足りません。
このモデルクラスの作成が必要なんですね。インスタンス作成のようなものです。

このモデルクラスの作成はマイグレーションを活用します。

``` bash
dotnet ef migrations add InitialCreate
dotnet ef database update
```

この過程を踏むことでテーブルの作成を行うことができます。

※ちょっと踏み込んだ話
このマイグレーションファイル、Laravelとか使用されているとちょっと想像とは異なるかと思います。
これの仕様としては「前回との差分をマイグレーションとして書き込む」という感じになっており、一つのテーブルを作成するためのものではありません。
なので今後複数のファイルを作成しても、マイグレーションを増やすだけでいいです。(まだ勉強し始めなので間違ってるかも...)

ここまで例外がなければテーブルが追加できているはずです。目に見えないけどね。

### 利用方法
では利用環境を作成していきましょう。
まずは前回の要領でインターフェースとサービスクラスを作成します。
今回はユーザー追加とログイン用で二つ作成していきましょう。

``` IAddUserService.cs
using MagicOnion;

namespace MagicOnionFolder.Shared
{
    public interface IAddUserService : IService<IAddUserService>
    {
        UnaryResult<bool> AddUserAsync(string name, string pass);
    }
}
```
``` ILoginService.cs
using MagicOnion;

namespace MagicOnionFolder.Shared
{
    public interface ILoginService : IService<ILoginService>
    {
        UnaryResult<bool> LoginAsync(string name, string pass);
    }
}
```
(全然変わらん...)

そしてサービスクラスを作成していきましょう。

```AddUserService.cs
using MagicOnion;
using MagicOnion.Server;
using MagicOnionFolder.Shared;
using Microsoft.EntityFrameworkCore; // ← FirstOrDefaultAsyncを使うために必要

namespace MagicOnionProject.Server.Services;

// Implements RPC service in the server project.
// The implementation class must inherit `ServiceBase<IMyFirstService>` and `IMyFirstService`
public class AddUserService : ServiceBase<IAddUserService>, IAddUserService
{
    private readonly AppDbContext _db;

    public AddUserService(AppDbContext db)
    {
        _db = db;
    }

    // `UnaryResult<T>` allows the method to be treated as `async` method.
    public async UnaryResult<bool> AddUserAsync(string name, string pass)
    {
        _db.Users.Add(new User { Name = name, Pass = pass });
        await _db.SaveChangesAsync();
        return true;
    }
}
```

``` LoginService.cs
using MagicOnion;
using MagicOnion.Server;
using MagicOnionFolder.Shared;
using Microsoft.EntityFrameworkCore;

namespace MagicOnionProject.Server.Services;

// Implements RPC service in the server project.
// The implementation class must inherit `ServiceBase<IMyFirstService>` and `IMyFirstService`
public class LoginService : ServiceBase<ILoginService>, ILoginService
{
    private readonly AppDbContext _db;

    public LoginService(AppDbContext db)
    {
        _db = db;
    }

    // `UnaryResult<T>` allows the method to be treated as `async` method.
    public async UnaryResult<bool> LoginAsync(string name, string pass)
    {
        // 名前に一致するユーザーを探す
        var existingUser = await _db.Users.FirstOrDefaultAsync(u => u.Name == name);

        return existingUser.Pass == pass;
    }
}
```

このような処理で実装ができます。
また応用を行う場合、EFCoreという技術を利用されているので記述法を知りたい場合は調べてみるといいかもですね。

そして動作確認用にSampleScene.csを変更しましょう。

``` SampleScene.cs
using System;
using MagicOnion;
using MagicOnion.Client;
using MagicOnionFolder.Shared;
using UnityEngine;

public class SampleScene : MonoBehaviour
{
    [SerializeField] private string username;
    [SerializeField] private string pass;
    [SerializeField] private bool createMode = false;
    // Start is called once before the first execution of Update after the MonoBehaviour is created
    async void Start()
    {
        try
        {
            if (createMode)
            {
                var channel = GrpcChannelx.ForAddress("http://localhost:5244");
                var client = MagicOnionClient.Create<IAddUserService>(channel);

                var result = await client.AddUserAsync(username, pass);
                string log = result ? "create" : "error";
                Debug.Log(log);
            }
            else
            {
                var channel = GrpcChannelx.ForAddress("http://localhost:5244");
                var client = MagicOnionClient.Create<ILoginService>(channel);

                var result = await client.LoginAsync(username, pass);
                string log = result ? "Login" : "error";
                Debug.Log(log);
            }
        }
        catch (Exception e)
        {
            Debug.LogException(e);
        }
    }

    // Update is called once per frame
    void Update()
    {

    }
}
```

これでインスペクターで値を設定すれば利用可能になりますね
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3236298/d990396e-e7d0-477e-8e46-03d7c100f329.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3236298/4be4d1d0-a0b1-4629-ace6-241254bbe069.png)

ちゃんと存在しなかったりパスワードを間違えたりすると失敗します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3236298/72b20a87-51ef-45bd-a0c7-d6bb3f96438f.png)

これで実装されていることが確認できましたね。

## 最後に
これでDB実装を終わります。
書き方さえ分かっていれば初期実装以外はほぼ応用で運用できますね。
これがMagicOnionを利用したい人の助けになると幸いです。