.NET MAUI  (データストア)

# .NET MAUI のデータストア

.NET MAUIのデータストアはどうなってるのかな？
とういうことで、勉強していきます。

# 開発環境
* Win11
* VS2022 Preview Version 17.3.0 Preview 2.0

環境構築＆プロジェクト作成の流れはこちらの記事でどうぞ。

https://qiita.com/kashin777/items/eae43a555dc28867926f

# サンプルソース
.NET MAUIの新規プロジェクト作成時のサンプルソースです。
画面上のボタンをクリックするとその回数をお知らせします。
悲しいことに、クリック回数を永続化しません。
2,147,483,647回クリックしても、アプリケーション閉じたら1からやり直しです。しないけど。

MainPage.cs 
```C#
namespace MauiDatabaseDemo;

public partial class MainPage : ContentPage
{
	int count = 0;

	public MainPage()
	{
		InitializeComponent();
	}

	private void OnCounterClicked(object sender, EventArgs e)
	{
		count++;

		if (count == 1)
			CounterBtn.Text = $"Clicked {count} time";
		else
			CounterBtn.Text = $"Clicked {count} times";

		SemanticScreenReader.Announce(CounterBtn.Text);
	}
}
```

# データを保存する方法
クリック回数を是が非でも永続したいんじゃっ！てことで、.NET MAUI でデータを永続する方法調べました。

1. Preferences
1. FileSystem
1. Database
1. Remote (この記事ではあつかいません。)
1. SecureStorage **<font color=red>2022/07/19追加</font>**

## 1. Preferences
key=value形式で値を永続することができます。
値としてサポートされるのは

* Boolean
* Double
* Int32
* Single
* Int64
* String
* DateTime

です。
複雑なデータには向きません。Jsonにシリアライズして・・・とか考えないように。

### ソース

では、クリック回数を永続してみましょう！

```C#
namespace MauiDatabaseDemo;

public partial class MainPage : ContentPage
{
	int count = 0;

	public MainPage()
	{
		InitializeComponent();
	}

	private void OnCounterClicked(object sender, EventArgs e)
	{
		// ADD START
		count = Preferences.Get("count", 0); // 第二引数はデフォルト値
        // ADD END

        count++;

		if (count == 1)
			CounterBtn.Text = $"Clicked {count} time";
		else
			CounterBtn.Text = $"Clicked {count} times";

		SemanticScreenReader.Announce(CounterBtn.Text);

        // ADD START
        Preferences.Default.Set("count", count);
        // ADD END
    }
}
```

### どこに保存されるか？

* iOS : NSUserDefaults 
* Android : SharedPreferences
* Windows : ApplicationDataContainer 
C:/Users/***UserId***/AppData/Local/Packages/***ApplicationIdGuid_xxxxxxxxxxxxx***/Settings/settings.dat ぽい。

## 2. FileSystem
アプリケーション専用のディレクトリであれば、権限を気にせずアクセス可能です。
※iOSな人は、うんたらかんたら書いてあったので、[参考資料](#参考資料)を見れ。

```C# 
// アプリケーションデータはこっち
string mainDir = FileSystem.Current.AppDataDirectory;
// キャッシュデータはこっち
string cacheDir = FileSystem.Current.CacheDirectory;
```

### ソース

では、クリック回数を永続してみましょう！
よい子は、Serializerとか使いましょう()

```C#
namespace MauiDatabaseDemo;

public partial class MainPage : ContentPage
{
	int count = 0;

	public MainPage()
	{
		InitializeComponent();
	}

	private void OnCounterClicked(object sender, EventArgs e)
	{
		// ADD START
		string path = Path.Combine(FileSystem.Current.AppDataDirectory, "count.txt");
		if (File.Exists(path))
		{
			using (var s = new StreamReader(File.OpenRead(path)))
			{
				count = int.Parse(s.ReadLine());
			}
		}
		else
		{
			count = 0;
		}
        // ADD END

        count++;

		if (count == 1)
			CounterBtn.Text = $"Clicked {count} time";
		else
			CounterBtn.Text = $"Clicked {count} times";

		SemanticScreenReader.Announce(CounterBtn.Text);

        // ADD START
        using (var s = new StreamWriter(File.OpenWrite(path)))
        {
			s.Write(count.ToString());
        }
        // ADD END
    }
}
```

### どこに保存されるか？

* Windows :  
C:/Users/***UserId***/AppData/Local/Packages/***ApplicationIdGuid_xxxxxxxxxxxxx***/LocalState/count.txt

## 3. Database
「この要件で、DBなしの設計なんてムリポぉ！」な時がくるかもしれないし。
[参考資料](#参考資料) が [sqlite-net-pcl](https://www.nuget.org/packages/sqlite-net-pcl/1.8.116?_src=template) 押しなので、使ってみましょう。

~~EF.Coreダメなん？~~ (未調査)
SQLite専用だけど、EF.Coreと操作感は似てました。
個人的にtransactionをusingできないのが違和感。

https://www.nuget.org/packages/sqlite-net-pcl/1.8.116?_src=template

### ソース

では、クリック回数を永続してみましょう！
無駄にクリックした日時も記録しちゃいますよ！無駄に！

```C#
using SQLite;

namespace MauiDatabaseDemo;

public partial class MainPage : ContentPage
{
	int count = 0;

	private SQLiteConnection conn;

	public MainPage()
	{
		InitializeComponent();

        // ADD START
        string path = Path.Combine(FileSystem.Current.AppDataDirectory, "count.db3");
		conn = new SQLiteConnection(path);
        // conn = new SQLiteAsyncConnection(path) 非同期版
		conn.CreateTable(typeof(ClickTime));
        // ADD END
    }

    private void OnCounterClicked(object sender, EventArgs e)
	{
		// ADD START
		count = conn.Table<ClickTime>().Count();		
        // ADD END

        count++;

		if (count == 1)
			CounterBtn.Text = $"Clicked {count} time";
		else
			CounterBtn.Text = $"Clicked {count} times";

		SemanticScreenReader.Announce(CounterBtn.Text);

		// ADD START
		try
		{
			conn.BeginTransaction();
			conn.Insert(new ClickTime() { Time = DateTime.Now });
			conn.Commit();
		}
		catch
		{
			conn.Rollback();
		}
        // ADD END
    }
}

[Table(nameof(ClickTime))]
public class ClickTime
{
	[PrimaryKey]
	[Column(nameof(Time))]
	public DateTime Time { get; set; }
}
```

### どこに保存されるか？
当然ながら[FileSystem](#2-filesystem)と同じ。

## 4. SecureStorage **<font color=red>2022/07/19追加</font>**
セキュリティで保護されたストレージだそうです。
認証キーとか、抜かれて困るものがあれば。
string型しか対応していません。

### ソース

```C#
// 設定
await SecureStorage.Default.SetAsync(string key, string value);

// 取得
await SecureStorage.Default.GetAsync(string key);

// 削除
await SecureStorage.Default.Remove(string key);

// すべて消す
SecureStorage.Default.RemoveAll();

```

# 参考資料
MS様に感謝☆

https://docs.microsoft.com/en-us/learn/dotnet-maui/store-local-data/