20210731のC#に関する記事は5件です。

重み付きランダム選択アルゴリズムの速度計測【C#】

はじめに 重み付きランダム選択アルゴリズムは、「アイテムのドロップ」や「ガチャ」など、それぞれのアイテムに選出確立が設定されている中から1つのアイテムを選り抜くためのものです。 その為に使用されるアルゴリズムは様々ですが、「結局、どの場面でどのアルゴリズムを使うのが最適なのか?」といったことが気になりました。 なので、この記事では様々なシチュエーションでそれぞれのアルゴリズムの速度を計測していきます。 使用するアルゴリズム この記事では『Choice』という重み付きランダム選択用のライブラリを使い、そこに実装されている3つのアルゴリズムを使用します。 アルゴリズム 説明 ソース(GitHub) Linear Scan 重みに沿って直線的に探索する最も単純なアルゴリズム。選択はO(n)。 実装コード Binary Search セットアップはO(n)だが、選択ごとに最大O(log(n))まで加速する。 実装コード Alias Method セットアップはO(n)だが、選択は驚異のO(1)。多くのアイテムを選択する際に効果的。 実装コード 計測方法 重みの配列の長さは[1, 10, 100, 1000, 10000]、選択回数は[1, 10, 100, 1000, 10000]。全部で25通りのパターンを試す。 計測結果には「セットアップ時間」と「アイテムの選択時間」を含める。 また、計測用コードは記事の最後に載せてあります。 計測結果 1 items Linear Scan が最速。アイテム数が一桁の場合は Linear Scan を使っておけば良さそう。 なお、Alias Method はセットアップ処理が最適化されているので、反復が1回のみの時だけ速い。 反復回数 1 10 100 1000 10000 Linear Scan 0.0104ms 0.0055ms 0.0081ms 0.0393ms 0.3408ms Binary Search 0.0091ms 0.0083ms 0.0126ms 0.0659ms 0.5944ms Alias Method 0.0069ms 0.0065ms 0.01ms 0.0459ms 0.4094ms 反復回数 1 10 100 1000 10000 Linear Scan 0.0155ms 0.0064ms 0.0077ms 0.0381ms 0.353ms Binary Search 0.0077ms 0.008ms 0.0123ms 0.0659ms 0.5919ms Alias Method 0.0062ms 0.0065ms 0.01ms 0.0462ms 0.41ms 反復回数 1 10 100 1000 10000 Linear Scan 0.009ms 0.0053ms 0.0081ms 0.0378ms 0.3388ms Binary Search 0.0073ms 0.0079ms 0.0129ms 0.0653ms 0.5927ms Alias Method 0.0054ms 0.0062ms 0.0104ms 0.0461ms 0.4194ms 10 items 10アイテムはアイテムドロップの選択であり得る例。 最初は Linear Scan と Binary Search が拮抗( Linear Scan がやや優勢か)しているが、100辺りで Alias Method の良さが出始める。 反復回数 1 10 100 1000 10000 Linear Scan 0.0113ms 0.0077ms 0.0182ms 0.1219ms 1.19ms Binary Search 0.0109ms 0.0114ms 0.0237ms 0.158ms 1.4975ms Alias Method 0.0136ms 0.022ms 0.0151ms 0.0601ms 0.5041ms 反復回数 1 10 100 1000 10000 Linear Scan 0.012ms 0.0072ms 0.0174ms 0.1272ms 1.1738ms Binary Search 0.0095ms 0.0099ms 0.023ms 0.16ms 1.5503ms Alias Method 0.0141ms 0.0104ms 0.0148ms 0.0618ms 0.5235ms 反復回数 1 10 100 1000 10000 Linear Scan 0.0095ms 0.009ms 0.0179ms 0.1216ms 1.1503ms Binary Search 0.0096ms 0.0103ms 0.0225ms 0.1572ms 1.4991ms Alias Method 0.0129ms 0.0105ms 0.015ms 0.0607ms 0.5176ms 100 items 10アイテムの時とほぼ同じような感じ。 だが、Linear Scan よりも Binary Search がやや優勢か。 反復回数 1 10 100 1000 10000 Linear Scan 0.0201ms 0.024ms 0.0822ms 0.741ms 7.2211ms Binary Search 0.0212ms 0.0211ms 0.0433ms 0.3118ms 2.6434ms Alias Method 0.0717ms 0.0364ms 0.0395ms 0.086ms 0.5462ms 反復回数 1 10 100 1000 10000 Linear Scan 0.0231ms 0.0247ms 0.0855ms 0.7027ms 7.0025ms Binary Search 0.0224ms 0.0231ms 0.0441ms 0.2776ms 2.6521ms Alias Method *0.039ms 0.0358ms 0.0405ms 0.0861ms 0.5561ms 反復回数 1 10 100 1000 10000 Linear Scan 0.0194ms 0.0232ms 0.0892ms 0.7582ms 7.1886ms Binary Search 0.0206ms 0.0218ms 0.0447ms 0.2804ms 2.6375ms Alias Method 0.0376ms 0.0381ms 0.0413ms 0.0871ms 0.5728ms 1000 items 綺麗な線形。 1000辺りで Alias Method 優勢になってくる。 反復回数 1 10 100 1000 10000 Linear Scan 0.1147ms 0.1672ms 0.7792ms 6.7539ms 66.8329ms Binary Search 0.1205ms 0.1183ms 0.1504ms 0.4758ms 3.7755ms Alias Method 0.2783ms 0.2722ms 0.2925ms 0.3238ms 0.7824ms 反復回数 1 10 100 1000 10000 Linear Scan 0.1068ms 0.1717ms 0.8331ms 6.8282ms 68.455ms Binary Search 0.1217ms 0.1173ms 0.1499ms 0.5026ms 3.7627ms Alias Method 0.2785ms 0.2889ms 0.2876ms 0.3318ms 0.908ms 反復回数 1 10 100 1000 10000 Linear Scan 0.102ms 0.1636ms 0.7271ms 6.743ms 66.4393ms Binary Search 0.1241ms 0.1208ms 0.1501ms 0.5216ms 4.0165ms Alias Method 0.2782ms 0.2755ms 0.2777ms 0.3454ms 0.8068ms 10000 items Binary Searchが優秀。 反復回数 1 10 100 1000 10000 Linear Scan 1.1885ms 1.7971ms 8.0482ms 69.1749ms 664.8696ms Binary Search 1.3329ms 1.3181ms 1.3454ms 1.7735ms 6.1215ms Alias Method 2.8859ms 2.8719ms 2.8832ms 2.9779ms 3.4764ms 反復回数 1 10 100 1000 10000 Linear Scan 1.1676ms 1.6953ms 8.0905ms 70.1629ms 668.3197ms Binary Search 1.3118ms 1.3361ms 1.3407ms 1.786ms 6.1105ms Alias Method 2.8833ms 2.934ms 2.951ms 2.9845ms 3.6259ms 反復回数 1 10 100 1000 10000 Linear Scan 1.3098ms 1.9826ms 8.063ms 68.9301ms 666.9364ms Binary Search 1.4456ms 1.3787ms 4.6233ms 1.7861ms 6.0783ms Alias Method 2.9751ms 2.9144ms 2.9236ms 2.9851ms 3.5149ms (おまけ)10000 items without setup セットアップ時間を含まない場合の計測結果。 やっぱり Alias Method が一番速い。 反復回数 1 10 100 1000 10000 Linear Scan 0.0207ms 0.7364ms 6.5433ms 67.3963ms 671.3184ms Binary Search 0.0015ms 0.0055ms 0.0492ms 0.496ms 4.828ms Alias Method 0.0005ms 0.0011ms 0.0066ms 0.0579ms 0.5559ms まとめ Linear Scan は要素数が少ない場合に有効 Binary Search は汎用性が高い Alias Methodはセットアップを除けば最速 アルゴリズム選定の参考になれば幸いです。 おわりに 今回使った『Choice』は気合を入れて作ったものなので使ってもらえると嬉しくなります。 参考 A Faster Weighted Random Choice Walker's Alias Methodの箱の作り方のわかりやすい説明 重み付きランダムサンプリングアルゴリズム 検証用コード WeightedSelectMethodSpeedTests.cs using System.Linq; using System.Collections.Generic; using System.Diagnostics; using NUnit.Framework; using UnityEngine; namespace MackySoft.Choice.Tests { public class WeightedSelectMethodSpeedTests { static readonly int[] k_Iterations = new int[] { 1, 10, 100, 1000, 10000 }; [Test] public void CompareSpeedAllMethods ([Values(1,10,100,1000,10000)] int count) { // 重みの配列を[1, 10, 100, 1000, 10000]、いずれかの長さで用意。 var source = ItemEnumerableGenerator.GenerateEnumerable(count).ToArray(); // 選択に使用する[0.0f~1.0f]の値を用意する。 float[] values = new float[k_Iterations.Max()]; for (int i = 0;values.Length > i;i++) { values[i] = Random.value; } Stopwatch stopwatch = new Stopwatch(); foreach (IWeightedSelectMethod method in AllWeightedSelectMethods()) { // [1, 10, 100, 1000, 10000]、5通りの反復回数を試みる。 for (int i = 0;k_Iterations.Length > i;i++) { int iteration = k_Iterations[i]; stopwatch.Start(); // セットアップ var weightedSelector = source.ToWeightedSelector(x => x.item,x => x.weight,method); // WeightedSelectorからアイテムを選択する。 for (int k = 0;iteration > k;k++) { weightedSelector.SelectItem(values[k]); } stopwatch.Stop(); UnityEngine.Debug.Log($"{method.GetType()}: Count {count}, Iteration {iteration}, Speed {stopwatch.Elapsed.TotalMilliseconds}ms"); stopwatch.Reset(); } } Assert.Pass(); } static IEnumerable<IWeightedSelectMethod> AllWeightedSelectMethods () { yield return WeightedSelectMethod.Linear; yield return WeightedSelectMethod.Binary; yield return WeightedSelectMethod.Alias; } } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

へっぽこプログラマがDIの利用例を考えてみる(C#)

はじめに 前回の投稿ではDIって何かを考えてみました。 今回は新たなサンプルプログラムを元に、DIを使用する場面について考えてみたいと思います。 ※ この記事に掲載しているサンプルプログラムは.NET Core 3.1上で動作することを確認しています。 非DIのサンプル 今回のサンプルプログラムは指定したサーバにPingを定期的に打ち、そのサーバが生きているかどうかを調べるプログラムになっています。 クラス構成は下図のようになっています。赤色の矢印は依存の向きを示しています。例えばProgramクラスはHeartBeatクラスに依存しています。 サンプルプログラムの中核をなすのは、HeartBeatクラスとPingCheckerクラスです。 HeartBeatクラスは定期的にPingを打ち、結果を画面に表示する機能を利用者(Programクラス)に提供しています。 PingCheckerクラスはHeartBeatクラスから使われ、実際にPingを打つ機能を提供しています。 HeartBeatクラスとPingCheckerクラスはクラスライブラリ(DLL)として作られ、利用者(Programクラス)に提供されています。 以下にサンプルプログラムを掲載します。ちょっと長いですが、全文を記載します。 ClassLibrary1 (DLL) まずはクラスライブラリ側から。HeartBeatクラスのStart()メソッドでチェックを開始し、Stop()メソッドで停止します。 HeartBeat.cs using System; using System.Net.NetworkInformation; using System.Threading.Tasks; namespace ClassLibrary1 { public class HeartBeat { private volatile bool _running; public async Task Start(string url, int interval) { var checker = new PingChecker(); _running = true; while (_running) { try { var (result, time, message) = checker.Check(url); var resultText = result ? "成功" : "失敗"; Console.WriteLine($"{DateTime.Now}\t{resultText}\t{time}ms\t{url}\t{message}"); } catch (Exception e) { Console.WriteLine($"{e.Message}"); } await Task.Delay(interval); } } public void Stop() { _running = false; } } public class PingChecker { public (bool success, long time, string message) Check(string url) { var ping = new Ping(); PingReply reply = ping.Send(url, timeout: 1000); return (reply.Status == IPStatus.Success, reply.RoundtripTime, reply.Status.ToString()); } } } HeartBeat.Start()メソッドを呼び出すと、PingCheckerクラスのインスタンスをnewしてからwhileループに突入。その後定期的にPingChecker.Check()メソッドを呼び出してその結果を表示します。 whileループは_runningフラグがfalseになるまで継続されます。HeartBeat.Stop()メソッドが呼び出されると、フラグがfalseになりwhileループを抜け出します。_runningメンバ変数は複数のスレッドからアクセスされる可能性があるためvolatile修飾子をつけています。 ※ あくまでサンプルプログラムなのでエラー処理など不完全な部分があります。ご了承ください。 ConsoleApp1 (EXE) 次にHeartBeatを使う側、Programクラスを示します。 やっていることはHeartBeatクラスをnewして、Start()メソッドを呼び出し、コンソール上で何かキー入力があったらStop()メソッドを呼び出して実行を終了しているだけです。シンプルですね。 Program.cs using ClassLibrary1; using System; namespace ConsoleApp1 { class Program { static void Main(string[] args) { var beat = new HeartBeat(); var task = beat.Start("abc.example.com", 3000); Console.ReadKey(); beat.Stop(); task.Wait(); } } } 実行結果 実行結果は以下のようになります。 2021/07/29 13:49:34 成功 17ms abc.example.com Success 2021/07/29 13:49:37 成功 15ms abc.example.com Success 2021/07/29 13:49:40 成功 15ms abc.example.com Success ICMPパケットを受け付けていないサーバを指定すると以下のようになります。 2021/07/29 13:55:09 失敗 0ms www.example.co.jp TimedOut 2021/07/29 13:55:13 失敗 0ms www.example.co.jp TimedOut 2021/07/29 13:55:17 失敗 0ms www.example.co.jp TimedOut DIのサンプル さて、非DIのサンプルを見てきましたが、これで何か問題があるのでしょうか? 話がこれだけで済むなら、このままでも問題ありません。だって、Pingを打ってチェックするという機能要件は満たしているし、シンプルで分かりやすいでしょ? ただ、将来の拡張性を考慮する必要がある場合は、ちょっと工夫したほうがよさそうです。 ある日の会話 例えば、あなたがこのクラスライブラリの作者だとして、ライブラリを使ってくれているユーザに「私はPingじゃなくて、Httpで通信できるかどうかをチェックしたいんだけど、なんとかならない?」って言われたらどうします? クラスライブラリをHttpでもチェックできるように書き換えますか? まあ、一度だけならそれもいいかもしれません。 でも次の日に別のユーザから「俺はデータベースにアクセスできるかどうかをチェックしたいんだけど、なんとかならない?」と言われたらどうします? またその次の日には噂を聞きつけた他の人から「FTPサーバの死活チェックしたいんだけど?」って言われたら? 要求があるたびにクラスライブラリをアップデートしてすべての要求に対応できるように作り替える、っていうのも一つの選択肢です。そうすべき時もあるでしょう。 ただ、いちいち要求に応えるのもしんどいでしょ? 嫌じゃない? そう? 私は嫌です。 DIを使った解決策 そんなわけでDIを使ってそんな悩みを解決してみましょう。欲しい機能があれば、欲しい人に作ってもらうのが一番です。 まずはDIを使った場合のクラス図を見ていきましょう。 非DIの場合のクラス図と比べると、クラスライブラリ中のHeartBeatとPingCheckerの間にICheckerインターフェイスが追加されていることが分かります。 PingCheckerと新たに作成するHttpCheckerはこのICheckerインターフェイスを実装する形にします。HeartBeatクラスはICheckerインターフェイスにのみ依存しているため、ICheckerを実装しているオブジェクトであれば実体がなんであろうと構いません。 新たに作るHttpCheckerクラスがConsoleApp2モジュールの中にあることに注意してください。これは、HttpCheckerは利用者側(クラスライブラリのユーザ)が作ることを意味しています。 では、このサンプルソースを見ていきましょう。これも長いですが、全文を掲載します。 ClassLibrary2 (DLL) まずはクラスライブラリ側から。HeartBeatクラスのStart()メソッド、Stop()メソッドの使い方は変わりません。ソースの下の方でICheckerインターフェイスが定義され、それを実装する形でPingCheckerクラスが定義されています。PingCheckerの中身は変わっていません。 HeartBeat.cs using System; using System.Net.NetworkInformation; using System.Threading.Tasks; namespace ClassLibrary2 { public class HeartBeat { private IChecker _checker; private volatile bool _running; public HeartBeat(IChecker checker) { _checker = checker; } public async Task Start(string url, int interval) { _running = true; while (_running) { try { var (result, time, message) = _checker.Check(url); var resultText = result ? "成功" : "失敗"; Console.WriteLine($"{DateTime.Now}\t{resultText}\t{time}ms\t{url}\t{message}"); } catch (Exception e) { Console.WriteLine($"{e.Message}"); } await Task.Delay(interval); } } public void Stop() { _running = false; } } public interface IChecker { (bool success, long time, string message) Check(string url); } public class PingChecker : IChecker { public (bool success, long time, string message) Check(string url) { var ping = new Ping(); PingReply reply = ping.Send(url, timeout: 1000); return (reply.Status == IPStatus.Success, reply.RoundtripTime, reply.Status.ToString()); } } } ではDIしている部分を見てみましょう。 コンストラクタでICheckerインターフェイスの引数を受け取って、それを後で使うために_checkerメンバ変数に格納していますね。 private IChecker _checker; public HeartBeat(IChecker checker) { _checker = checker; } 実際にチェックを行うときには、この_checkerメンバ変数を通してCheck()メソッドを呼び出しています。_checkerの実体は新しく作るHttpCheckerクラスのインスタンス(オブジェクト)になるのですが、HeartBeatクラスにしてみればコンストラクタに渡されるオブジェクトの実体が何であろうとICheckerインターフェイスさえ実装していればいいわけで、実体が何であるかには頓着しません。 try { var (result, time, message) = _checker.Check(url); // 中略 } 非DIバージョンの場合はStart()メソッドの中でPingCheckerをnewしていましたが、その部分がなくなっていることに注意してください。これは、HeartBeatクラスがPingCheckerクラスに依存しなくなったことを意味しています。 // 非DIバージョン public async Task Start(string url, int interval) { var checker = new PingChecker(); // 後略 ConsoleApp2 (EXE) 次に利用者側のコードを見ていきましょう。 Programクラスの次に、HttpCheckerクラスが定義されています。HttpCheckerクラスはICheckerインターフェイスを実装しています。 HttpCheckerクラスはHttp通信を行ってサーバと通信できるかをチェックしているのですが、その中身はここではあまり重要ではありません。重要なのはHttpCheckerがICheckerインターフェイスを実装していることです。 Program.cs using ClassLibrary2; using System; using System.Diagnostics; using System.IO; using System.Net.Http; using System.Threading.Tasks; namespace ConsoleApp2 { class Program { static void Main(string[] args) { IChecker checker = new HttpChecker(); var beat = new HeartBeat(checker); var task = beat.Start("https://www.example.co.jp/", 3000); Console.ReadKey(); beat.Stop(); task.Wait(); } } class HttpChecker : IChecker { private HttpClient _http; public HttpChecker() { _http = new HttpClient(); _http.Timeout = TimeSpan.FromMilliseconds(5000); } public (bool success, long time, string message) Check(string path) { try { var sw = new Stopwatch(); sw.Start(); var result = _http.GetAsync(path).Result; sw.Stop(); return (result.StatusCode == System.Net.HttpStatusCode.OK, sw.ElapsedMilliseconds, $"{(int)result.StatusCode} {result.StatusCode}"); } catch (AggregateException e) { if (e.InnerException is TaskCanceledException) { return (false, 0, "Timeout"); } else { throw; } } } } } ではProgramクラスの方にうつって、DIしている部分を見てみましょう。 最初にHttpCheckerのインスタンスをnewして、それをHeartBeatクラスのコンストラクタに渡しています。 HeartBeatが依存するオブジェクト(HttpChecker)をコンストラクタ経由でHeartBeatに注入しています。依存するオブジェクトを注入している、依存性の注入、DIしてますね! static void Main(string[] args) { IChecker checker = new HttpChecker(); var beat = new HeartBeat(checker); // 後略 実行結果 実行結果は以下のようになります。 2021/07/29 15:37:10 成功 639ms https://www.example.co.jp/ 200 OK 2021/07/29 15:37:13 成功 279ms https://www.example.co.jp/ 200 OK 2021/07/29 15:37:17 成功 301ms https://www.example.co.jp/ 200 OK 非DIと比べて何がよくなったのか? DIにすることで利用者から「Httpでチェックしたいんだけど」、「データベースを開けるかチェックしたいだけど」、「FTPサーバが生きてるか調べたいんだけど」とか言われても、「ICheckerインターフェイスを実装すればできるから、自分で作ってね!」と言うことができます。 楽でしょ!? そんなことない? と、まあこんな感じなのですが、実際はこんなにうまくいかない場面も多いと思います。例えばユーザからの要望の内容によってはICheckerインターフェイス自体を改良する必要がでてくるかもしれません。そうなると、どのみちクラスライブラリを更新する必要がでてきます。 まとめ ここまでDIの適用例についてサンプルプログラムを通して見てきました。DIすることで機能の変更(PingCheker → HttpChecker)が容易になることが分かったのではないでしょうか。 ただ、前回の投稿でも書きましたが、何が何でもDIしなきゃならないってわけではないことに注意してください。「俺は拡張性なんて求めないからDIなんていらねぇ」っておっしゃるのなら、それもありだと思います。ただ、チームで作業しててDIしてねって言われたら、その妥当性を判断して妥当なら従ってくださいね(笑) 将来的に機能を変更、拡張できるようにしたいとか、単体テストをしやすくしたいとか、DIする動機はいろいろあると思いますが、本当にDIが必要かどうかはケースバイケースだと思います。まずは必要かどうかを検討し、必要であればDIすればいいのではないかと個人的には思います。 長文、乱文失礼しました。 間違い、勘違い等あるかもしれませんがご容赦ください。なにぶん、へっぽこなもので。 この記事が何かの参考になれば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【C# / .NET】Newtonsoft.json と DBNull.Value と DateTime 型の値を変換した結果が異なる

前置き Newtonsoft.json と System.Text.Json で、DBNull.Value と DateTime 型の値を変換する際の動作が異なっていました。 実行環境、コードは以下のような感じです。 といっても、両者の違いはメソッド名だけですが。 実行環境 .NET Framework 4.8 Newtonsoft.json 13.0.1 System.Text.Json 5.0.2 Newtonsoft.json dynamic data = new ExpandoObject(); data.nv = DBNull.Value; data.dt = DateTime.Now; var serialized = JsonConvert.SerializeObject(data); var deserialized = JsonConvert.DeserializeObject<ExpandoObject>(r); System.Text.Json dynamic data = new ExpandoObject(); data.nv = DBNull.Value; data.dt = DateTime.Now; var serialized = JsonSerializer.Serialize(data); var deserialized = JsonSerializer.Deserialize<ExpandoObject>(serialized); 結果 Newtonsoft.json シリアライズ {"nv":null,"dt":"2021-07-31T06:30:38.8041996+09:00"} デシリアライズ System.Text.Json シリアライズ {"nv":{},"dt":"2021-07-31T06:30:38.4280875+09:00"} デシリアライズ # Newtonsoft.json System.Text.Json DBNull.Value null string 「"{}"」 DateTime DateTime string 「"JSONの日付フォーマット"」 シリアライズ時、DBNull.Value は Newtonsoft.json では null に出力され、 System.Text.Json では "{}"(空文字)で出力され、 DateTime は双方"JSONの日付フォーマット"で出力されました。 dynamic型でデシリアライズした際、System.Text.Json は System.Text.Json.JsonValueKind というクラスにラップされ、DateTime も文字列で返されます。 この辺は状況によってどちらが適切か分かれると思いますが、私は Newtonsoft.json の仕様の方が素直な感じがして賛成。 というか DateTime が文字列になってしまうのは辛いです。 マニアックな記事かもしれませんが、現在でハマったのでこの記事が誰かのお役に立てば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【C# / .NET】Newtonsoft.json とSystem.Text.Json で DBNull.Value と DateTime 型の値を変換した結果が異なる

前置き Newtonsoft.json と System.Text.Json で、DBNull.Value と DateTime 型の値を変換する際の動作が異なっていました。 実行環境、コードは以下のような感じです。 といっても、両者の違いはメソッド名だけですが。 実行環境 .NET Framework 4.8 Newtonsoft.json 13.0.1 System.Text.Json 5.0.2 Newtonsoft.json dynamic data = new ExpandoObject(); data.nv = DBNull.Value; data.dt = DateTime.Now; var serialized = JsonConvert.SerializeObject(data); var deserialized = JsonConvert.DeserializeObject<ExpandoObject>(r); System.Text.Json dynamic data = new ExpandoObject(); data.nv = DBNull.Value; data.dt = DateTime.Now; var serialized = JsonSerializer.Serialize(data); var deserialized = JsonSerializer.Deserialize<ExpandoObject>(serialized); 結果 Newtonsoft.json シリアライズ {"nv":null,"dt":"2021-07-31T06:30:38.8041996+09:00"} デシリアライズ System.Text.Json シリアライズ {"nv":{},"dt":"2021-07-31T06:30:38.4280875+09:00"} デシリアライズ # Newtonsoft.json System.Text.Json DBNull.Value null string 「"{}"」 DateTime DateTime string 「"JSONの日付フォーマット"」 シリアライズ時、DBNull.Value は Newtonsoft.json では null に出力され、 System.Text.Json では "{}"(空文字)で出力され、 DateTime は双方"JSONの日付フォーマット"で出力されました。 dynamic型でデシリアライズした際、System.Text.Json は System.Text.Json.JsonValueKind というクラスにラップされ、DateTime も文字列で返されます。 この辺は状況によってどちらが適切か分かれると思いますが、私は Newtonsoft.json の仕様の方が素直な感じがして賛成。 というか DateTime が文字列になってしまうのは辛いです。 マニアックな記事かもしれませんが、現在でハマったのでこの記事が誰かのお役に立てば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

'The model backing the 'XXXXXX' context has changed since the database was created. のエラーが出たとき

.NET MVCで簡単なアプリを作っているときに、題のようなエラーが生じたので、このエラーが起こった時の解決方法についてまとめた。 なお、今作っている簡易アプリはEntity Frameworkを使用しており、コードファーストでModelを作成している。 エラーの意味は、モデルの内容が変更されてしまっているので、マイグレーションをする必要があるというもの このエラーは作成途中でモデルに変更を加えると起こるようだ。 解決方法 「Enable-Migrations」を叩く そうすると Migrations have already been enabled in project 'XXXXXXXXX'. To overwrite the existing migrations configuration, use the -Force parameter. のメッセージが表示された。 すでにマイグレーションがある的なメッセージだろうか。 「Add-Migration AddStaffRequiredConstraint」を叩く そうすると、 The Designer Code for this migration file includes a snapshot of your current Code First model. This snapshot is used to calculate the changes to your model when you scaffold the next migration. If you make additional changes to your model that you want to include in this migration, then you can re-scaffold it by running 'Add-Migration AddStaffRequiredConstraint' again. というメッセージが表示された。 これでもうまくいってないっぽい。 「Update-Database」を叩く そうすると、 Specify the '-Verbose' flag to view the SQL statements being applied to the target database. Applying explicit migrations: [202107301524230_AddStaffRequiredConstraint]. Applying explicit migration: 202107301524230_AddStaffRequiredConstraint. Running Seed method. が表示された。 そうすると、Migrationsディレクトリにマイグレーションが作成されたので、成功したっぽい? デバッグも動いて成功。 所感 Controllerの.csファイルなどで、元々作成したModelを継承して新しいモデルを作成作ったりすると今回のようなエラーが起こるんだそうだ。 ひとまず、継承なり、何なり、何かモデルをいじった場合は「Update-Database」を叩けば解決するのだろうか。 参考文献
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む