20220119のC#に関する記事は9件です。

Unity WebGLでおえかきした画像の保存

はじめに おえかきアプリを作っていて保存機能を実装することになったが、WebGLなのでFile.WriteAllBytes()もApplication.persistentDataPath使えない…となったので、javascriptプラグインを作ったお話。 ※おえかきアプリの実装はメインではないので割愛します。 開発環境 Unity 2020.3.25f1 Visual Studio Code プラグインの作成(.jslib) Unityには、ブラウザのスクリプトと通信を行う方法が大きく2つあります。 1. Application.ExternalCall() か SendMessage()を使う。 2. プラグイン(.jslib)を作り、コードを記述。 今回は後者の方法で実装を進めます。 plugin.jslib mergeInto(LibraryManager.library, { ExportTextureJS:function(base64){ // 手順1 const image = Pointer_stringify(base64); // 手順2 var bin = atob(image.replace(/^.*,/, '')); var buffer = new Uint8Array(bin.length); for (var i = 0; i < bin.length; i++) { buffer[i] = bin.charCodeAt(i); } try{ var blob = new Blob([buffer.buffer], { type: 'image/png' }); }catch (e){ return; } // 手順3 var url = (window.URL || window.webkitURL); var dataUrl = url.createObjectURL(blob); var a = document.createElement('a'); a.download = "picture.png"; a.href = dataUrl; a.click(); }, }); 手順1 javascript文字列への変換 const image = Pointer_stringify(base64); まず引数から入ってきたbase64文字列を、ヘルパー関数Pointer_stringify()を使用してJavaScript文字列に変換します。 これは文字列がemscripten ヒープ内のポインターとして渡されるためです。 手順2 Blob形式への変換 var bin = atob(image.replace(/^.*,/, '')); var buffer = new Uint8Array(bin.length); for (var i = 0; i < bin.length; i++) { buffer[i] = bin.charCodeAt(i); } try{ var blob = new Blob([buffer.buffer], { type: 'image/png' }); }catch (e){ return; } 変換した文字列をBlob形式のFileに変換します。 手順3 アンカー要素を使用して保存 var url = (window.URL || window.webkitURL); var dataUrl = url.createObjectURL(blob); var a = document.createElement('a'); a.download = "picture.png"; a.href = dataUrl; a.click(); HTMLのアンカー要素<a>を作ってdataURLを渡し、クリックイベントを強制的に呼び出します。 プラグイン呼び出し側の実装(C#) using System; using UnityEngine; using UnityEngine.UI; using System.Runtime.InteropServices; public class ExportTexture : MonoBehaviour { #if UNITY_WEBGL && !UNITY_EDITOR [DllImport("__Internal")] private static extern void ExportTextureJS(string base64); #endif [SerializeField] private RawImage _image; public void OnPressExport() { #if UNITY_WEBGL && !UNITY_EDITOR var texture = (Texture2D)_image.texture; var png = texture.EncodeToPNG(); var base64 = Convert.ToBase64String(png); ExportTextureJS("data:image/png;base64," + base64); #endif } } ブラウザでのみ動くため#if UNITY_WEBGL && !UNITY_EDITOR #endifで括っています。 プラグインの関数のインポート [DllImport("__Internal")] private static extern void ExportTextureJS(string base64); jslibに記述した関数を呼び出せるようにしています。 この時、関数名と引数を一致させる必要があります。 テクスチャのbase64文字列への変換、関数の呼び出し var texture = (Texture2D)_image.texture; var png = texture.EncodeToPNG(); var base64 = Convert.ToBase64String(png); ExportTextureJS("data:image/png;base64," + base64); 今回はRawImageを使用しているので、RawImageのtextureをTexture2Dにキャストして、EncodeToPNG()を使用してbyte[]に変換。さらにそれをSystem.Convert.ToBase64String()に渡してbase64形式に変換しています。 ExportTextureJS()に渡す際に"data:image/png;base64,"と連結しているのは、今回HTMLのアンカー要素に埋め込む手順を踏んでいるためです。(ただここでやる理由はない) 動作物 おわりに 物凄く雑に紹介しましたが、面倒くさいというのが伝わったなら幸いです。 追伸:PlayCanvasならこんなに面倒くさくない リンク・参考記事など GitLab Unity(WebGL)のC#とブラウザのJavaScriptの連携 JS: base64文字列をBlob形式のFileに変換する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プログラミング言語を簡単にまとめる

Java JavaとはC言語ベースに開発された汎用性の高いプログラミング言語。求人需要が多い WEBサービスから業務システム、組み込み、など多岐に渡り開発現場で採用されている。 フレームワークはspring Framework Ruby Rubyとはオブジェクト指向のプログラミング言語。コードはシンプルに書きやすく読みやすい。 WEBサービスやWEBアプリケーションができる。 フレームワークのRuby on Rails。クックパッドや食べログに使用されている PHP PHPとは動的なWEBページを生成することができるサーバーサイドのスクリプト語。HTMLは静的WEBページ。 他のプログラミング言語と比べると文法が比較的容易なので取得しやすい。 フレームワークはLaravel C# マイクロソフト社が開発したオブジェクト指向のプログラミング言語。Javaと似ている。 GUIアプリケーション、WEBアプリケーション、ゲーム開発ができる フレームワークはASP.NET
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C# の単体テストのコードカバレッジの結果をWeb形式で出力する

はじめに 本稿は、C#の単体テストのコードカバレッジの結果をWeb形式で出力する手順の紹介です。 以下の Microsoft の公式ページをもとに私が確認した内容を説明します。 単体テストにコードカバレッジを使用する ざっくり要点を説明すると、Coverlet というカバレッジの計測結果を出力する nuget ライブリを使って XML ファイルを出力してから、ReportGenerator という .NET ツールでその XML ファイルを入力に HTML ファイルのレポートページを出力します。 上記はコマンドラインで実行できます。 カバレッジの出力手順 対象のプログラム 以下のクラスをテスト対象とします。 namespace ConsoleAppSample.Samples { public class Person { public string Name { get; private set; } = string.Empty; public void ChangeName(string name) { Name = name; } public int GetNameLength() { return Name.Length; } } } 上記クラスをテストするためのテストクラスを以下とします。 namespace ConsoleAppSampleTest { public class PersonTest { [Fact] public void ChangeNameTest() { var person = new Person(); person.ChangeName("xxx"); Assert.Equal("xxx", person.Name); } } } 上記のテストクラスのテストフレームワークは、xUnit を利用しています。 C#の有名なテストフレームワークは、MSTest、NUnit、xUnitの3つがありますが、以下の記事の通り xUnit が人気なので、本稿では xUnit を利用します。 レポートの出力手順 coverlet.collector で XML ファイルを出力 まず、単体テスト用のプロジェクトに、coverlet.collector というライブラリを nuget からインストールします。 (テストフレームワークに xUnit を利用している場合は、既定でインストールされています) coverlet.collector をインストールしたら、以下のコマンドをコマンドプロンプトから実行して、カバレッジの結果をXML形式で出力します。 ちなみに、Visual Studio のメニューから [表示] - [ターミナル] で表示するウィンドウからでも実行できます。 dotnet test --collect:"XPlat Code Coverage" 上記のコマンドが成功すれば、以下のファイルパスにカバレッジの結果が記載されたXMLファイルを出力します。 {テストプロジェクトのファイルパス}\TestResults\{guid}\coverage.cobertura.xml ReportGenerator で HTML ファイルを出力 次に、ReportGenerator というツールを用いて HTML ファイルを出力します。 そのために、以下のコマンドで、.NET グローバル ツールとして ReportGenerator をインストールします。 dotnet tool install -g dotnet-reportgenerator-globaltool Reportgenerator をインストールしたら、以下のコマンドで、coverlet.collector が出力した XML ファイルを入力に HTML ファイルのレポートページを出力します。 reportgenerator -reports:"{テストプロジェクトのファイルパス}\TestResults\{guid}\coverage.cobertura.xml" -targetdir:"coveragereport" -reporttypes:Html 上記のコマンドが成功すると、ソリューションフォルダの直下に coveragereport というフォルダが作成され、その中に HTML ファイルのレポートページが作成されます。 レポートページの内容 出力したレポートページの例を記します。 図のように、アセンブリ数、クラス数、メソッド数、テストでカバーしている行数、カバーしていない行数などのメトリクスを記した表と、クラスごとのカバレッジを記した表があります。 上図で、クラス名のリンクをクリックすると、そのクラスのメトリクスを表示します。 さらにスクロールすると、ソースコードが表示されて、カバーしている行とカバーしていない行がどこなのか分かります(カバーしていない行にマウスホバーすると Not Covered と表示されます)。 その他、ReportGenerator についての詳細は公式ページを参照ください。 https://github.com/danielpalme/ReportGenerator まとめ C#の単体テストのコードカバレッジの結果をWeb形式で出力する手順を紹介しました。 CIと連携するのも良さそうです。 ちなみに私は、普段はエンジニアリングマネージャーとして、チームの皆で楽しく開発する施策を色々実施しています。詳しくは以下を参照ください。 Twitterでも開発に役立つ情報を発信しています → @kojimadev
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gtk3アプリ BGMを再生する

System.Media.SoundPlayerが使えない。 Ubuntu .net5ではWindowsで使えたSoundPlayer System.Media.SoundPlayerが使えない。 NAudioというライブラリを使えばMp3が再生できるようだが、.net5サポートしていない。 VLCを実行させてMP3を再生させる。 $ sudo apt-get install vlc /usr/bin/vlc MP3ファイルのパス --play-and-exit --input-repeat=3 再生し終わった後に終了する --play-and-exit リピート回数 --input-repeat=3 VLCの引数 サブスレッド内で実行するとTimer関数内で実行しても長時間実行しても落ちずにすむ using System; using System.Threading.Tasks; public class clsTest2 { //メンバーにする。メモリリークを防止する private System.Diagnostics.Process P; private void _mkSound() { //サブスレッド内で実行する var task = new Task(async () => { string argsStr = " " + "/mp3ファイル" + " --play-and-exit --input-repeat=3"; p = new System.Diagnostics.Process(); p.StartInfo.FileName = "/usr/bin/vlc"; //引数 p.StartInfo.Arguments = argsStr; // コンソール・ウィンドウを開かない p.StartInfo.CreateNoWindow = true; // シェル機能を使用しない p.StartInfo.UseShellExecute = false; // 標準出力をリダイレクト p.StartInfo.RedirectStandardOutput = true; // 標準入力をリダイレクト p.StartInfo.RedirectStandardInput = true; p.StartInfo.RedirectStandardError = true; //プロセスが終了したときに、Exited イベントを発生させるかどうか設定 p.EnableRaisingEvents = true; P.Exited += delegate(object? sender, EventArgs args) { P.Close(); P.Dispose(); }; P.Start(); P.BeginOutputReadLine(); //P.WaitForExit(); }); task.Start(); } } 追記 Gtk3アプリ TreeViewとデータ各種処理2へ続く
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CallerArgumentExpressionによる引数の式表現の文字列化

はじめに 例えばrustやC/C++では、単体テスト等でAssert内の a != b のような表現をエラー内容の一部として出すことができる。 マクロがある言語ではこのような機能が比較的容易に実現可能だが、C#ではマクロを排除していたため、この機能の実現が難しかった(式木やSourceGeneratorを使えば実現は可能?)。 しかし、C#10より、CallerArgumentExpressionという仕様が策定され、dotnet-6.0から使用できるようになったため、容易に実現ができるようになった(検討自体はかなり前からされていたようだが)。 この記事では、それを解説しようと思う。 環境 コンパイラがC#10に対応している必要があるため、dotnet-sdkは6.0以降が必要となる。 言語バージョン(msbuildプロパティのLangVersion)に関しては10以上から使えることになっているが、9指定でも使えたので制限があるのかは不明。 ただし、TargetFrameworkに関しては、netcoreapp3.1以上は特に何もしなくても使える。 それ以下のバージョン(net4x含む)で使いたい場合は、System.Runtime.CompilerServices.CallerArgumentExpressionAttributeをクラスライブラリから拝借する必要がある。 条件としては 名前空間とクラス名が一致(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute) ParameterNameというgetプロパティを持っている コンストラクタに引数としてstringただ一つだけを取り、ParameterNameにセットする とすればOKのようだ。共存は不可なので、TargetFrameworkによる条件付きコンパイルで分ける必要がある。 使い方 呼び出される側 例として、引数に真偽値をとるメソッドvoid Assert(bool isok)を想定する。 これをCallerArgumentExpressionに対応するためには、メソッド側は以下のようにする。 void Assert(bool isok, [CallerArgumentExpression("isok")] string isokExpression = null) { // 実際の処理... } ポイントは、 後ろにCallerArgumentExpressionを属性に付け、型をstringにしたオプション引数を追加する オプション引数にしないとビルドエラーになる CallerArgumentExpressionのコンストラクタには、対応する引数の名前を設定する 名前が間違っていたりすると警告が出る(エラーにはならない) 引数の名前は特に制約はない 対応する引数の型(例ではisokの型)に制約はない となる。 対応しない環境(dotnet-sdk-5.0以下でビルドした場合等)では、ここは普通にnullが入ることに注意。 呼び出し側 呼び出す方は、普通のメソッドと同様に Assert(a == b) のように呼び出せばいい。 どのような値が入るか expressionに何が入るかという所だが、これは引数部分に指定した式がそのままconst文字列として入る。 具体的に言うと、Assert(0 == 1)のように呼び出すと、expressionには"0 == 1"が入り、1.ToString() == "1"のようにした場合はそのまま"1.ToString() == \"1\""が入る。途中にある改行やスペース、コメント等もそのまま保持されるようだ。 ただし、前後の改行や空白、コメントがある場合は削除される。 sharplabの結果を見る限り、これはコンパイル時に解決される。 また、LINQのクエリ式やラムダ式でも受け付け可能。 void LinqCallerExpression(IEnumerable<int> list, [CallerArgumentExpression("list")]string? expr = null) { // expr = 'from i in Enumerable.Range(0, 100) select i' Console.WriteLine($"expr = '{expr}'"); } LinqCallerExpression(from i in Enumerable.Range(0, 100) select i); void FuncExpression(Func<int, int> f, [CallerArgumentExpression("f")]string? expr = null) { // expr = 'i => i + 1' Console.WriteLine($"expr = '{expr}'"); } FuncExpression(i => i + 1); 注意点 コンパイル時に決定されるので、中身の展開はされず、あくまで書いたままの表現で変換される。 また、表現の伝搬は行わないので、 void A(int a, [CallerArgumentExpression("a")] string aExpresssion = null) { B(a); } void B(int b, [CallerArgumentExpression("b")] string bExpression = null) { } とすると、A(int a)の方にどのような形式で渡そうと、bExpressionに入るのは"a"という事になる。 終わりに CallerArgumentExpressionについて解説したが、実際の使いどころというと、 Assertでfalseだった時の例外メッセージに含める ThrowIfNullの時に例外メッセージに含める LINQクエリを使う時にデバッグメッセージに入れる 辺りで、主にトラブルシューティング想定の仕込みかデバッグ用かなと。 いずれにせよ、こういう仕組みがあるということを知っておいて損はないだろう。 参考リンク Microsoftのドキュメント
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Blazor + Azure ActiveDirectory B2C認証時に'Cannot read property 'toLowerCase' of undefined' エラーが出る件

事象 Blazor(WASM) + ASP.Netで作成したアプリをAzure AppSerivceにデプロイし認証をテストしたところ 'Cannot read property 'toLowerCase' of undefined' を吐き出しエラーが発生 ローカルPCでデバッガーで動作したところ問題なく動作 環境 .Net6 + VS2022 + AzureAD B2C dotnet new blazorwasm -au IndividualB2C にてテンプレートを作成しました なお、.Net5でも同様のアプリを作成したことがあり、その時は動作しているので.Net6以降の問題だと思われます 現象 AppServiceにデプロイ後アプリを開きログインボタンを押すと Authentication.razorの @page "/authentication/{action}" @using Microsoft.AspNetCore.Components.WebAssembly.Authentication <RemoteAuthenticatorView Action="@Action" /> <----このコントロール @code{ [Parameter] public string? Action { get; set; } } RemoteAuthenticatorViewが'Cannot read property 'toLowerCase' of undefined' というエラーを吐いているところまで突き止めました。 対応策 ネットを見てみると同様のエラーで困っている人を発見 だが、Net 6 Preview 4とやや古い環境なので参考になるか?と思いつつ試してみたら治りました。 Clientプロジェクト(*.csproj)に <ItemGroup> <TrimmerRootAssembly Include="Microsoft.Authentication.WebAssembly.Msal" /> </ItemGroup> を追加 2. Clientのbin、objフォルダを一度削除後、再ビルドしデプロイ 英語が得意ではないためなぜ?という部分が不明なのですが、対処療法として記述しました。 いずれ根本的な解決がなされることを期待しています
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】1文字ずつ文字を表示する処理

はじめに 今後エンジニアとして活動するか分からないので,ここを一旦の区切りとし,自分が作ったものの中で皆の役に立ちそうなものを共有します。 メイン 今回のスクリプトには,1文字ずつ表示する機能の他に下記のものを実装しました。 自動進行 一時停止 速度調整 この他に,UnityのTextMeshProに対応しています。 using UnityEngine; using TMPro; public class SentenceDisplay : MonoBehaviour { [SerializeField, TextArea] private string[] _sentences; [SerializeField] private float _charaDisplayInterval = 0.1f; [SerializeField] private float _textAutoProgressInterval = 2.0f; [SerializeField] private bool _auto, _highSpeed, _wait; [SerializeField] private float _highSpeedValue = 2; private float _progressTimer; private float _autoProgressTimer; // リッチテキストが進行中かどうか private bool _inRichTextDuringProgress; private string _currentSentence; // 現在の文 private int _currentLineIndex; // 現在の行数 private int _currentCharaCount; // 現在の文字数 private TextMeshProUGUI _textMeshPro; private void Awake() { _textMeshPro = GetComponentInChildren<TextMeshProUGUI>(); } private void Start() { SetSentence(); } private void Update() { var inputAction = Input.GetMouseButtonDown(0); // 文字の表示が完了している if (SentenceDisplayIsComplete()) { if (_auto) _autoProgressTimer += Time.deltaTime; bool autoTimeElapsed = _textAutoProgressInterval <= _autoProgressTimer; // 未表示のテキストが残っている,尚且つ(待機時間が完了,またはボタンが押される) if (_currentLineIndex < _sentences.Length && (autoTimeElapsed || inputAction)) { SetSentence(); } return; } // 文字の表示が完了していない else { _autoProgressTimer = 0; if (inputAction) { // 文字を全て表示 _textMeshPro.text = _currentSentence; _currentCharaCount = _currentSentence.Length; } } var specifiedRangeIn = _currentCharaCount < _currentSentence.Length; if (!_wait && specifiedRangeIn) { // 次に表示する文字によって処理を変更する switch (_currentSentence[_currentCharaCount]) { // RichTextを進行中に case '<': _inRichTextDuringProgress = true; break; // RichTextを終了 case '>': _inRichTextDuringProgress = false; _currentCharaCount++; return; } if (_inRichTextDuringProgress) { _currentCharaCount++; return; } _progressTimer += Time.deltaTime; if (_highSpeed) _progressTimer *= _highSpeedValue; if (_charaDisplayInterval < _progressTimer) { _progressTimer = 0f; _textMeshPro.text = _currentSentence.Substring(0, _currentCharaCount + 1); _currentCharaCount++; } } } private void SetSentence() { // 現在のテキストを更新 _currentSentence = _sentences[_currentLineIndex]; _currentLineIndex++; _textMeshPro.text = string.Empty; _currentCharaCount = 0; } private bool SentenceDisplayIsComplete() => _currentCharaCount == _currentSentence.Length; } かなりシンプルな構造だと思うので,機能追加や修正もしやすいと思います。 他,セットするテキストの配列をJsonから取得すると,使用の幅が広がるかなと(本来ならこの取得方法含めて記事を書く予定だった)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】テキストを1文字ずつ表示する処理

はじめに 今後エンジニアとして活動するか分からないので,ここを一旦の区切りとし,自分が作ったものの中で皆の役に立ちそうなものを共有します。 メイン 今回のスクリプトには,1文字ずつ表示する機能の他に下記のものを実装しました。 自動進行 一時停止 速度調整 この他に,UnityのTextMeshProに対応しています。 using UnityEngine; using TMPro; public class SentenceDisplay : MonoBehaviour { [SerializeField, TextArea] private string[] _sentences; [SerializeField] private float _charaDisplayInterval = 0.1f; [SerializeField] private float _textAutoProgressInterval = 2.0f; [SerializeField] private bool _auto, _highSpeed, _wait; [SerializeField] private float _highSpeedValue = 2; private float _progressTimer; private float _autoProgressTimer; // リッチテキストが進行中かどうか private bool _inRichTextDuringProgress; private string _currentSentence; // 現在の文 private int _currentLineIndex; // 現在の行数 private int _currentCharaCount; // 現在の文字数 private TextMeshProUGUI _textMeshPro; private void Awake() { _textMeshPro = GetComponentInChildren<TextMeshProUGUI>(); } private void Start() { SetSentence(); } private void Update() { var inputAction = Input.GetMouseButtonDown(0); // 文字の表示が完了している if (SentenceDisplayIsComplete()) { if (_auto) _autoProgressTimer += Time.deltaTime; bool autoTimeElapsed = _textAutoProgressInterval <= _autoProgressTimer; // 未表示のテキストが残っている,尚且つ(待機時間が完了,またはボタンが押される) if (_currentLineIndex < _sentences.Length && (autoTimeElapsed || inputAction)) { SetSentence(); } return; } // 文字の表示が完了していない else { _autoProgressTimer = 0; if (inputAction) { // 文字を全て表示 _textMeshPro.text = _currentSentence; _currentCharaCount = _currentSentence.Length; } } var specifiedRangeIn = _currentCharaCount < _currentSentence.Length; if (!_wait && specifiedRangeIn) { // 次に表示する文字によって処理を変更する switch (_currentSentence[_currentCharaCount]) { // RichTextを進行中に case '<': _inRichTextDuringProgress = true; break; // RichTextを終了 case '>': _inRichTextDuringProgress = false; _currentCharaCount++; return; } if (_inRichTextDuringProgress) { _currentCharaCount++; return; } _progressTimer += Time.deltaTime; if (_highSpeed) _progressTimer *= _highSpeedValue; if (_charaDisplayInterval < _progressTimer) { _progressTimer = 0f; _textMeshPro.text = _currentSentence.Substring(0, _currentCharaCount + 1); _currentCharaCount++; } } } private void SetSentence() { // 現在のテキストを更新 _currentSentence = _sentences[_currentLineIndex]; _currentLineIndex++; _textMeshPro.text = string.Empty; _currentCharaCount = 0; } private bool SentenceDisplayIsComplete() => _currentCharaCount == _currentSentence.Length; } かなりシンプルな構造だと思うので,機能追加や修正もしやすいと思います。 他,セットするテキストの配列をJsonから取得すると,使用の幅が広がるかなと(本来ならこの取得方法含めて記事を書く予定だった)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

.Net6 DateOnly型でテーブルをつくる

2年ぶりの記事を書いています。 コロナ禍で環境が大幅に変わったりと色々大変でして, 書く時間がなくて...  はじめに 最近はMicrosoftがすごいと噂を聞きまして, 自分の新サービスをBlazorで書いてみようかとおもってはじめたのですが 結構苦労したのでここに自分用のメモとして残しておきます. 環境は C# .Net6 EFCore6 Blazor SQLServer 事象 EFCore6 から DateOnly 型が導入されたようなのですが /Shared/Models/Building.cs using System.ComponentModel.DataAnnotations; namespace BlazorApp.Shared.Models { public class Building { public DateOnly BuildedDate { get; set; } } } 上記のモデルで、migrateを実行すると dotnet ef migrations add Building エラーになって、Migrationファイルが作成されません。 The property 'Building.BuildedDate' could not be mapped because it is of type 'DateOnly',which is not a supported primitive type or a valid entity type. Either explicitly map this property, or ignore it using the '[NotMapped]' attribute or by using 'EntityTypeBuilder. Ignore' in 'OnModelCreating'. [日本語訳] プロパティ 'Building.BuildedDate'は、サポートされているプリミティブ型または有効なエンティティ型ではないタイプ 'DateOnly'であるため、マップできませんでした。このプロパティを明示的にマップするか、「[NotMapped]」属性を使用するか、「OnModelCreating」の「EntityTypeBuilder.Ignore」を使用して無視します。 このエラーに従ってると目的が達成できなくなりますね.... ひとまずDateTime型でMigrationファイルをつくる /Shared/Models/Building.cs - public DateOnly BuildedDate { get; set; } + public DateTime BuildedDate { get; set; } dotnet ef migrations add Building 上記を実行すると /Server/Migrations/2022XXXXX_Building.cs namespace BlazorApp.Server.Migrations { public partial class InitialTables : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( name: "Buildings", columns: table => new { BuildingDate = table.Column<DateTime>(type: "datetime2", nullable: false), 以下略 次に, DatetimeをDateOnlyに変更 /Server/Migrations/2022XXXXX_Building.cs - BuildingDate = table.Column<DateTime>(type: "datetime2", nullable: false), + BuildingDate = table.Column<DateOnly>(type: "date", nullable: false), /Shared/Models/Building.cs - public DateTime BuildedDate { get; set; } + public DateOnly BuildedDate { get; set; } この段階でMigrateしても上でみたおなじエラーがでます。 そこで、以下のファイルを修正します /Server/Data/AppDbContext.cs using BlazorApp.Shared.Models; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.ChangeTracking; + using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace BlazorApp.Server.Data { public class AppDbContext : DbContext { public AppDbContext(DbContextOptions options) : base(options) { } + + protected override void ConfigureConventions(ModelConfigurationBuilder builder) + { + builder.Properties<DateOnly>() + .HaveConversion<DateOnlyConverter>() + .HaveColumnType("date"); + builder.Properties<DateOnly?>() + .HaveConversion<NullableDateOnlyConverter,NullableDateOnlyComparer>() + .HaveColumnType("date"); + } public DbSet<Building> Buildings => Set<Building>(); } + + /// <summary> + /// Converts <see cref="DateOnly" /> to <see cref="DateTime"/> and vice versa. + /// </summary> + public class DateOnlyConverter : ValueConverter<DateOnly, DateTime> + { + /// <summary> + /// Creates a new instance of this converter. + /// </summary> + public DateOnlyConverter() : base( + d => d.ToDateTime(TimeOnly.MinValue), + d => DateOnly.FromDateTime(d)) + { } + } + + /// <summary> + /// Compares <see cref="DateOnly" />. + /// </summary> + public class DateOnlyComparer : ValueComparer<DateOnly> + { + /// <summary> + /// Creates a new instance of this converter. + /// </summary> + public DateOnlyComparer() : base( + (d1, d2) => d1 == d2 && d1.DayNumber == d2.DayNumber, + d => d.GetHashCode()) + { + } + } + + /// <summary> + /// Converts <see cref="DateOnly?" /> to <see cref="DateTime?"/> and vice versa. + /// </summary> + public class NullableDateOnlyConverter : ValueConverter<DateOnly?, DateTime?> + { + /// <summary> + /// Creates a new instance of this converter. + /// </summary> + public NullableDateOnlyConverter() : base( + d => d == null + ? null + : new DateTime?(d.Value.ToDateTime(TimeOnly.MinValue)), + d => d == null + ? null + : new DateOnly?(DateOnly.FromDateTime(d.Value))) + { } + } + + /// <summary> + /// Compares <see cref="DateOnly?" />. + /// </summary> + public class NullableDateOnlyComparer : ValueComparer<DateOnly?> + { + /// <summary> + /// Creates a new instance of this converter. + /// </summary> + public NullableDateOnlyComparer() : base( + (d1, d2) => d1 == d2 && d1.GetValueOrDefault().DayNumber == d2.GetValueOrDefault().DayNumber, + d => d.GetHashCode()) + { + } + } } こちらを参照しました 他にも、以下のやり方があるみたいです。 Install-Package TinyHelpers.EntityFrameworkCore このあと dotnet ef migrations add AlterDateOnly dotnet ef database update これで, カラムがDateOnly型に変更できました 以下のファイルが追加されます Service/Migrations/2022XXX_AlterDateOnly.cs using Microsoft.EntityFrameworkCore.Migrations; #nullable disable namespace BlazorApp.Server.Migrations { public partial class AlterDateOnly : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.AlterColumn<DateTime>( name: "BuildedDate", table: "Buildings", type: "date", nullable: false, oldClrType: typeof(DateTime), oldType: "datetime2"); } } } うまくいってるんだけど.... 以下のファイルをのぞいてみると Server/Migrations/AppDbContextModelSnapshot.cs using BlazorApp.Server.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; namespace BlazorApp.Server.Migrations { [DbContext(typeof(AppDbContext))] partial class AppDbContextModelSnapshot : ModelSnapshot { protected override void BuildModel(ModelBuilder modelBuilder) { modelBuilder.Entity("ChintaiApp.Shared.Models.Building", b => { b.Property<DateTime>("BuildedDate") .HasColumnType("date"); 以下略 DateTime のままなんですよね... これでうまくいってるけど、なぜなんでしょうね。。。 最後に 日本語でこれに関する記事が見当たらなかったので, 記事を書きました. Date型すら標準機能で作れない.Netってフレームワークの完成度が低い気がして.... DateOnly型がないときは, どうやって開発してたんですかね? 不思議だ 次は Seedについて書きます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む