C#で、JRA-VANから出馬表のデータを取得して表示する その1メニュー画面

C#で、JRA-VANから出馬表のデータを取得して表示する - その1メニュー画面 C#

C#で、JRA-VANのデータを取得する方法を書きました。

今回はサンプルプログラムとして提供されている出馬表を作成してみます。
出馬表のデータを取得して、表示するプログラムです。
https://jra-van.jp/dlb/sdv/pgm.html#sample

こちらのサンプルはVBで実装されているため、同じような内容の出馬表をC#で作成します。

環境

  • Visual Studio 2022
  • .NET 7
  • SQL Server Express LocalDB

Entity Framework Core をインストールする

プロジェクトを右クリック→NuGet パッケージの管理から、以下をインストールします。

  • Microsoft.EntityFrameworkCore
  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.EntityFrameworkCore.Tools
  • Microsoft.Extensions.Configuration.Json

モデルを作成する

まずは、DBコンテキストを実装します。
SQL Serverの接続文字列の設定と、モデルを定義します。
ここで使用するモデルは3つです。

データの内容については公式のドキュメントをご覧ください。
※2023年10月時点、Ver.4.9.0
https://jra-van.jp/dlb/sdv/sdk/JV-Data490.pdf

using Microsoft.EntityFrameworkCore;
using System.Configuration;

namespace JraVanRaceHorseTable.Models
{
    public class ApplicationDbContext : DbContext
    {
        public DbSet<Race> Races { get; set; }
        public DbSet<RaceUma> RaceUmas { get; set; }
        public DbSet<Uma> Umas { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder options)
            => options.UseSqlServer(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString);
    }
}

接続文字列は設定ファイルに定義します。
.NETなので、「appsetting.json」にしようと思いましたが、別途書き込み処理があるので、App.configを使用しています。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
	<connectionStrings>
        <add name="DefaultConnection" connectionString="Server=(localdb)\mssqllocaldb;Database=JraVanRaceHorseTable;Trusted_Connection=True;Connection Timeout=60;Command Timeout=60;" />
	</connectionStrings>
</configuration>

レース詳細(Race)

レース詳細のモデルです。
後述する馬毎レース情報と1対Nの関係にしています。

項目数が多いので、全量はGitHubをご覧ください。
https://github.com/specially198/jra-van-race-horse-table/blob/main/JraVanRaceHorseTable/Models/Race.cs

namespace JraVanRaceHorseTable.Models
{
    public class Race
    {
        public int Id { get; set; }

        public required string RecordSpec { get; set; }          // レコード種別
        public required string DataKubun { get; set; }           // データ区分
        public required string MakeDate { get; set; }            // データ作成年月日
        (中略)
        public required string RecordUpKubun { get; set; }       // レコード更新区分

        public List<RaceUma> RaceUmas { get; } = new();
    }
}

馬毎レース情報(RaceUma)

馬毎レース情報のモデルです。

項目数が多いので、全量はGitHubをご覧ください。
https://github.com/specially198/jra-van-race-horse-table/blob/main/JraVanRaceHorseTable/Models/RaceUma.cs

namespace JraVanRaceHorseTable.Models
{
    public class RaceUma
    {
        public int Id { get; set; }
        public int RaceId { get; set; }

        public required string RecordSpec { get; set; }          // レコード種別
        public required string DataKubun { get; set; }           // データ区分
        public required string MakeDate { get; set; }            // データ作成年月日
        (中略)
        public required string KyakusituKubun { get; set; }      // 今回レース脚質判定
    }
}

競走馬マスタ(Uma)

競走馬マスタのモデルです。

項目数が多いので、全量はGitHubをご覧ください。
https://github.com/specially198/jra-van-race-horse-table/blob/main/JraVanRaceHorseTable/Models/Uma.cs

namespace JraVanRaceHorseTable.Models
{
    public class Uma
    {
        public int Id { get; set; }

        public required string RecordSpec { get; set; }               // レコード種別
        public required string DataKubun { get; set; }                // データ区分
        public required string MakeDate { get; set; }                 // データ作成年月日
        (中略)
        public required string RaceCount { get; set; }                // 登録レース数
    }
}

データベースを作成する

マイグレーションを使用して、データベースを作成します。

Install-Package Microsoft.EntityFrameworkCore.Tools
Add-Migration InitialCreate
Update-Database

JV-Linkを利用できるようにするために32bitで作成しているので、32bit用のSDKが必要です。
https://dotnet.microsoft.com/ja-jp/download/dotnet/7.0

インストールしたSDKにパスを通して、従来の64bitより優先されるようにしておきます。
(環境変数で上にする)

サービスクラスを作成する

DBアクセスを行う各処理をサービスクラスとして実装します。
サービスクラスはDI対象とするため、インターフェースとそれに対応するクラスを実装しています。

レース詳細(Race)サービスクラス

レース詳細用のサービスクラスです。

項目数が多いので、全量はGitHubをご覧ください。
https://github.com/specially198/jra-van-race-horse-table/blob/main/JraVanRaceHorseTable/Services/RaceService.cs

using JraVanRaceHorseTable.Models;
using JraVanRaceHorseTable.Models.ViewModel;
using Microsoft.EntityFrameworkCore;
using static JVData_Struct;

namespace JraVanRaceHorseTable.Services
{
    public interface IRaceService
    {
        void Add(JV_RA_RACE structRace);
        List<RaceYearMonthViewModel> GetRaceYearMonthDayList();
        int GetRaceId(string year, string monthDay, string jyoCD, string kaiji, string nichiji, string raceNum);
        void DeleteAll();
    }

    public class RaceService : IRaceService
    {
        private readonly ApplicationDbContext _db;

        public RaceService(ApplicationDbContext db)
        {
            _db = db;
        }

        public void Add(JV_RA_RACE structRace)
        {
            var head = structRace.head;
            var id = structRace.id;
            var raceInfo = structRace.RaceInfo;
            var jyokenInfo = structRace.JyokenInfo;
            var tenkoBaba = structRace.TenkoBaba;

            var race = new Race
            {
                RecordSpec = head.RecordSpec,
                DataKubun = head.DataKubun,
                MakeDate = head.MakeDate.Year + head.MakeDate.Month + head.MakeDate.Day,
                (中略)
                RecordUpKubun = structRace.RecordUpKubun,
            };
            _db.Add(race);
            _db.SaveChanges();
        }

        public int GetRaceId(string year, string monthDay, string jyoCD, string kaiji, string nichiji, string raceNum)
        {
            var raceId = _db.Races
                .Where(r => r.Year == year)
                .Where(r => r.MonthDay == monthDay)
                .Where(r => r.JyoCD == jyoCD)
                .Where(r => r.Kaiji == kaiji)
                .Where(r => r.Nichiji == nichiji)
                .Where(r => r.RaceNum == raceNum)
                .Select(r => r.Id)
                .First();

            return raceId;
        }

        public List<RaceYearMonthViewModel> GetRaceYearMonthDayList()
        {
            // 地方、海外レースを除外
            string[] kubuns = {"A", "B"};
            var races = _db.Races
                .Where(r => !kubuns.Contains(r.DataKubun))
                .OrderByDescending(r => r.Year)
                .OrderByDescending(r => r.MonthDay)
                .Select(r => new RaceYearMonthViewModel()
                {
                    Year = r.Year,
                    MonthDay = r.MonthDay,
                })
                .Distinct()
                .ToList();

            return races;
        }

        public void DeleteAll()
        {
            _db.Database.ExecuteSqlRaw("DELETE FROM Races");
        }
    }
}

馬毎レース情報(RaceUma)サービスクラス

馬毎レース情報用のサービスクラスです。

項目数が多いので、全量はGitHubをご覧ください。
https://github.com/specially198/jra-van-race-horse-table/blob/main/JraVanRaceHorseTable/Services/RaceUmaService.cs

using JraVanRaceHorseTable.Models;
using Microsoft.EntityFrameworkCore;
using static JVData_Struct;

namespace JraVanRaceHorseTable.Services
{
    public interface IRaceUmaService
    {
        void Add(JV_SE_RACE_UMA structRaceUma, int raceId);
        void DeleteAll();
    }

    public class RaceUmaService : IRaceUmaService
    {
        private readonly ApplicationDbContext _db;

        public RaceUmaService(ApplicationDbContext db)
        {
            _db = db;
        }

        public void Add(JV_SE_RACE_UMA structRaceUma, int raceId)
        {
            var head = structRaceUma.head;

            var raceUma = new RaceUma
            {
                RaceId = raceId,
                RecordSpec = head.RecordSpec,
                DataKubun = head.DataKubun,
                (中略)
                KyakusituKubun = structRaceUma.KyakusituKubun,
            };
            _db.Add(raceUma);
            _db.SaveChanges();
        }

        public void DeleteAll()
        {
            _db.Database.ExecuteSqlRaw("TRUNCATE TABLE RaceUmas");
        }
    }
}

競走馬マスタ(Uma)サービスクラス

競走馬マスタ用のサービスクラスです。

項目数が多いので、全量はGitHubをご覧ください。
https://github.com/specially198/jra-van-race-horse-table/blob/main/JraVanRaceHorseTable/Services/UmaService.cs

using JraVanRaceHorseTable.Models;
using Microsoft.EntityFrameworkCore;
using static JVData_Struct;

namespace JraVanRaceHorseTable.Services
{
    public interface IUmaService
    {
        void Add(JV_UM_UMA structUma);
        void DeleteAll();
    }

    public class UmaService : IUmaService
    {
        private readonly ApplicationDbContext _db;

        public UmaService(ApplicationDbContext db)
        {
            _db = db;
        }

        public void Add(JV_UM_UMA structUma)
        {
            var head = structUma.head;

            var uma = new Uma
            {
                RecordSpec = head.RecordSpec,
                DataKubun = head.DataKubun,
                MakeDate = head.MakeDate.Year + head.MakeDate.Month + head.MakeDate.Day,
                (中略)
                RaceCount = structUma.RaceCount,
            };
            _db.Add(uma);
            _db.SaveChanges();
        }

        public void DeleteAll()
        {
            _db.Database.ExecuteSqlRaw("TRUNCATE TABLE Umas");
        }
    }
}

DIを設定する

DBコンテキスト、サービスクラスはDIに登録して使用できるようにします。

using JraVanRaceHorseTable.Models;
using JraVanRaceHorseTable.Services;
using Microsoft.Extensions.DependencyInjection;

namespace JraVanRaceHorseTable
{
    internal static class Program
    {
        /// <summary>
        ///  The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            // To customize application configuration such as set high DPI settings or default font,
            // see https://aka.ms/applicationconfiguration.
            ApplicationConfiguration.Initialize();

            IServiceCollection services = new ServiceCollection();
            services.AddDbContext<ApplicationDbContext>();
            services.AddTransient<IRaceService, RaceService>();
            services.AddTransient<IRaceUmaService, RaceUmaService>();
            services.AddTransient<IUmaService, UmaService>();
            services.AddTransient<frmMenu>();

            ServiceProvider serviceProvider = services.BuildServiceProvider();
            frmMenu form = serviceProvider.GetRequiredService<frmMenu>();
            Application.Run(form);
        }
    }
}

フォームを作成する

各項目の名前などは、サンプルプログラムに準ずるものとします。

コンストラクタ

上記でDIの設定を行ったので、フォームの生成時に、各サービスクラスを生成します。

using JraVanRaceHorseTable.Services;
using System.Configuration;
using System.Text;
using static JraVanRaceHorseTable.Const;
using static JVData_Struct;

namespace JraVanRaceHorseTable
{
    public partial class frmMenu : Form
    {
        private readonly IRaceService _raceService;
        private readonly IRaceUmaService _raceUmaService;
        private readonly IUmaService _umaService;

        // キャンセルフラグ
        bool bCancelFlag = false;

        // FromTime
        string? strFromTime;

        public frmMenu(
            IRaceService raceService,
            IRaceUmaService raceUmaService,
            IUmaService umaService)
        {
            InitializeComponent();

            _raceService = raceService;
            _raceUmaService = raceUmaService;
            _umaService = umaService;
        }
    }
}

フォームのロード

フォームをロードする時の処理です。

private void frmMenu_Load(object sender, EventArgs e)
{
    // FROMTIME
    strFromTime = ConfigurationManager.AppSettings["fromTime"];
    strFromTime ??= "00000000000000";

    // JVInitの呼び出し
    int lReturnCode = JVLink.JVInit("JVLinkSDKSampleAPP1");
    if (lReturnCode != 0)
    {
        MessageBox.Show("JVInitエラー コード:" + lReturnCode);
    }

    // 開催年月日選択コンボボックスの表示
    SetCmdYearItem();

    // データベース関連機能ボタンを使用可に設定
    btnGetJVData.Enabled = true;
    btnInitDB.Enabled = true;

    if (cmbYear.Items.Count > 0)
    {
        btnViewDenmaList.Enabled = true;
        cmbYear.Enabled = true;
    }
    else
    {
        btnViewDenmaList.Enabled = false;
        cmbYear.Enabled = false;
    }

    Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
}

FromTimeは、設定ファイル(App.config)から取得しています。
前述しましたが、.NETなので、「appsetting.json」にしようと思いましたが、FromTimeの書き込み処理があるので、App.configを使用しています。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  (中略)
	<appSettings>
		<add key="fromTime" value="00000000000000" />
	</appSettings>
</configuration>

開催情報取得ボタンクリック

開催情報取得ボタンクリック時の処理です。

JVLinkを使用して、各種データを取得します。
取得したデータは構造体に変換した後、上述したサービスクラスを使用して、DBに登録します。

private void btnGetJVData_Click(object sender, EventArgs e)
{
    try
    {
        // キャンセル検知フラグの初期化
        bCancelFlag = false;
        // プログレスバーの初期化
        barFileCount.Value = 0;
        barReadSize.Value = 0;

        // JVOpenの呼び出し

        // JVOpen:ファイル識別子
        string strDataSpec = "RACERCVN";
        // JVOpen:オプション
        int lOption = 2;

        // JVLink:戻り値
        int lReadCount = 0;
        // JVOpen:総ダウンロードファイル数
        int lDownloadCount = 0;
        // JVOpen:最新ファイルのタイムスタンプ
        int lReturnCode = JVLink.JVOpen(
            strDataSpec, strFromTime, lOption, ref lReadCount, ref lDownloadCount, out string strLastFileTimestamp);
        if (lReturnCode < 0)
        {
            MessageBox.Show("JVOpenエラー コード:" + lReturnCode);
            return;
        }

        // ボタンの抑止
        btnGetJVData.Enabled = false;
        btnViewDenmaList.Enabled = false;
        btnInitDB.Enabled = false;
        btnSettingJVLink.Enabled = false;
        cmbYear.Enabled = false;
        btnStopJVData.Enabled = true;

        // 合計ファイル数のプログレスバーの上限を設定
        barFileCount.Maximum = lReadCount;

        // JVGets用(バッファポインタ)
        object objBuff = Array.Empty<byte>();
        // JVGets用(バッファサイズ)
        int lBuffSize = 110000;

        // プログレスバー用変数
        var lTotalFileCount = 0;
        var lTotalReadSize = 0;

        // レース詳細情報構造体
        var structRace = new JV_RA_RACE();
        // 馬毎レース情報構造体
        var structRaceUma = new JV_SE_RACE_UMA();
        // 競走馬マスタ構造体
        var structUma = new JV_UM_UMA();

        var bSkipFlg = false;

        while (true)
        {
            // バックグラウンドでの処理を実行
            Application.DoEvents();

            // キャンセルが押されたら処理(ループ)を抜ける
            if (bCancelFlag)
            {
                break;
            }

            // JVGetsの呼び出し
            lReturnCode = JVLink.JVGets(ref objBuff, lBuffSize, out string strFileName);
            var szBuff = (byte[])objBuff;

            // エラー判定
            switch (lReturnCode)
            {
                // 正常
                case int i when i >= (int)JvRead.Success:
                    // 文字コード変換(SJIS→UNICODE)
                    var strBuff = Encoding.GetEncoding("sjis").GetString(szBuff);

                    // データ区分の取得
                    var strRecID = strBuff[..2];

                    bSkipFlg = false;
                    // 処理対象データのみデータベースへ登録
                    if (strRecID == RecordKind.Race)
                    {
                        // レース詳細
                        structRace.SetDataB(ref strBuff);
                        _raceService.Add(structRace);
                    }
                    else if (strRecID == RecordKind.RaceUma)
                    {
                        // 馬毎レース情報
                        structRaceUma.SetDataB(ref strBuff);

                        var id = structRaceUma.id;
                        var raceId = _raceService.GetRaceId(
                            id.Year!, id.MonthDay!, id.JyoCD!, id.Kaiji!, id.Nichiji!, id.RaceNum!);

                        _raceUmaService.Add(structRaceUma, raceId);
                    }
                    else if (strRecID == RecordKind.Uma)
                    {
                        // 競走馬マスタ
                        structUma.SetDataB(ref strBuff);
                        _umaService.Add(structUma);
                    }
                    else
                    {
                        // 対象外ファイルはスキップ(フラグを設定)
                        bSkipFlg = true;
                    }

                    if (bSkipFlg)
                    {
                        // 対象外ファイルはスキップ
                        JVLink.JVSkip();
                        // カレントファイルのプログレスバーを更新
                        barReadSize.Value = barReadSize.Maximum;

                        // 合計ファイル数のプログレスバーを更新
                        lTotalFileCount++;
                        barFileCount.Value = lTotalFileCount;
                    }
                    else
                    {
                        // カレントファイルのプログレスバーを更新
                        barReadSize.Maximum = JVLink.m_CurrentReadFilesize;
                        lTotalReadSize = lTotalReadSize + szBuff.Length - 1;
                        barReadSize.Value = lTotalReadSize;
                    }

                    break;
                // ファイルの区切れ
                case (int)JvRead.Eof:
                    // 合計ファイル数のプログレスバーを更新
                    lTotalFileCount++;
                    barFileCount.Value = lTotalFileCount;

                    // カレントファイルのプログレスバーを初期化
                    lTotalReadSize = 0;

                    // FromTimeを退避
                    strFromTime = JVLink.m_CurrentFileTimeStamp;

                    break;
                // 全レコード読込み終了(EOF)
                case (int)JvRead.Eol:
                    goto readFinish;
                // ダウンロード中
                case (int)JvRead.DownloadNow:
                    continue;
                // エラー
                case (int)JvRead.Err:
                    MessageBox.Show("JVGetsエラー コード:" + lReturnCode);
                    goto readFinish;
            }
        }
    readFinish:;

        // FromTimeを設定ファイルに保存
        Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
        config.AppSettings.Settings["fromTime"].Value = strFromTime;
        config.Save();

        if (!bCancelFlag)
        {
            MessageBox.Show("開催情報の取得が終了しました。");
        }
        else
        {
            MessageBox.Show("開催情報の取得を中止しました。");
        }

        // 開催年月日選択コンボボックスの表示
        SetCmdYearItem();

        // ボタンの抑止を解除
        btnGetJVData.Enabled = true;
        btnViewDenmaList.Enabled = true;
        btnInitDB.Enabled = true;
        btnSettingJVLink.Enabled = true;
        cmbYear.Enabled = true;
        btnStopJVData.Enabled = true;
        // プログレスバーを元に戻す
        barFileCount.Value = 0;
        barReadSize.Value = 0;
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.ToString());
    }
    finally
    {
        // JVCloseの呼出
        int lReturnCode = JVLink.JVClose();
        if (lReturnCode != 0)
        {
            MessageBox.Show("JVCloseエラー コード:" + lReturnCode);
        }
    }
}

キャンセルボタンクリック

キャンセルボタンクリック時の処理です。

private void btnStopJVData_Click(object sender, EventArgs e)
{
    // キャンセルフラグの設定
    bCancelFlag = true;
}

DB初期化ボタンクリック

DB初期化ボタンクリック時の処理です。

private void btnInitDB_Click(object sender, EventArgs e)
{
    DialogResult result = MessageBox.Show("DBを初期化しますか?", "確認", MessageBoxButtons.YesNo);
    if (result == DialogResult.No)
    {
        return;
    }

    // プログレスバーの初期化
    barReadSize.Value = 0;
    barFileCount.Value = 0;
    barFileCount.Maximum = 100;

    // テーブルの全レコードをクリア
    _raceService.DeleteAll();
    barFileCount.Value = 30;

    _raceUmaService.DeleteAll();
    barFileCount.Value = 60;

    _umaService.DeleteAll();
    barFileCount.Value = 90;

    // FromTimeを初期化し設定ファイルに保存
    Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
    config.AppSettings.Settings["fromTime"].Value = "00000000000000";
    config.Save();

    // 開催年月日選択コンボボックスのクリア
    SetCmdYearItem();

    btnViewDenmaList.Enabled = false;
    cmbYear.Enabled = false;

    barFileCount.Value = 100;

    MessageBox.Show("DBの初期化が終了しました。");
}

設定ボタンクリック

設定ボタンクリック時の処理です。

private void btnSettingJVLink_Click(object sender, EventArgs e)
{
    // 設定画面表示
    int lReturnCode = JVLink.JVSetUIProperties();
    if (lReturnCode != 0)
    {
        MessageBox.Show("JVSetUIPropertiesエラー コード:" + lReturnCode);
    }
}

開催年月日選択コンボボックスの設定

開催年月日選択のコンボボックスを設定する処理です。

private void SetCmdYearItem()
{
    // コンボボックスのクリア
    cmbYear.Text = "";
    cmbYear.Items.Clear();

    var raceYearMonthList = _raceService.GetRaceYearMonthDayList();

    foreach (var raceYearMonth in raceYearMonthList)
    {
        cmbYear.Items.Add(raceYearMonth.Year + raceYearMonth.MonthDay);
    }

    // 直近日付を初期表示
    if (cmbYear.Items.Count > 0)
    {
        cmbYear.SelectedIndex = cmbYear.Items.Count - 1;
    }
}

その2出馬表選択画面については、以下の記事をご覧ください。

タイトルとURLをコピーしました