ユースケース

/

コードメンテナンス

レガシーコードのモダナイズ

モデル

クロード 3.7 ソネット

WPF を使用して VB6 インベントリデスクトップアプリを C# .NET 8 にモダナイズします。移行計画と 1 つのフォーム用に書き直されたサンプルコードを提供します。

クエリを試す
クロード 3.7 ソネット
/
コーダー

WPF を使用した VB6 インベントリアプリケーションの C# .NET 8 へのマイグレーション

マイグレーションプラン

フェーズ 1: 評価と計画

  1. 既存の機能をインベントリする
    • すべてのフォーム、モジュール、クラスを文書化
    • データベース接続とデータアクセス方法を特定する
    • ビジネスロジックとワークフローのマッピング
    • サードパーティーの依存関係を特定
  2. 建築計画
    • MVVM パターンを使用して新しいソリューション構造を設計する
    • データベース移行戦略の計画
    • テクノロジースタック (EF Coreなど) の定義
    • 再利用可能なコンポーネントを特定する

フェーズ 2: 環境設定

  1. 開発環境
    • ビジュアルスタジオ 2022 のセットアップ
    • .NET 8 を使用して新しい WPF ソリューションを作成する
    • ソースコントロールの設定
    • CI/CD パイプラインのセットアップ
  2. プロジェクト構造の作成
    • コアプロジェクト (モデル、ビューモデル、サービス) の作成
    • 依存性注入の設定
    • ロギングとエラー処理の設定

フェーズ 3: データベース移行

  1. データベース評価
    • 既存のデータベーススキーマの分析
    • 必要に応じてモダンデータベースへの移行を計画する
    • デザイン・エンティティ・フレームワークのコアモデル
  2. データアクセス層の実装
    • DbContextとエンティティ構成の作成
    • リポジトリの実装
    • データ移行スクリプトの作成

フェーズ 4: UI の移行

  1. UI デザイン
    • WPF スタイルとテーマの作成
    • 再利用可能なコントロールの設計
    • 基本的な MVVM インフラストラクチャの実装
  2. フォーム移行
    • 複雑さ/重要度によってフォームに優先順位を付ける
    • フォームを 1 つずつ実装する
    • 各フォームの単体テストを作成

フェーズ 5: ビジネスロジックの移行

  1. サービスレイヤーの実装
    • ビジネスロジックをサービスクラスに移行
    • 検証ルールを実装する
    • ユニットテストの作成
  2. インテグレーション
    • UI をサービスに接続
    • エラー処理を実装する
    • ログを追加

フェーズ 6: テストとデプロイ

  1. テスト
    • ユニットテスト
    • 統合テスト
    • ユーザー承認テスト
  2. デプロイ
    • インストーラーの作成
    • 文書導入プロセス
    • ロールアウト戦略の計画

サンプル移行:インベントリアイテムフォーム

オリジナルの VB6 フォーム (仮定)

1' frmInventoryItem.frm
2Option Explicit
3
4Private Sub Form_Load()
5    ' Initialize form
6    txtItemID.Text = ""
7    txtName.Text = ""
8    txtDescription.Text = ""
9    txtQuantity.Text = "0"
10    txtPrice.Text = "0.00"
11    cboCategory.Clear
12    
13    ' Load categories
14    Dim rs As ADODB.Recordset
15    Set rs = New ADODB.Recordset
16    rs.Open "SELECT CategoryID, CategoryName FROM Categories", conn, adOpenStatic
17    
18    Do Until rs.EOF
19        cboCategory.AddItem rs!CategoryName
20        cboCategory.ItemData(cboCategory.NewIndex) = rs!CategoryID
21        rs.MoveNext
22    Loop
23    rs.Close
24End Sub
25
26Private Sub btnSave_Click()
27    ' Validate input
28    If Trim(txtName.Text) = "" Then
29        MsgBox "Please enter item name", vbExclamation
30        txtName.SetFocus
31        Exit Sub
32    End If
33    
34    If Not IsNumeric(txtQuantity.Text) Then
35        MsgBox "Quantity must be numeric", vbExclamation
36        txtQuantity.SetFocus
37        Exit Sub
38    End If
39    
40    If Not IsNumeric(txtPrice.Text) Then
41        MsgBox "Price must be numeric", vbExclamation
42        txtPrice.SetFocus
43        Exit Sub
44    End If
45    
46    ' Save to database
47    Dim sql As String
48    If Trim(txtItemID.Text) = "" Then
49        ' Insert new record
50        sql = "INSERT INTO Inventory (Name, Description, CategoryID, Quantity, Price) VALUES ('" & _
51              Replace(txtName.Text, "'", "''") & "', '" & _
52              Replace(txtDescription.Text, "'", "''") & "', " & _
53              cboCategory.ItemData(cboCategory.ListIndex) & ", " & _
54              Val(txtQuantity.Text) & ", " & _
55              Val(txtPrice.Text) & ")"
56        conn.Execute sql
57        MsgBox "Item added successfully", vbInformation
58    Else
59        ' Update existing record
60        sql = "UPDATE Inventory SET Name = '" & Replace(txtName.Text, "'", "''") & _
61              "', Description = '" & Replace(txtDescription.Text, "'", "''") & _
62              "', CategoryID = " & cboCategory.ItemData(cboCategory.ListIndex) & _
63              ", Quantity = " & Val(txtQuantity.Text) & _
64              ", Price = " & Val(txtPrice.Text) & _
65              " WHERE ItemID = " & Val(txtItemID.Text)
66        conn.Execute sql
67        MsgBox "Item updated successfully", vbInformation
68    End If
69    
70    Unload Me
71End Sub
72
73Private Sub btnCancel_Click()
74    Unload Me
75End Sub

C# .NET 8 WPF マイグレーション

1。モデル

1// Models/InventoryItem.cs
2namespace InventoryApp.Models
3{
4    public class InventoryItem
5    {
6        public int ItemId { get; set; }
7        public string Name { get; set; } = string.Empty;
8        public string Description { get; set; } = string.Empty;
9        public int CategoryId { get; set; }
10        public string CategoryName { get; set; } = string.Empty;
11        public int Quantity { get; set; }
12        public decimal Price { get; set; }
13    }
14
15    public class Category
16    {
17        public int CategoryId { get; set; }
18        public string CategoryName { get; set; } = string.Empty;
19    }
20}

2。データアクセス

1// Data/AppDbContext.cs
2using InventoryApp.Models;
3using Microsoft.EntityFrameworkCore;
4
5namespace InventoryApp.Data
6{
7    public class AppDbContext : DbContext
8    {
9        public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
10
11        public DbSet<InventoryItem> InventoryItems { get; set; }
12        public DbSet<Category> Categories { get; set; }
13
14        protected override void OnModelCreating(ModelBuilder modelBuilder)
15        {
16            modelBuilder.Entity<InventoryItem>().ToTable("Inventory");
17            modelBuilder.Entity<Category>().ToTable("Categories");
18        }
19    }
20}

3。サービス

1// Services/IInventoryService.cs
2using InventoryApp.Models;
3using System.Collections.Generic;
4using System.Threading.Tasks;
5
6namespace InventoryApp.Services
7{
8    public interface IInventoryService
9    {
10        Task<List<Category>> GetCategoriesAsync();
11        Task<InventoryItem?> GetInventoryItemAsync(int id);
12        Task<bool> SaveInventoryItemAsync(InventoryItem item);
13    }
14}
15
16// Services/InventoryService.cs
17using InventoryApp.Data;
18using InventoryApp.Models;
19using Microsoft.EntityFrameworkCore;
20using System.Collections.Generic;
21using System.Threading.Tasks;
22
23namespace InventoryApp.Services
24{
25    public class InventoryService : IInventoryService
26    {
27        private readonly AppDbContext _dbContext;
28
29        public InventoryService(AppDbContext dbContext)
30        {
31            _dbContext = dbContext;
32        }
33
34        public async Task<List<Category>> GetCategoriesAsync()
35        {
36            return await _dbContext.Categories.ToListAsync();
37        }
38
39        public async Task<InventoryItem?> GetInventoryItemAsync(int id)
40        {
41            return await _dbContext.InventoryItems
42                .FirstOrDefaultAsync(i => i.ItemId == id);
43        }
44
45        public async Task<bool> SaveInventoryItemAsync(InventoryItem item)
46        {
47            try
48            {
49                if (item.ItemId == 0)
50                {
51                    await _dbContext.InventoryItems.AddAsync(item);
52                }
53                else
54                {
55                    _dbContext.InventoryItems.Update(item);
56                }
57                
58                await _dbContext.SaveChangesAsync();
59                return true;
60            }
61            catch
62            {
63                return false;
64            }
65        }
66    }
67}

4。ビューモデル

1// ViewModels/InventoryItemViewModel.cs
2using CommunityToolkit.Mvvm.ComponentModel;
3using CommunityToolkit.Mvvm.Input;
4using InventoryApp.Models;
5using InventoryApp.Services;
6using System;
7using System.Collections.ObjectModel;
8using System.ComponentModel.DataAnnotations;
9using System.Threading.Tasks;
10using System.Windows;
11
12namespace InventoryApp.ViewModels
13{
14    public partial class InventoryItemViewModel : ObservableObject
15    {
16        private readonly IInventoryService _inventoryService;
17
18        [ObservableProperty]
19        private int _itemId;
20
21        [ObservableProperty]
22        [NotifyDataErrorInfo]
23        [Required(ErrorMessage = "Name is required")]
24        private string _name = string.Empty;
25
26        [ObservableProperty]
27        private string _description = string.Empty;
28
29        [ObservableProperty]
30        [NotifyDataErrorInfo]
31        [Range(0, int.MaxValue, ErrorMessage = "Quantity must be a positive number")]
32        private int _quantity;
33
34        [ObservableProperty]
35        [NotifyDataErrorInfo]
36        [Range(0, double.MaxValue, ErrorMessage = "Price must be a positive number")]
37        private decimal _price;
38
39        [ObservableProperty]
40        private Category? _selectedCategory;
41
42        [ObservableProperty]
43        private ObservableCollection<Category> _categories = new();
44
45        [ObservableProperty]
46        private bool _isLoading;
47
48        [ObservableProperty]
49        private string _windowTitle = "New Inventory Item";
50
51        public InventoryItemViewModel(IInventoryService inventoryService)
52        {
53            _inventoryService = inventoryService;
54        }
55
56        public async Task LoadDataAsync(int? itemId = null)
57        {
58            try
59            {
60                IsLoading = true;
61                
62                // Load categories
63                var categoryList = await _inventoryService.GetCategoriesAsync();
64                Categories = new ObservableCollection<Category>(categoryList);
65
66                // If editing existing item
67                if (itemId.HasValue && itemId.Value > 0)
68                {
69                    var item = await _inventoryService.GetInventoryItemAsync(itemId.Value);
70                    if (item != null)
71                    {
72                        ItemId = item.ItemId;
73                        Name = item.Name;
74                        Description = item.Description;
75                        Quantity = item.Quantity;
76                        Price = item.Price;
77                        SelectedCategory = Categories.FirstOrDefault(c => c.CategoryId == item.CategoryId);
78                        WindowTitle = "Edit Inventory Item";
79                    }
80                }
81            }
82            finally
83            {
84                IsLoading = false;
85            }
86        }
87
88        [RelayCommand]
89        private async Task SaveAsync()
90        {
91            if (SelectedCategory == null)
92            {
93                MessageBox.Show("Please select a category", "Validation Error", MessageBoxButton.OK, MessageBoxImage.Warning);
94                return;
95            }
96
97            var item = new InventoryItem
98            {
99                ItemId = ItemId,
100                Name = Name,
101                Description = Description,
102                CategoryId = SelectedCategory.CategoryId,
103                Quantity = Quantity,
104                Price = Price
105            };
106
107            var result = await _inventoryService.SaveInventoryItemAsync(item);
108            if (result)
109            {
110                MessageBox.Show(ItemId == 0 ? "Item added successfully" : "Item updated successfully", 
111                    "Success", MessageBoxButton.OK, MessageBoxImage.Information);
112                CloseWindow?.Invoke();
113            }
114            else
115            {
116                MessageBox.Show("Error saving item", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
117            }
118        }
119
120        [RelayCommand]
121        private void Cancel()
122        {
123            CloseWindow?.Invoke();
124        }
125
126        // Event to close the window
127        public Action? CloseWindow { get; set; }
128    }
129}

5。見る

1<!-- Views/InventoryItemView.xaml -->
2<Window x:Class="InventoryApp.Views.InventoryItemView"
3        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
4        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
5        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
6        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
7        xmlns:vm="clr-namespace:InventoryApp.ViewModels"
8        mc:Ignorable="d"
9        Title="{Binding WindowTitle}"
10        Height="450" Width="500"
11        WindowStartupLocation="CenterOwner">
12    
13    <Window.Resources>
14        <Style TargetType="TextBlock">
15            <Setter Property="Margin" Value="0,5,0,2" />
16        </Style>
17        <Style TargetType="TextBox">
18            <Setter Property="Padding" Value="5" />
19            <Setter Property="Margin" Value="0,0,0,10" />
20        </Style>
21        <Style TargetType="ComboBox">
22            <Setter Property="Padding" Value="5" />
23            <Setter Property="Margin" Value="0,0,0,10" />
24        </Style>
25        <Style TargetType="Button">
26            <Setter Property="Padding" Value="15,5" />
27            <Setter Property="Margin" Value="5" />
28            <Setter Property="MinWidth" Value="80" />
29        </Style>
30    </Window.Resources>
31    
32    <Grid Margin="20">
33        <Grid.RowDefinitions>
34            <RowDefinition Height="*" />
35            <RowDefinition Height="Auto" />
36        </Grid.RowDefinitions>
37        
38        <StackPanel Grid.Row="0">
39            <TextBlock Text="Item Name:" />
40            <TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
41            
42            <TextBlock Text="Description:" />
43            <TextBox Text="{Binding Description}" Height="60" TextWrapping="Wrap" AcceptsReturn="True" />
44            
45            <TextBlock Text="Category:" />
46            <ComboBox ItemsSource="{Binding Categories}" 
47                      SelectedItem="{Binding SelectedCategory}"
48                      DisplayMemberPath="CategoryName" />
49            
50            <TextBlock Text="Quantity:" />
51            <TextBox Text="{Binding Quantity, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
52            
53            <TextBlock Text="Price:" />
54            <TextBox Text="{Binding Price, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
55        </StackPanel>
56        
57        <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right">
58            <Button Content="Save" Command="{Binding SaveCommand}" />
59            <Button Content="Cancel" Command="{Binding CancelCommand}" />
60        </StackPanel>
61        
62        <Grid Grid.RowSpan="2" Background="#80000000" Visibility="{Binding IsLoading, Converter={StaticResource BooleanToVisibilityConverter}}">
63            <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
64                <TextBlock Text="Loading..." Foreground="White" FontSize="16" HorizontalAlignment="Center" />
65                <ProgressBar IsIndeterminate="True" Width="200" Height="20" Margin="0,10,0,0" />
66            </StackPanel>
67        </Grid>
68    </Grid>
69</Window>
1// Views/InventoryItemView.xaml.cs
2using InventoryApp.ViewModels;
3using System.Windows;
4
5namespace InventoryApp.Views
6{
7    public partial class InventoryItemView : Window
8    {
9        private readonly InventoryItemViewModel _viewModel;
10
11        public InventoryItemView(InventoryItemViewModel viewModel, int? itemId = null)
12        {
13            InitializeComponent();
14            _viewModel = viewModel;
15            DataContext = _viewModel;
16            
17            _viewModel.CloseWindow = () => Close();
18            
19            Loaded += async (s, e) => 
20            {
21                await _viewModel.LoadDataAsync(itemId);
22            };
23        }
24    }
25}

6。アプリの起動と DI 設定

1// App.xaml.cs
2using InventoryApp.Data;
3using InventoryApp.Services;
4using InventoryApp.ViewModels;
5using InventoryApp.Views;
6using Microsoft.EntityFrameworkCore;
7using Microsoft.Extensions.DependencyInjection;
8using Microsoft.Extensions.Hosting;
9using System.Windows;
10
11namespace InventoryApp
12{
13    public partial class App : Application
14    {
15        private readonly IHost _host;
16
17        public App()
18        {
19            _host = Host.CreateDefaultBuilder()
20                .ConfigureServices((context, services) =>
21                {
22                    // Register DbContext
23                    services.AddDbContext<AppDbContext>(options =>
24                        options.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=InventoryApp;Trusted_Connection=True;"));
25
26                    // Register services
27                    services.AddScoped<IInventoryService, InventoryService>();
28
29                    // Register ViewModels
30                    services.AddTransient<InventoryItemViewModel>();
31
32                    // Register Views
33                    services.AddTransient<InventoryItemView>();
34                    services.AddTransient<MainWindow>();
35                })
36                .Build();
37        }
38
39        protected override void OnStartup(StartupEventArgs e)
40        {
41            _host.Start();
42
43            var mainWindow = _host.Services.GetRequiredService<MainWindow>();
44            mainWindow.Show();
45
46            base.OnStartup(e);
47        }
48
49        protected override void OnExit(ExitEventArgs e)
50        {
51            _host.Dispose();
52            base.OnExit(e);
53        }
54    }
55}

移行の主な改善点

  1. モダンアーキテクチャ
    • 懸念事項が明確に分離されたMVVMパターン
    • テストしやすくするための依存性注入
    • レスポンシブ UI の非同期/待機
  2. データアクセス
    • ADO.NET の代わりにエンティティフレームワークコア
    • SQL インジェクションを防ぐためのパラメータ化されたクエリ
    • データアクセス用のリポジトリパターン
  3. ユーザーインターフェースの改良
    • WPF スタイルとテンプレート
    • 検証付きデータバインディング
    • 非同期操作のロードインジケーター
  4. コード品質
    • バリアント型ではなく強い型付け
    • 適切なエラー処理
    • データ注釈を使用した入力検証
  5. メンテナンス性
    • 組織化されたプロジェクト構造
    • テスト可能なコンポーネント
    • 最新の C# 言語機能

この移行アプローチは、コア機能を維持しながら最新の開発手法で改善しながら、VB6インベントリアプリケーションをモダナイズするための強固な基盤となります。

関連するユースケースを見る

忍者の AI アシスタントを体験

今すぐ無料でお試しください。プランは月額19ドルから。