SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

ComponentZine(ComponentOne)byメシウス(AD)

【ComponentOne Data Services活用術】バッチ処理とEF Core連携でパフォーマンス向上

.NET対応のクラウドデータ連携ライブラリセット「ComponentOne Data Services」活用術 第3回

  • X ポスト
  • このエントリーをはてなブックマークに追加

 メシウス株式会社が提供する「ComponentOne」は、.NETに対応したUIコントロールのセットです。Windows Forms、WPF、ASP.NET、ASP.NET Core、Blazor、WinUI、.NET MAUIの7つの.NETプラットフォームに対応しているのが特徴です。また、静的ファイルを含むさまざまなデータソースに接続して.NETアプリで各種のデータ操作を実行するライブラリセットとして「Data Services」エディションが提供されています。本記事では、前回に引き続きCSVファイルをComponentOne Data Servicesを使って取得、更新する事例を紹介します。

  • X ポスト
  • このエントリーをはてなブックマークに追加

【これまでの記事】.NET対応のクラウドデータ連携ライブラリセット「ComponentOne Data Services」活用術

必要な環境

 本記事のサンプルコードは、以下の環境で動作を確認しています。

  • Windows 11 Pro(23H2)
  • ComponentOne Enterprise 2025J v1
  • .NET SDK(9.0.302)
  • Visual Studio 2022(17.14.10)

対象読者

  • さまざまな種類のデータと簡単に連携したい方
  • フォームやで高機能なコントロールを利用したい方
  • デスクトップアプリ開発、バックエンド開発が得意な方

一括処理の実装

 今回は、第2回で扱ったサンプルCSVConnectorConsoleAppに一括処理(バッチ処理)を導入します。一括処理によって、CSVファイルへの変更をバッファリングし、大量の変更がある場合のパフォーマンスを向上させることができます。第2回のサンプルに一括処理のメソッドを追加していくことで対応していきます。新しいアプリのコマンドライン書式は以下の通りとします。

program [ [C|R|U|D|BC|BU|BD] パラメータ]

 新規のパラメータとして、BC(一括追加)、BU(一括更新)、BD(一括削除)のいずれかを指定します。パラメータには、それぞれに必要なデータを記述します。

  • BCまたはbc:新規データをCSV形式で格納したファイル名(No列は自動決定されるので省略可)
  • BUまたはbu:更新対象行のNoをカンマ区切りで指定(一致する行の全ての列が「削除」に置き換えられる)
  • BDまたはbd:削除対象行のNoをカンマ区切りで指定

 仕分け部分のコードは本記事の主旨とは関係が薄いので、掲載を割愛します。内容は配布サンプルを参照してください。

[NOTE]DetectionSchemeへの変更

 2025Jにおいて、CSVファイルのスキーム解析を指示するパラメータが、DetectionSchemeTypeからDetectionSchemeに変更されました。よって本サンプルも接続文字列を変更しています。掲載は省くので、内容は配布サンプルを参照してください。

一括追加(BulkCreate)を実装してみる

 まずは一括追加です。一括処理の恩恵を感じやすいとしたら、まずはこの追加でしょう。BulkCreateメソッドを以下のリストのように追加します。

[リスト]:Program.cs
void BulkCreate(C1CSVConnection conn, string filename)
{
  // 追加時のレコード番号を取得する
  var no = GetNextNo(conn);	(1)
  // 全レコードのDataTableを取得する
  var adapter = new C1CSVDataAdapter(conn, "SELECT * FROM customer");
  var dataTable = new DataTable();
  adapter.Fill(dataTable);
  // SQLのINSERTコマンドを生成する
  adapter.InsertCommand = new C1CSVCommand(conn);	(2)
  adapter.InsertCommand.CommandText = "INSERT INTO customer([No],[氏名],[氏名(ひらがな)],[メールアドレス],[電話番号],[郵便番号],[住所],[会社名]) VALUES(@No,@Name,@Kana,@Email,@Tel,@Zip,@Address,@Company)";
  …略…
  // 入力ファイルから1行ずつ読み込んでDataTableに追加する
  // countは追加行数を数える変数
  var count = 0;	(3)
  using (var reader = new StreamReader(filename))
  {
    while (!reader.EndOfStream)
    {
      var line = reader.ReadLine();
      var values = line.Split(',');
      // DataTableに新規の行を追加して各列の値をセットする
      var customerRow = dataTable.NewRow();
      customerRow["No"] = (no++).ToString();
      customerRow["氏名"] = values[1];
      customerRow["氏名(ひらがな)"] = values[2];
      customerRow["メールアドレス"] = values[3];
      customerRow["電話番号"] = values[4];
      customerRow["郵便番号"] = values[5];
      customerRow["住所"] = values[6];
      customerRow["会社名"] = values[7];
      dataTable.Rows.Add(customerRow);
      count++;
    }
  }
  if (count > 0)	(4)
  {
    // 書き込みサイズをUpdateBatchSizeプロパティでセットする
    adapter.UpdateBatchSize = count;
    // 書き込みを実行する。書き込み行数がマイナスならエラー
    if (adapter.Update(dataTable) < 0)
    {
      Console.WriteLine("一括作成でエラーが発生しました。");
    }
  }
}

 (1)のレコード番号を求める処理とデータテーブルの取得の処理、(2)のINSERTコマンドの作成については、既存のcreateメソッドと同じです。No列は、既存のレコードの最大値に+1したもの以降に置き換えられます。

 (3)で、CSVファイルから1行ずつ読み込み、DataTableに新規行を追加していきます。このあたりもinsertメソッドと同様ですが、一括処理するためにDataAdapterのUpdateメソッドを毎回呼び出さない点が異なります。

 (4)は、追加した行がある場合の処理です。一括追加では、以下の点が単一行の追加と異なります。

  • DataAdapterのUpdateBatchSizeプロパティに、一括処理の行数(この場合は追加行数)を指定する。このサイズ単位でCSVファイルに書き込みを実行する
  • DataAdapterのUpdateメソッドをここで呼び出す。CSVファイルに書き込みを実行する

 これは一括更新や一括削除でも同じなので、覚えておきましょう。CSVファイルcustomer.csvとcustomer_new.csvを、あらかじめ実行ファイルと同じ場所(bin\Debug\net9.0など)に配置後にアプリをビルドします。コマンドラインパラメータを図1のように指定して実行し、CSVファイルを確認するか全データを出力してください。新しい行が追加されていればBulkCreateメソッドは正しく動作しています。

図1:一括追加時のコマンドラインパラメータ(デバッグプロパティ)
図1:一括追加時のコマンドラインパラメータ(デバッグプロパティ)

一括更新(BulkUpdate)を実装してみる

 次は、一括更新のBulkUpdateメソッドです。

[リスト]:Program.cs
void BulkUpdate(C1CSVConnection conn, string nos)
{
  //  指定されたレコードのみのDataTableを取得する
  var adapter = new C1CSVDataAdapter(conn, $"SELECT * FROM customer WHERE [No] IN ({nos})");	(1)
  …略…
  // DataTableの全行を更新する
  // countは更新行数を数える変数
  var count = 0;	(2)
  for (int i = 0; i < dataTable.Rows.Count; i++)
  {
    // DataTableの各行で各列の値をセットする
    var customerRow = dataTable.Rows[i];
    customerRow["氏名"] = "削除";
    customerRow["氏名(ひらがな)"] = "削除";
    customerRow["メールアドレス"] = "削除";
    customerRow["電話番号"] = "削除";
    customerRow["郵便番号"] = "削除";
    customerRow["住所"] = "削除";
    customerRow["会社名"] = "削除";
    count++;
  }
  …略…
}

 一括更新におけるポイントは、(1)のようにSQL文を構成して、DataTableには更新対象の行だけを読み込んでおくことです。そして、(2)のようにDataTableの各行について列の更新を実行します。UpdateBatchSizeプロパティの設定、Updateメソッドの実行も同様です。

 コマンドラインパラメータを図2のように指定して実行し、CSVファイルを確認するか全データを出力してください。指定した行の全列が「削除」になっていればBulkUpdateメソッドは正しく動作しています。

図2:一括更新時のコマンドラインパラメータ(デバッグプロパティ)
図2:一括更新時のコマンドラインパラメータ(デバッグプロパティ)

一括削除(BulkDelete)を実装してみる

 最後は、一括削除のBulkDeleteメソッドです。

[リスト]:Program.cs
void BulkDelete(C1CSVConnection conn, string nos)
{
  //  指定されたレコードのみのDataTableを取得する
  var adapter = new C1CSVDataAdapter(conn, $"SELECT * FROM customer WHERE [No] IN ({nos})");
  …略…
  // DataTableの全行を更新する
  // countは削除行数を数える変数
  var count = 0;	(1)
  for (int i = 0; i < dataTable.Rows.Count; i++)
  {
    // DataTableの各行で削除を実行する
    var customerRow = dataTable.Rows[i];
    customerRow.Delete();
    count++;
  }
  …略…
}

 ポイントは、一括更新とほぼ同様で、読み込んだDataTableの各行に対してDeleteメソッドを実行するだけです(「dataTable.Rows[i].Delete();」としても同様)。UpdateBatchSizeプロパティの設定、Updateメソッドの実行も同様です。

 コマンドラインパラメータを図3のように指定して実行し、CSVファイルを確認するか全データを出力してください。指定した行が存在しなければ、BulkDeleteメソッドは正しく動作しています。

図3:一括削除時のコマンドラインパラメータ(デバッグプロパティ)
図3:一括削除時のコマンドラインパラメータ(デバッグプロパティ)

Entity Framework CoreでCRUD処理を実装

 ここでは、Windows Formsアプリを作成して、Entity Framework Core(以降、EF Core)とData Servicesの連携を見ていきましょう。EF Coreを使うと、データベースファーストのアプローチで、シンプルなコードでCRUD処理を実装できます。

Windows Formsアプリを作成する

 Windows Formsアプリを作成します。[新しいプロジェクトの作成]から、「Windows Formsアプリ」などを選択して、プロジェクト名には「EFCoreWinFormApp」などと適当な名前を指定してプロジェクトを作成します。

 必要なパッケージは、C1.EntityFrameworkCore.CSVです。データソースの利用に必要なC1.AdoNet.CSVパッケージは依存関係で自動的にインストールされます。

[NOTE]Scaffoldingの利用

 Microsoft.EntityFrameworkCore.Toolsパッケージをインストールして、Scaffoldingによりファイル群を作成する方法もあります。ただし、フィールド名がCSVの列名そのまま(マルチバイト文字)になるなどの問題点があるので、本記事では使用していません。

エンティティクラスを作成する

 EF Coreにおけるデータの入れ物となるエンティティクラスを作成しておきます。このデータクラスは、CSVファイルから返されるレコードの各フィールドを保持する役割を持ちます。プロジェクトのルートフォルダにData.csファイルを以下のような内容で作成します。

[リスト]:Data.cs
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations.Schema;

namespace EFCoreWinFormsApp	(1)
{
  public partial class CustomerCSV	(2)
  {
    // CSVの列名とプロパティを対応付ける
    [Column("No")]	(3)
    public int Id { get; set; }	(4)
    [Column("氏名")]
    public string Name { get; set; }
    [Column("氏名(ひらがな)")]
    public string NameKana { get; set; }
    [Column("メールアドレス")]
    public string Email { get; set; }
    [Column("電話番号")]
    public string Tel { get; set; }
    [Column("郵便番号")]
    public string ZipCode { get; set; }
    [Column("住所")]
    public string Address { get; set; }
    [Column("会社名")]
    public string Company { get; set; }
  }
}

 (1)の名前空間の指定は、フォームのものと同じになるように指定してください(この場合はEFCoreWinFormsApp)。(2)では、クラスCustomerCSVを定義しています。これが、EF Coreにおけるデータの入れ物になります。partialキーワードによって部分クラスとしているのは、データモデルを自動生成した場合に、独自に追加するプロパティやビジネスロジックを分離するためです。今回はあえてpartialとする意味はないのですが、EF Coreにおけるエンティティクラスの一つの構成として押さえておくとよいでしょう。

 (3)では、CSVファイルにおける列名をSystem.ComponentModel.DataAnnotations.Schema.Column属性で指定しています。この属性と続く(4)のプロパティ定義において、列名とプロパティが結び付けられます。なお、EF CoreではIdプロパティは暗黙的に主キーと見なされるので、NoプロパティではなくIdプロパティとしています。

データベースコンテキストを作成する

 EF Coreでデータアクセスを担当し、データベースコンテキストクラスを作成します。同じくData.csファイルのEFCoreWinFormsApp名前空間に、以下のコードを追記します。

[リスト]:Data.cs
…略…
namespace EFCoreWinFormsApp
{
  …略…
  public partial class CSVContext : DbContext	(1)
  {
     // コンストラクタ。自動トランザクションを無効にする
     public CSVContext()	(2)
     {
       Database.AutoTransactionBehavior = AutoTransactionBehavior.Never;
    }
    public CSVContext(DbContextOptions<CSVContext> options)	(3)
      : base(options)
    {
      Database.AutoTransactionBehavior = AutoTransactionBehavior.Never;
    }
    // イベントハンドラからのデータベースコンテキストアクセスのためのプロパティ
    public virtual DbSet<CustomerCSV> CustomerCSV { get; set; }	(4)
    // データベースコンテキストの構築時に呼び出される。CSVファイルのURIを指定
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)	(5)
    {
      if (!optionsBuilder.IsConfigured)
      {
        optionsBuilder.UseCSV("Uri='customer.csv'");
      }
    }
    // モデルの作成時に呼び出される。エンティティを設定
    protected override void OnModelCreating(ModelBuilder modelBuilder)	(6)
    {
      modelBuilder.Entity<CustomerCSV>(entity =>
        {
          entity.ToTable("customer");
        });

      OnModelCreatingPartial(modelBuilder);
    }

    partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
  }
}

 (1)で、データベースコンテキストクラスCSVContextを定義しています。このクラスは、Microsoft.EntityFrameworkCore.DbContextクラスを継承する必要があります。こちらも、部分メソッドの利用のためにpartialキーワードを付加して部分クラスとしています。

 (2)(3)はコンストラクタの定義です。オプションを渡せるオーバーロードも定義していますが、今回のサンプルでは使用していません。配下では、Database.AutoTransactionsBehaviorプロパティをNeverに指定して自動トランザクション処理を無効にしているだけです。

 (4)は、データセットプロパティの定義です。このプロパティを通じて、フォームからCSVファイルにアクセスします。(5)は、OnConfiguringメソッドのオーバライドです。データベースコンテキストの構築時に呼び出されます。引数として受けとったDbContextOptionsBuilderオブジェクトのUseCSVメソッドを使って、利用するCSVファイルの接続文字列を指定しています。

 (6)は、OnModelCreatingメソッドのオーバライドです。モデルの作成時に呼び出されます。引数として受け取ったModelBuilderオブジェクトを介してエンティティを設定します。ここでは、ToTableメソッドでCSVファイルに対応するテーブル名を指定しているだけですが、Propertyメソッドを使ってCSVファイルの列名とエンティティクラスのプロパティを対応付けることもできます。サンプルでは、Column属性で指定しているので、propertyメソッドの呼び出しは不要です。

フォームをレイアウトする

 データが準備できたら、フォームをレイアウトします。シンプルにDataGridViewコントロールと、データ保存とレコード削除のためのButtonコントロールを配置するだけです。配置は特に問いませんが、Anchorプロパティを設定し、ウィンドウのリサイズに追従させた方が見やすくなります(図4)。また、各列の幅を自動調整するAutoSizeColumnModeプロパティをAllCellsとしています。

図4:フォームへのコントロール配置後
図4:フォームへのコントロール配置後

Windows Formsアプリのコードを記述する

 イベントハンドラとして、DataGridViewコントロールへのバインドのコード、レコード削除とデータ保存のためのコードを用意します。内容はリストの通りです。

using System;
using System.Windows.Forms;
using Microsoft.EntityFrameworkCore;

namespace EFCoreWinFormsApp
{
  public partial class Form1 : Form
  {
    private CSVContext db = null;	(1)

    public Form()
    {
      InitializeComponent();
    }
    // フォームロード時にデータベースコンテキストを構築しデータソースを設定
    private void Form_Load(object sender, EventArgs e)	(2)
    {
      db = new CSVContext();
      db.CustomerCSV.Load<CustomerCSV>();
      dataGridView1.DataSource = db.CustomerCSV.Local.ToBindingList();
    }
    // 保存ボタンクリック。変更を保存
    private void SaveButton_Click(object sender, EventArgs e)	(3)
    {
      db.SaveChanges();
    }
    // フォームクローズ時にデータベースコンテキストを解放
    private void Form_FormClosed(object sender, FormClosedEventArgs e)	(4)
    {
      db.Dispose();
    }
    // 削除ボタンクリック。選択行の全てに削除を実行(新規の行を除く)
    private void RemoveButton_Click(object sender, EventArgs e)	(5)
    {
      foreach (DataGridViewRow row in dataGridView1.SelectedRows)
      {
        if (!row.IsNewRow)
        {
           dataGridView1.Rows.Remove(row);
        }
      }
    }
  }
}

 (1)では、データベースコンテキスト(Data.csファイルで定義したCSVContextクラス)を格納するためのprivateフィールドを準備しています。これで、フォーム中の任意のメソッドからデータベースを利用できるようになります。

 (2)は、フォームの作成時に呼び出されるイベントハンドラです。(1)のprivateフィールドにインスタンスを格納し、Loadメソッドでデータセットを読み込みます。読み込んだデータソースは、ToBindingListメソッドでDataGridViewコントロールに紐づけておきましょう。

 (3)は、保存ボタンクリック時に呼び出されるイベントハンドラです。データセットに変更がある場合には、SaveChangesメソッドで変更内容をCSVファイルに反映します。

 (4)は、フォームの破棄時に呼び出されるイベントハンドラです。データベースコンテキストオブジェクトをDisposeメソッドによって破棄します。

 (5)は、削除ボタンクリック時に呼び出されるイベントハンドラです。SelectedRowsプロパティによって選択されている行を取得し、その全ての行を、Removeメソッドで削除しています。ただし、IsNewRowプロパティで新規レコードは除外します。

 以上を理解できたら、アプリを実行してみましょう。あらかじめcustomer.csvを実行ファイルのある場所(例えばbin\Debug\net9.0-windowsフォルダなど)に配置しておきます。プロジェクトを実行するとアプリが起動するので、問題なければグリッドにCSVファイルのデータが読み込まれて表示されます(図5)。

図5:Windows Formsアプリの実行結果(起動直後)
図5:Windows Formsアプリの実行結果(起動直後)

 グリッドの最終行に新規レコードを追加したり、既存のレコードのデータを変更したり、行を選択して[削除]ボタンをクリックすることで、グリッドの内容が変化するのを確認してください。最後に[保存]ボタンをクリックすれば、CSVファイルに作業内容が反映されます。CSVファイルをテキストエディタなどで開いて、ファイルも変更されていることを確認してみましょう。

まとめ

 今回は、CSVデータをComponentOne Data Servicesを使って操作するアプリを一括処理に対応させて、Data Servicesが大量のデータの処理にも対応できることを紹介しました。また、Entity Framework連携によるモデル指向のデータ操作を、WinFormsアプリを作成する事例を通じても紹介しました。

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加

提供:メシウス株式会社

【AD】本記事の内容は記事掲載開始時点のものです 企画・制作 株式会社翔泳社

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/22124 2025/10/15 12:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング