WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

null 許容許容参照型と EF Code First

by WebSurfer 12. May 2022 15:15

Visual Studio 2022 のテンプレートを使って .NET 6.0 アプリのプロジェクトを作ると、デフォルトで「Null 許容」オプションが有効にされています。その状態での EF Code First による SQL Server データベース生成に新発見 (自分が知らなかっただけですが) があったので備忘録として書いておきます。

「Null 許容」オプション

EF Code First でデータベースを生成すると、元となるコードのクラス定義の中のプロパティの型と付与する属性によって生成されるデータベースの列の型と NULL 可/不可が決まってきます。新発見というのは「Null 許容」オプションの有効化によって、生成される列の NULL 可/不可が以前と違ってくるということです。

値型の場合は「Null 許容」オプションの有効/無効は関係なく結果は以前と同じになります。すなわちデフォルトでデータベースの当該列は NULL 不可になります。NULL 可にしたい場合は Nullable<T> 型(例えば int? とか DateTime? など)をプロパティの型に使います。

違うのは参照型の場合です。「Null 許容」オプションが有効にされていると、例えばプロパティの型を string とすると当該データベースの列は NULL 不可に、string? とすると NULL 可になります。

以前 (null 許容参照型が使えない時代または「Null 許容」オプションが無効) は string 型は NULL 可になりました。NULL 不可にしたい場合は当該プロパティに RequiredAttribute 属性を付与していました。

実際にアプリを作って試してみましたので具体例を以下に書きます。

Visual Studio 2022 のテンプレートでフレームワークを .NET 6.0 としてコンソールアプリを作成します。その状態で上の画像のように「Null 許容」オプションが有効化されています。

NuGet パッケージ Microsoft.EntityFrameworkCore.SqlServer と Microsoft.EntityFrameworkCore.Tool をインストールします。前者は SQL Server 用の EF Core 本体、後者は Migration 操作を行うためのツールです。

NuGet パッケージ

Microsoft のドキュメント「新しいデータベースの Code First」と同様なコンテキストクラスとエンティティクラスを実装します。コードは以下の通りです。(null 許容参照型対応のため = null! を追加するなどしていますが基本は同じ)

public class Blog
{
    public int BlogId { get; set; }
    public string Name { get; set; } = null!;

    public virtual List<Post>? Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; } = null!;

    public string? Content { get; set; }

    public int BlogId { get; set; }
    public virtual Blog? Blog { get; set; }
}

public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; } = null!;
    public DbSet<Post> Posts { get; set; } = null!;

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (!optionsBuilder.IsConfigured)
        {
            optionsBuilder.UseSqlServer("接続文字列");
        }
    }
}

上のコードの Blog クラスの Name プロパティ、Post クラスの Title, Content プロパティの型を string / string? と使い分けている点に注目してください。これをベースに Migration 操作によって SQL Server にデータベースを生成した結果が以下の画像です。

SQL Server にデータベース生成

プロパティが string 型になっている Name, Title プロパティに対応する SQL Server データベースの Name 列と Title 列は NULL 不可に、string? 型になっている Content プロパティに対応するデータベースの Content 列は NULL 可になっています。

以前 (null 許容参照型が使えない時代または「Null 許容」オプションが無効) は、上にも書きましたが、プロパティの型が参照型の場合はデータベースの当該列は NULL 可になります。NULL 不可にする場合は RequiredAttribute 属性を付与します。試しに、以下のように #nullable disable を付与したクラス定義を追加し、Migration 操作で SQL Server に Products テーブルを生成してみました。

#nullable disable
public class Product
{
    public int ProductId { get; set; }

    [Required]
    public string ProductName { get; set; }

    public string Decription { get; set; }

    [Column(TypeName = "decimal(18,4)")]
    public decimal UnitPrice { get; set; }
}

結果は以下の通りです。プロパティの型が string の ProductName, Description に該当する列の NULL 可/不可を見てください。プロパティに RequiredAttribute 属性を付与しないと NULL 可になります。

#nullable disable で生成

既存のデータベースからリバースエンジニアリングで生成したコンテキストクラス、エンティティクラスも「Null 許容」オプションが有効化されている場合は上の Blog, Post クラスと同様になります。

試しに Microsoft のサンプルデータベース Northwind の Categories テーブルからリバースエンジニアリングでコンテキストクラス、エンティティクラスを生成してみました。Categories テーブルは以下の内容になっています。CategoryName 列が NULL 不可、Description 列が NULL 可になっているところに注目してください。

Northwind の Categories テーブル

上の Categories テーブルからリバースエンジニアリングを使ってデータアノテーション属性を含めてエンティティクラスを生成すると以下の通りとなります。データベースのテーブルの各列の NULL 可/不可と、生成されたクラス定義の各プロパティの型を見てください。データベースの列が NULL 可の場合はプロパティの型は null 許容(? を付与されている)となっています。

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;

namespace MvcCore6App2.Models
{
    [Index("CategoryName", Name = "CategoryName")]
    public partial class Category
    {
        public Category()
        {
            Products = new HashSet<Product>();
        }

        [Key]
        [Column("CategoryID")]
        public int CategoryId { get; set; }
        [StringLength(15)]
        public string CategoryName { get; set; } = null!;
        [Column(TypeName = "ntext")]
        public string? Description { get; set; }
        [Column(TypeName = "image")]
        public byte[]? Picture { get; set; }

        [InverseProperty("Category")]
        public virtual ICollection<Product> Products { get; set; }
    }
}

この記事の本題の話は以上ですが、このクラス/プロパティ定義をそのまま ASP.NET MVC の Model として使った場合、ユーザー入力の検証がどうなるのかが気になります。それも調べましたので以下に書いておきます。

以前は string 型のプロパティに該当するテキストボックスへのユーザー入力を必須とする場合、その項目の当該プロパティに RequiredAttribute 属性を付与していました。それにより未入力の場合は検証 NG となってエラーメッセージが表示されます。

「Null 許容」オプションが有効化されている場合、string 型の項目は必須入力になるはずですが、上のコードではプロパティには RequiredAttribute 属性は付与されていません。そこはどうなるのかが疑問でした。

実際にアプリを動かして試してみると、RequiredAttribute 属性は付与されてなくても、未入力の場合は検証 NG となってエラーメッセージが表示されました。

View から生成される html ソースを調べてみると、当該 input 要素には data-val-required="The xxx field is required." という検証属性が付与され、未入力の場合は検証機能が働いてエラーメッセージが表示されるようになっていました。

エラーメッセージを任意のものに変えたい場合は、プロパティに RequiredAttribute を付与して ErrorMessage にメッセージを設定します。そうすると data-val-required 属性に設定される文字列が ErrorMessage に設定したものに置き換わります。

このあたりは先の記事「int 型プロパティの検証、エラーメッセージ」に書いた int 型の場合と同じになっているようです。

最後にもう一つ、こんなことをする人はいないかも���れませんが、string? 型のプロパティ(null 可)に RequiredAttribute 属性を付与(null 不可)するとどうなるかを書いておきます。

そのような設定をすると、EF Core を使って SQL Server からデータを取得する際当該列のデータに NULL が含まれていると、

System.Data.SqlTypes.SqlNullValueException: Data is Null. This method or property cannot be called on Null values.

・・・という例外がスローされます。下の画像を見てください。

SqlNullValueException

Microsoft のドキュメントによると、SqlNullValueException は「System.Data.SqlTypes 構造体の Value プロパティが null に設定されている場合にスローされる例外」ということだそうです。

メカニズムは不明ですが、string? 型のプロパティに RequiredAttribute 属性を付与し EF Core で SQL Server からデータを取得してくるときに、データに NULL があると「SqlTypes 構造体の Value プロパティが null に設定」ということになるようです。

SqlNullValueException の説明と RequiredAttribute 属性を設定したことが結びつかなくて、解決に悩んで無駄な時間を費やすことになるかもしれませんので注意してください。(実は自分がそうでした)

Tags: , , ,

ADO.NET

CalendarExtender の利用例

by WebSurfer 7. May 2022 14:04

Ajax Control Toolkit の CalendarExtender を、下の画像のようにテキストボックスなし(実際は非表示)で、img 要素(普通のボタンでも可)をクリックして表示し、ユーザーがカレンダー上で選択した日付をサーバー側に送信する方法を書きます。

CalendarExtender

今時 CalendarExtender を使うことはあまりなさそうで、しかもこの記事に書いてあるような特殊な使い方をすることはまずなさそうですが、せっかく考えたので備忘録として書いておくことにしました。

CalendarExtender の基本的な使い方については、DevExpress のデモページ「Calendar Demonstration」と、@IT の記事「Calendarコントロールで日付入力ボックスを定義するには?」が参考になると思いますので一読されることをお勧めします。

カレンダーを img 要素(または普通のボタン)をクリックして表示する方法ですが、DevExpress のデモページの「Calendar with an associated button」と @IT の記事の「補助カレンダーを任意のボタンから起動する」を見てください。

この記事では、DevExpress のデモページと同じ画像を img 要素を使って表示し、CalendarExtender の PopupButtonID プロパティに img 要素の id を設定して、その img 要素をクリックしてカレンダーを表示します。img 要素に代えて <button type="button"> も使えます。ただし、クリックするとポストバックが起こる Button コントロールなどを使う場合は、ポストバックが起こらないように処置する必要があるので注意してください。

CalendarExtender の対象となるテキストボックスを指定する TargetControlID の設定は必須です。なので必ずテキストボックスをページに配置する必要があります。非表示にしたい場合は CSS で display: none; を設定してください。TextBox.Visible プロパティを false に設定するのはダメです。

カレンダーの表示位置はデフォルトでは自動的にテキストボックス直下になります。テキストボックスを非表示にする場合は自力で JavaScript を書いてカレンダーの表示位置を設定する必要があります。

その方法は Code Project の記事 Ajax CalendarExtender pop up position の Solution 1 を参考にさせてもらいました。

最後に、ユーザーが選択した日付をサーバーに送信する方法ですが、選択した時点で日付はテキストボックスに自動的に入力されますので、何らかの手段でポストバックをかければサーバー側で Text プロパティから取得できます。

CalendarExtender の OnClientDateSelectionChanged プロパティに JavaScript のリスナを設定してそこで取得し、Ajax を使ってサーバーに送信することもできます。

以上を実装したサンプルコードを下に載せておきます。この記事の上の画像を表示したものです。

CalendarExtender.aspx.cs

using System.Web.UI;

namespace WebForms1
{
    public partial class CalendarExtender : System.Web.UI.Page
    {
        protected void Page_Init(object sender, EventArgs e)
        {
            // マスターページに配置した ScriptManager のプロパティ
            // EnableScriptGlobalization を true に設定する
            ScriptManager sm = ScriptManager.GetCurrent(this.Page);
            sm.EnableScriptGlobalization = true;
        }

        protected void Page_Load(object sender, EventArgs e)
        {
            // ユーザーが選択した日付を取得・表示
            Label1.Text = TextBox1.Text;
        }
    }
}

CalendarExtender.aspx.cs

<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master"    
    AutoEventWireup="true" CodeBehind="CalendarExtender.aspx.cs" 
    Inherits="WebForms1.CalendarExtender" %>

<asp:Content ID="Content1" ContentPlaceHolderID="HeaderContent" runat="server">

    <script type="text/javascript">
        // Calendar の表示位置を img 要素直下に調整する
        function onCalendarShown(sender, args) {
            var img = $get('image1');

            sender._popupDiv.parentElement.style.top =
                img.offsetTop + img.height + 'px';
            sender._popupDiv.parentElement.style.left =
                img.offsetLeft + 'px';
        }

        // ユーザーが日付を選択すると発生するイベントのリスナ
        function sendResult(sender) {
            // 選択した日付は以下のようにして取得できる
            var selectedDate = sender.get_selectedDate();

            // ポストバックすればサーバー側では TextBox1.Text 
            // プロパティから選択した日付を取得できる
            var btn = $get('<%=Button1.ClientID %>');
            btn.click();
        }
    </script>

    <style type="text/css">
        /* TextBox1 を非表示にする */
        .style1 {
            display: none;
        }
    </style>

</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <h3>Ajax Control Toolkit CalendarExtender</h3>    
   
    <asp:TextBox ID="TextBox1" runat="server" CssClass="style1">
    </asp:TextBox>

    <%--PopupButtonID に設定するのはサーバーコントロールでなくても可--%>
    <img src="images/Calendar_scheduleHS.png" id="image1" alt="" />

    <ajaxToolkit:CalendarExtender ID="CalendarExtender1" 
        runat="server" 
        PopupButtonID="image1"
        TargetControlID="TextBox1" 
        DaysModeTitleFormat="yyyy年M月"
        TodaysDateFormat="yyyy年M月d日"
        Format="yyyy/MM/dd"
        OnClientDateSelectionChanged="sendResult" 
        OnClientShown="onCalendarShown" />

    <br />
    <%--ポストバックするための Button コントロール--%>
    <asp:Button ID="Button1" runat="server" Text="PostBack" />

    <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
</asp:Content>

なお、本題とは関係ないことですが、カレンダー上の日付の表示はデフォルトではなく、先の記事「CalendarExtender の日付の表示」に書いたように日本語形式にしています。

Tags: ,

AJAX

React で日本語を使うとコンパイルエラー / 文字化け

by WebSurfer 10. April 2022 14:26

Visual Studio 2019 / 2022 の「React.js での ASP.NET Core」のテンプレートで作ったプロジェクトで、自動生成された Home.js とか Counter.js などに以下のように <p>日本語</p> という日本語を書き加えると Unexpected character '�' というエラーメッセージが出てコンパイルエラーになります。文字によってはコンパイルは通りますが文字化けします。

Home.js に日本語追加

フレームワークを .NET Core 3.1 としてプロジェクトを作って同様なことをすると、ブラウザに空白画面が表示されるだけなので何が起こったのか分からないと思いますが、.NET 6.0 とするとコマンドラインに以下のように表示されます。(コンパイルエラーにならない場合は文字化けしてブラウザに表示されるので分かります)

コマンドラインのエラーメッセージ

ブラウザにも以下のようにエラーメッセージが表示されます。

ブラウザのエラーメッセージ

文字によってはコンパイルは通り、ブラウザには結果が表示されますが、日本語の部分は文字化けします。 (コンパイルが通るもの / 通らないものの違いは不明です)

原因は Visual Studio で編集して保存する時ファイルの文字コードが Shift_JIS になってしまうからです。下の画像は Home.js をバイナリエディタで表示したものですが「日本語」の部分が 93 FA 96 78 8C EA と Shift_JIS になっているのが分かるでしょうか。

バイナリエディタで表示

何故そんなことになってしまうかというと、Visual Studio の設定がそうなっているからです。下の画像を見てください。Home.js の「保存オプションの詳細設定」ですが、そこで[エンコード(E)]が「日本語 (シフト JIS) - コードページ 932」になっているのに注目してください。

保存オプションの詳細設定

このため日本語を書き込んで保存すると Shift_JIS になってしまい、UTF-8 を期待している React ではコンパイルエラーまたは文字化けするということです。Home.js 以外にも ClientApp/src フォルダのほとんどのファイルがその設定になっていますので注意してください。

解決策は、下の画像のように[エンコード(E)]を「Unicode (UTF-8 シグネチャなし) - コードページ 65001」として上書きすることです。

保存オプションの詳細設定

または、メモ帳で問題の .js ファイルを開いて[文字コード(E)]を BOM なし UTF-8 に設定して上書きすることでも OK です。一旦メ��帳で BOM なし UTF-8 で保存してしまえば、その後は Visual Studio で .js ファイルを開いて日本語を書き込んでも UTF-8 で保存されるのでこの問題は起こりません。(詳しい仕組みは分かりませんが、Visual Studio の「保存オプションの詳細設定」設定に反映されるようです)

なお、BOM 付き UTF-8 にすると下の画像のように Unexpected Unicode BOM (Byte Order Mark) という警告が出ますので注意してください。(「保存オプションの詳細設定」でシグネチャ付きというのは BOM 付きのことですので注意)

BOM による警告

BOM 付きでもコンパイルエラーにはならず文字化けもしませんが、そこは警告に従って BOM なし UTF-8 にするのが良さそうです。

Visual Studio を操作して[追加(D)]⇒[新しい項目(W)...]で「JavaScript ファイル」を選んで作成すると BOM 付き UTF-8 になることに注意してください。コンパイルは通りますが、やはり Unexpected Unicode BOM (Byte Order Mark) という警告が出ます。

React プロジェクトでは ClientApp/src フォルダの .js ファイルだけでなく、Program.cs や Startup.cs でも保存オプションはデフォルトで「日本語 (シフト JIS) - コードページ 932」になっています。そこは ASP.NET Core プロジェクトでも同じです。

Program.cs や Startup.cs は Visual Studio が読んで処理を行いますので正しく文字コードが解釈され Shift_JIS でも問題は出ないはずです。ただ、ちょっと特殊な話ですが問題事例はありました。興味がありましたらTeratail のスレッド「asp.net.coreでの文字化けを解決したい。」を見てください。これはブラウザに送信する際は UTF-8 になっていたが、応答ヘッダやメタタグで文字コードの指定をしてなかったので、ブラウザが Shif_JIS として処理して文字化けを起こしたというもので、開発者のミスに近いものですが。

この記事の React の .js ファイルの話は、昔々 .NET Core 1 時代の MVC アプリで経験した「ASP.NET Core で文字化け」の .cshtml ファイルの件と似た話です。Microsoft も文字コードの問題はなかなか気が付かないのでしょうね。

Tags: , , , ,

React

About this blog

2010年5月にこのブログを立ち上げました。その後ブログ2を追加し、ここはプログラミング関係、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  May 2022  >>
MoTuWeThFrSaSu
2526272829301
2345678
9101112131415
16171819202122
23242526272829
303112345

View posts in large calendar