WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

DataRowExtensions.Field メソッド

by WebSurfer 2022年12月16日 17:45

DataRowExtensions.Field メソッドを使って DataRow から値を取得すると、対象のセルの値が DBNull の場合は null を返してくれるという話を書きます。

例えば、以下のような NULL が含まれる SQL Server のテーブルから SqlDataAdapter クラスの Fill メソッドを使って DataTable を作成したとします。

SQL Server のテーブル

そうすると SQL Server DB のフィールドが NULL の場合、DataTable の DataRow の当該セルには DBNull オブジェクトへの参照が代入されます。

下の画像は、上の SQL Server のテーブルの 3 行目(Name と Date が NULL)から取得した DataRow を Visual Studio 2022 のデバッガで開いても見たもので、SQL Server の NULL は DataRow では DBNull になっています。

DBNull

この DataTable の DataRow の例えば Name 列から値を取得する場合、DataRow.Item プロパティを使って以下のようにすると、

string name = (string)row["Name"];

当該セルに DBNull が入っている場合は InvalidCastException がスローされ、"Unable to cast object of type 'System.DBNull' to type 'System.String'." というエラーメッセージが出ます。

なので、row["Name"] が DBNull か否かを判定して処理を分けるようなコードを書くことになります。例えば以下のようにする必要があります。

string name = (row["Name"] is DBNull) ? null : (string)row["Name"];

一方、DataRowExtensions.Field メソッドを使って以下のよう書くと、当該セルが DBNull の場合は null が返されます。キャストも不要です。

string name = row.Field<string>("Name");

検証に使ったコードを以下に載せておきます。Visual Studio 2022 を使って作った .NET 6.0 のコンソールアプリケーションです。デフォルトで null 許容参照型が有効になりますので string ではなく string? としています。

using System.Data;
using System.Data.SqlClient;

var selectQuery = "SELECT [Id],[Name],[Price],[Date] FROM [Sample]";
var connString = "接続文字列";
var table = new DataTable();
using (var connection = new SqlConnection(connString))
{
    using (var command = new SqlCommand(selectQuery, connection))
    {
        var adapter = new SqlDataAdapter(command);
        adapter.Fill(table);
    }
}

var list = table.AsEnumerable()
           .Select(row => new Sample
           {
               Id = row.Field<int>("Id"),
               Name = row.Field<string?>("Name"),
               Price = row.Field<decimal?>("Price"),
               Date = row.Field<DateTime?>("Date")
           })
           .ToList();

foreach (var c in list)
{
    Console.WriteLine($"ID: {c.Id}, Name: {c.Name ?? "null"}, " +
                      $"Price: {c.Price?.ToString() ?? "null"}, " +
                      $"Date: {c.Date?.ToString() ?? "null"}");
}


public class Sample
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public decimal? Price { get; set; }
    public DateTime? Date { get; set; }
}

上のコードの実行結果は以下の通りとなります。

実行結果

Tags: , , ,

ADO.NET

.NET の多次元配列の JSON シリアル化

by WebSurfer 2022年12月14日 18:06

Newtonsoft.Json のシリアライザなら .NET の多次元配列を JSON 文字列にシリアライズできるという話を書きます。何を今さらと言われそうですが。(汗)

Newtonsoft.Json

System.Text.Json 名前空間のシリアライザは多次元配列の JSON シリアル化をサポートしていないようで、試してみましたが System.NotSupportedException がスローされ "The type 'System.String[,,]' is not supported." というエラーメッセージが出ます。(ジャグ配列ならサポートしていることは確認しました)

なので、調べもしないで Newtonsoft.Json も同じだと思っていたのですが、Json.NET 4.5 Release 8 – Multidimensional Array Support, Unicode Improvements によると 10 年も前から多次元配列のシリアライズをサポートしているとのことです。

Microsoft のドキュメント「Newtonsoft.Json と System.Text.Json を比較して、System.Text.Json に移行する」にもいろいろ書いてありますが、多次元配列のサポートについては言及してないです。「さまざまな型のサポート ⚠」に含むのでしょうか?

実際にコードを書いて、Newtonsoft.Json が多次元配列とジャグ配列の両方のシリアライズ/デシリアライズをサポートしていることは確認しました。以下に検証に使ったコードを載せておきます。.NET Framework 4.8.1 のコンソールアプリです。

using Newtonsoft.Json;
using System;

namespace ConsoleAppArrayJson
{
    internal class Program
    {
        static void Main(string[] args)
        {
            // 多次元配列
            var mdArray = new string[30, 12, 31];
            for (int i = 0; i < 30; i++)
            {
                for (int j = 0; j < 12; j++)
                {
                    for (int k = 0; k < 31; k++)
                    {
                        mdArray[i, j, k] = $"e-{i}-{j}-{k}";
                    }
                }
            }

            // ジャグ配列
            var jArray = new string[30][][];
            for (int i = 0; i < 30; i++)
            {
                jArray[i] = new string[12][];
                for (int j = 0; j < 12; j++)
                {
                    jArray[i][j] = new string[31];
                    for (int k = 0; k < 31; k++)
                    {
                        jArray[i][j][k] = $"e-{i}-{j}-{k}";
                    }
                }
            }

            // 多次元配列を JSON 文字列にシリアライズ
            var json1 = JsonConvert.SerializeObject(mdArray);

            // ジャグ配列を JSON 文字列にシリアライズ
            var json2 = JsonConvert.SerializeObject(jArray);

            // json1 == json2 は true になる
            Console.WriteLine($"json1 == json2: {json1 == json2}");

            // JSON 文字列を多次元配列にデシリアライズ
            var array1 = JsonConvert.DeserializeObject<string[,,]>(json1);
            Console.WriteLine($"array1[0,1,2]: {array1[0, 1, 2]}");

            // JSON 文字列をジャグ配列にデシリアライズ
            var array2 = JsonConvert.DeserializeObject<string[][][]>(json2);
            Console.WriteLine($"array2[0][1][2]: {array2[0][1][2]}");
        }
    }
}

上のコードの実行結果は以下のようになります。

実行結果

Tags: , , ,

.NET Framework

JavaScript の async / await

by WebSurfer 2022年11月25日 18:50

JavaScript の async / await の使い方について思い違いをしていました。また同じミスを犯さないように、どういうことだったかを以下に書いておきます。

例として、GET 要求を受けると 2 秒後に以下の JSON 文字列を返す Web API から fetch を使って応答を取得するサンプルを書きました。

{"id":5,"name":"スライムマン"}

JavaScript のコードは以下のようにし、ボタンクリックなどのイベントで asyncTest を実行します。

const fetchHero5 = async () => {
    const response = await fetch("/api/values/5")
    const data = await response.json();
    console.log(data.name);
};

const writeLog = () => {
    console.log("writeLog の実行");
}

// 同期的に順番に実行される
function asyncTest()
{
    fetchHero5();
    writeLog();
}

fetchHero5 の中で await で待機しているので、asyncTest の中の fetchHero5 の呼び出しでまず Web API からの応答の "スライムマン" がコンソールに書き込まれ、その後 writeLog が呼び出されて "writeLog の実行" が書き込まれると思っていたのですが、そうではなかったです。

結果は以下の通りで、思っていたのと逆の順序でした。

コンソール書き込み

asyncTest は同期関数でその中の fetchHero5() と writeLog() は同期的に実行されます。つまり、同期的に順番にまず fetchHero5 が呼ばれ、fetchHero5 の完了後 writeLog が呼ばれるという動きになります。

ここで、fetchHero5 は async が付与された非同期関数であるということが、自分が思っていた結果と違ったことになった原因のようです。

現代の JavaScript チュートリアル の記事 Async/await に "async は関数が promise を返すことを保証し、非 promise をその中にラップします" という説明があります。

ということは、fetchHero5 が呼ばれると即 Promise が返され、その時点で fetchHero5 は完了とみなされて、次の writeLog が呼ばれてコンソールに "writeLog の実行" と書き込んだということのようです。

その後、Web API からの応答の処理は fetchHero5 から返された Promise が行うようです。Promise オブジェクトが fetch で Web API を呼んで 2 秒後に応答が返ってきてから "スライムマン" と書き込んだということだと思います。

fetchHero5() で "スライムマン" と書き込んだ後で、writeLog() で "writeLog の実行" と書き込む(上の画像の順序を反対にする)には、asyncTest に async を付与してを非同期関数にし、その中の fetchHero5 に await を付与してそこで待機するようにします。

すなわち以下のようなコードにします。

async function asyncTest()
{
    await fetchHero5();
    writeLog();
}

結果は以下の通りとなります。

コンソール書き込み

上のコード例で、一番下位の関数 fetch, json は Promise を返します。その上位の関数 fetchHero5 も async が付与されているので Promise を返します。そういう形で Promise が下位から上位に伝搬して行くので、上のコード例でのように最上位の関数 asyncTest を非同期にして、そこで await を使って fetchHero5 の完了を待機すると期待したようになるということのようです。

.NET アプリの Microsoft のドキュメント「非同期プログラミングのベスト プラクティス」には "上から下に (または下から上に) 非同期コードが他の非同期コードを呼び出す、または呼び出される場合に最もうまく機能する" ということがに書かれています。

それは JavaScript でも同じことなのかもしれません。

(自分用のメモ: 検証に使ったのは VS2022 の MvcCore6App2 プロジェクトの Home/Api)

Tags: , , ,

JavaScript

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar