Blazor で IndexedDB と memory access out of bounds エラー
Blazor WebAssembly アプリ アプリから IndexedDB を使う方法です。
Blazor から使えるようにしたライブラリはいくつかありますが、更新が止まっているなど、現時点では良さそうなものがありませんでした。汎用的な IndexedDB をラップしたライブラリを使うのではなく、JavaScript 相互運用 で直接 JavaScript コードを書いて、必要なデータのやりとりだけ行う方法で、解決するのが良さそうです。
- wtulloch/Blazor.IndexedDB: A Blazor library for accessing IndexedDB
- Reshiru/Blazor.IndexedDB.Framework: A framework for blazor which acts as an interface to IndexedDB
- amuste/DnetIndexedDb: Blazor Library for IndexedDB DOM API
- nwestfall/BlazorDB: Use IndexedDB in Blazor WebAssembly (WASM)
Blazor + Dexie.js
IndexDB を扱う JavaScript ライブラリとして、Dexie.js を使います。
index.html に script を追加します。
<script src="_framework/blazor.webassembly.js"></script> | |
<script src="https://unpkg.com/[email protected]/dist/dexie.js"></script> | |
<script src="js/db.js"></script> |
IndexDB を扱う JavaScript のコードです。
var db = new Dexie("friend_database"); | |
db.version(1).stores({ | |
friends: 'name,shoeSize' | |
}); | |
window.putFriend = (friend) => | |
db.friends.put(friend); | |
window.getFriends = () => | |
db.friends.toArray(); |
JavaScript の function を呼び出す C# のコードです。
@code | |
{ | |
public class Friend | |
{ | |
public string Name { get; set; } | |
public int ShoeSize { get; set; } | |
} | |
private async Task GetFriends() | |
{ | |
var friends = | |
await JsRuntime.InvokeAsync<List<Friend>>("getFriends"); | |
} | |
private async Task PutFriend() | |
{ | |
var friend = new Friend | |
{ | |
Name = "Nicolas", | |
ShoeSize = 8 | |
}; | |
var key = await JsRuntime.InvokeAsync<string>("putFriend", friend); | |
} | |
} |
memory access out of bounds
IndexedDB も Blazor も直接は関係のないエラーですが、JavaScript 相互運用で、大きなサイズのデータを JavaScript 側から C# 側に受け取ろうとすると(逆方向も起きるかは未確認)、次のエラーが発生します。
RuntimeError: memory access out of bounds
単純な文字列を返す JavaScript の関数で、1MB を少し超えると起きました。IndexDB で大量のデータを扱うと発生する場合があります。
原因は Mono 由来のようです(関連 issue: Mono WebAssembly)。
回避策としては、やり取りを複数に分割する以外に、C# の byte[] と JavaScript の ArrayBuffer でやりとりするテクニックがあるようです(関連: .net core - Blazor preview 9/mono-wasm memory access out of bounds: max string size for DotNet.invokeMethod? - Stack Overflow)。BlazorFileReader はこの問題を解決しているみたいですね。
単純に、JsonPropertyName 属性を使って JSON データを少し小さくする方法も有効です。
public class Friend | |
{ | |
[JsonPropertyName("n")] | |
public string Name { get; set; } | |
[JsonPropertyName("s")] | |
public int ShoeSize { get; set; } | |
} |