WebSurfer's Home

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

.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

PostgreSQL とデータソース構成ウィザード

by WebSurfer 2022年10月5日 16:14

PostgreSQL の既存のデータベースから Visual Studio 2022 のデータソース構成ウィザードを利用して型付 DataSet / DataTable + TableAdapter を作ることができます。それを使って、DB のレコードの一覧表示・編集ができる Windows Forms + DataGridView アプリを容易に作成できます。

Windows Forms + GataGridView アプリ

SQL Server の場合は当たり前の話なので何をいまさらと言われるかもしれませんが、先日初めて PostgreSQL をインストールしたので試してみた次第です。

(ASP.NET MVC5 アプリで Entity Framework 6 を使って PostgreSQL の既存の DB の CRUD を行う場合については、先の記事「PostgreSQL で EF6 DB First」に書きましたので、興味があれば見てください)

この記事を書いた時の環境は以下の通りです。

  • PostgreSQL 14.4
  • Visual Studio Community 2022 17.3.5
  • Npgsql PostgreSQL Integration 4.1.12
  • Npgsql 6.0.7
  • .NET Framework 4.8
  • Windows フォームアプリケーション (.NET Framework)

Visual Studio で PostgreSQL に対してデータソース構成ウィザードが使えるようにするには Npgsql PostgreSQL Integration という拡張機能の追加が必要です。下の画像のバージョン 4.1.12 は Visual Studio 2022 用にリリースされたものだそうです。

Npgsql PostgreSQL Integration

拡張機能の追加後、Microsoft のドキュメント「新しいデータ ソースの追加」のようにデータソース構成ウィザードを起動すれば、「データ接続の選択」で PostgreSQL Database を選択できるようになります。

データ接続の選択

以下のように PostgreSQL への接続情報を設定して接続できれば、

接続情報を設定

そのあとは SQL Server とほぼ同様な手順で DataSet(xsd ファイル)を作成できます。

DataSet(xsd ファイル)

xsd ファイルが作成できると、デザイン画面でデータソースウィンドウに DataSet / DataTable が表示されるので (下の画像で MovieDataSet / Movie)、DataTable (Movie) の DataGridView を選択してから Form にドラッグ&ドロップします。

データソースウィンドウの DataSet / DataTable

上の画像はドラッグ&ドロップして必要なコードがすべて生成された後、DataGridView.Dock プロパティを Fill に設定したものです。

次に NuGet から Npgsql をインストールします。

NuGet から Npgsql をインストール

その後ソリューションをビルドして実行すればこの記事の一番上の画像のようなアプリが動きます。DataGridView を編集した結果も期待通り DB に反映されます。

ただ、微妙なところがあって、なぜか Npgsql のインストールのタイミングが問題で、自分の環境で試した時は以下の順番で行う必要がありましたので注意してください。

  1. データソース構成ウィザードを起動し型付 DataSet / DataTable + TableAdapter を生成
  2. 「データソース」ウィンドウから作成した DataTable を Form にドラッグ&ドロップ
  3. NuGet から Npgsql をインストール
  4. ソリューションをビルド

想像ですが、データソース構成ウィザードが使う Npgsql と NuGet からインストールする Npgsql やその他の関係 dll とのバージョンの不整合が問題ではないかと思われます。

Tags: , ,

.NET Framework

Chart の X 軸マージン

by WebSurfer 2022年9月9日 18:44

Chart の X 軸にはデフォルトでグラフの左右にマージンが表示されます。下の Chart サンプルの画像を見てください。X 軸の端にはデータはないのですが自動的に 0 と 10 というラベルが追加されています。

Axis Margins

データがないのに X 軸にラベルが表示されること、さらには、例えば数字が月を表しているような場合は存在しない月「0」を表示するのは好ましくなさそうです。

必要のない X 軸のマージンやラベルを表示しないようにする方法を調べましたので、以下に備忘録として書いておきます。

(1) Axis Margins

簡単なのは ChartArea.AxisX.IsMarginVisible プロパティを false に設定することです。上の画像の Chart サンプルで false に設定すると以下のようになります。

IsMarginVisible を false に設定

上の画像のような折れ線グラフのような場合はこれが簡単でよさそうです。

しかし、棒グラフのような場合は、上の画像で言うと X 軸の 1 と 9 上の棒の半分が見切れてしまいます。その場合は下に述べる CustomLabel を使用するのが良さそうです。

(2) Custom Labels

先の記事「WPF で Chart を表示」のコードでは以下の画像のように Chart の X 軸に 0 と 7 が表示されてしまいました。

WPF アプリに Chart 表示

それを CustomLabel クラスを使って以下ようにしてみました。

CustomLabel の使用

そのコードは以下の通りです。基本的には先の記事とほとんど同じで、下の方に「// CostomLabel の設定」とコメントした部分のコードを追加しただけです。

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;

namespace WindowsFormsApp1
{
    public partial class Form8 : Form
    {
        private readonly Chart chart;

        public Form8()
        {
            InitializeComponent();

            this.chart = new Chart
            {
                Name = "chart1",
                Dock = DockStyle.Fill
            };

            this.Controls.Add(chart);
        }

        private void Form8_Load(object sender, EventArgs e)
        {
            Legend legend = new Legend()
            {
                DockedToChartArea = "ChartArea1",
                IsDockedInsideChartArea = false,
                Name = "Legend1"
            };

            chart.Legends.Add(legend);

            Series series = new Series()
            {
                Name = "三吉",
                ChartType = SeriesChartType.StackedColumn,
                CustomProperties = "DrawingStyle=Cylinder",
                IsValueShownAsLabel = true,
                Label = "#PERCENT{P1}",
                Legend = "Legend1",
                XValueMember = "Month",
                YValueMembers = "三吉"
            };
            chart.Series.Add(series);

            series = new Series()
            {
                Name = "春日",
                ChartType = SeriesChartType.StackedColumn,
                CustomProperties = "DrawingStyle=Cylinder",
                IsValueShownAsLabel = true,
                Label = "#PERCENT{P1}",
                Legend = "Legend1",
                XValueMember = "Month",
                YValueMembers = "春日"
            };
            chart.Series.Add(series);

            series = new Series()
            {
                Name = "東雲",
                ChartType = SeriesChartType.StackedColumn,
                CustomProperties = "DrawingStyle=Cylinder",
                IsValueShownAsLabel = true,
                Label = "#PERCENT{P1}",
                Legend = "Legend1",
                XValueMember = "Month",
                YValueMembers = "東雲"
            };
            chart.Series.Add(series);

            series = new Series()
            {
                Name = "府中",
                ChartType = SeriesChartType.StackedColumn,
                CustomProperties = "DrawingStyle=Cylinder",
                IsValueShownAsLabel = true,
                Label = "#PERCENT{P1}",
                Legend = "Legend1",
                XValueMember = "Month",
                YValueMembers = "府中"
            };
            chart.Series.Add(series);

            series = new Series()
            {
                Name = "広島",
                ChartType = SeriesChartType.StackedColumn,
                CustomProperties = "DrawingStyle=Cylinder",
                IsValueShownAsLabel = true,
                Label = "#PERCENT{P1}",
                Legend = "Legend1",
                XValueMember = "Month",
                YValueMembers = "広島"
            };
            chart.Series.Add(series);

            ChartArea chartArea = new ChartArea()
            {
                Name = "ChartArea1",
                AxisY = new Axis() { Title = "売上高" },
                AxisX = new Axis() { Title = "売上月" }
            };
            chart.ChartAreas.Add(chartArea);

            // CostomLabel の設定
            var dic = new Dictionary<int, string>
            {
                { 1, "Jan" }, { 2, "Feb" }, { 3, "Mar" }, { 4, "Apr" },
                { 5, "May" }, { 6, "Jun" }, { 7, "Jul" }, { 8, "Aug" },
                { 9, "Sep" }, { 10, "Oct" }, { 11, "Nov" }, { 12, "Dec" }
            };
            DataTable table = CreateDataTable();
            foreach (DataRow row in table.Rows)
            {
                int m = (int)row["Month"];
                chartArea.AxisX.CustomLabels.Add(m - 0.5d, m + 0.5d, dic[m]);
            }

            chart.DataSource = table.DefaultView;
        }

        private DataTable CreateDataTable()
        {
            string connString = @"接続文字列";
            string selectQuery = @"SELECT Month, 
                SUM(CASE WHEN Name = N'三吉' THEN Sales ELSE 0 END) AS 三吉,
                SUM(CASE WHEN Name = N'春日' THEN Sales ELSE 0 END) AS 春日,
                SUM(CASE WHEN Name = N'東雲' THEN Sales ELSE 0 END) AS 東雲,
                SUM(CASE WHEN Name = N'府中' THEN Sales ELSE 0 END) AS 府中,
                SUM(CASE WHEN Name = N'広島' THEN Sales ELSE 0 END) AS 広島
                FROM Shop GROUP BY Month";

            using (var connection = new SqlConnection(connString))
            {
                using (var command = new SqlCommand(selectQuery, connection))
                {
                    var adapter = new SqlDataAdapter(command);
                    var table = new DataTable();
                    adapter.Fill(table);
                    return table;
                }
            }
        }        
    }
}

上のコードの「// CostomLabel の設定」とコメントした部分を説明します。

CreateDataTable メソッドの中の SELECT クエリからは以下の結果が得られ、それが DataTable に格納されて返されます。

SELECT クエリの結果

その DataTable の Month 列のデータを使って CustomLabel を生成し AxisX.CustomLabels で取得できる CustomLabelsCollection オブジェクトに追加しています。

Add メソッドの第 1 引数は CustomLabel.FromPosition を、第 2 引数は CustomLabel.ToPosition を設定します。それらは Midrosoft のドキュメント「CustomLabel.FromPosition プロパティ」に以下のように書いてある通りラベルの表示位置を設定するものです。

"The difference between the values of the ToPosition and FromPosition properties determines where the label text will be displayed. For example, suppose the X-axis has a scale that ranges from 1 to 5 (1, 2 ,3 , 4, 5). In order to display a custom label just underneath the second point, you would set the ToPosition property to 1.5 and the FromPosition property to 2.5."

Add メソッドの第 3 引数はラベルのテキストを設定するものです。上のコードでは Dictionary を作って Month 列の int 型の数字を Jan, Feb, Mar ... という文字列にしています。

Tags: , ,

.NET Framework

About this blog

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

Calendar

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

View posts in large calendar