DEV/ETC

C# MVVM 1

SBP 2025. 4. 24. 08:49

 
Visual Studio Code(VS Code)를 사용하여 C# WPF 기반의 MVVM 패턴 프로젝트를 구성하고, Oracle 및 MS SQL 데이터베이스 연동, OPC 서버 데이터 조회 기능을 포함하는 기본 틀을 설명해 드리겠습니다.
 
로컬 PC에서 개발, 테스트, 배포까지 가능하도록 구성하는 것을 목표로 합니다.
 
1. 사전 준비 (Prerequisites)
 * .NET SDK 설치: 최신 버전 또는 프로젝트에 맞는 .NET SDK가 설치되어 있어야 합니다. (https://dotnet.microsoft.com/download)
 * Visual Studio Code 설치: VS Code가 설치되어 있어야 합니다. (https://code.visualstudio.com/)
 * C# Dev Kit 확장 설치: VS Code 마켓플레이스에서 'C# Dev Kit' 확장을 설치합니다. 이 확장은 C# 개발 경험을 크게 향상시켜 줍니다 (솔루션 탐색기, IntelliSense 강화, 테스트 탐색기 등 포함).
 * 데이터베이스: 로컬 또는 네트워크에 접근 가능한 Oracle 및 MS SQL 데이터베이스 서버가 준비되어 있어야 합니다.
 * OPC 서버: 접근 가능한 OPC UA 또는 OPC DA 서버가 필요합니다. (테스트용 OPC 서버를 로컬에 설치할 수도 있습니다.)
 
 
2. 프로젝트 생성 및 구조
VS Code 터미널 또는 개발자 명령 프롬프트를 열고 다음 명령어를 사용하여 WPF 프로젝트를 생성합니다.
dotnet new wpf -n YourProjectName -f net8.0 # 또는 원하는 .NET 버전 (예: net6.0)
cd YourProjectName
 
이제 기본적인 MVVM 패턴에 따라 폴더 구조를 만듭니다.
YourProjectName/

├── YourProjectName.csproj # 프로젝트 파일
├── App.xaml # 애플리케이션 정의
├── App.xaml.cs # 애플리케이션 코드 비하인드 (DI 설정 등)
├── MainWindow.xaml # 메인 윈도우 UI
├── MainWindow.xaml.cs # 메인 윈도우 코드 비하인드 (DataContext 설정 등)
├── appsettings.json # 설정 파일 (DB 연결 문자열, OPC 서버 주소 등)

├── Views/ # UI 요소 (XAML 파일 - Windows, UserControls)
│ └── ExampleView.xaml
│ └── ExampleView.xaml.cs

├── ViewModels/ # 뷰 로직 및 상태 관리
│ └── MainWindowViewModel.cs
│ └── ExampleViewModel.cs
│ └── ViewModelBase.cs # (Optional) INotifyPropertyChanged 구현 베이스 클래스

├── Models/ # 데이터 구조 정의 (POCO 클래스)
│ └── OpcTagData.cs
│ └── DatabaseRecord.cs

├── Services/ # 비즈니스 로직, 데이터 접근, 외부 서비스 통신
│ ├── Interfaces/ # 서비스 인터페이스
│ │ ├── IDataService.cs
│ │ └── IOpcService.cs
│ ├── Implementations/ # 서비스 구현체
│ │ ├── OracleDataService.cs
│ │ └── MsSqlDataService.cs
│ │ └── OpcUaService.cs # (OPC UA 사용 시 예시)

├── DataAccess/ # 데이터베이스 접근 계층 (EF Core DbContext 등)
│ └── AppDbContext.cs # (EF Core 사용 시 예시)

└── Helpers/ # 유틸리티 클래스 (Converters, Extensions 등)
    └── RelayCommand.cs # (MVVM Framework 미사용 시 직접 구현)
 
3. NuGet 패키지 설치
프로젝트에 필요한 라이브러리들을 NuGet을 통해 설치합니다. VS Code 터미널에서 다음 명령어를 실행합니다.
 * MVVM Framework (CommunityToolkit.Mvvm 추천): 가볍고 현대적인 MVVM 지원
   dotnet add package CommunityToolkit.Mvvm
 
 * Dependency Injection (DI): 서비스 관리를 위해 필수적
   dotnet add package Microsoft.Extensions.Hosting
 
 * Configuration: appsettings.json 파일 사용
   dotnet add package Microsoft.Extensions.Configuration.Json
dotnet add package Microsoft.Extensions.Configuration.Binder
 
 * Entity Framework Core (EF Core - 데이터베이스 ORM):
   * SQL Server Provider:
     dotnet add package Microsoft.EntityFrameworkCore.SqlServer
 
   * Oracle Provider: (Oracle 공식 또는 커뮤니티 제공)
     dotnet add package Oracle.EntityFrameworkCore # Oracle 공식 제공
# 또는 dotnet add package Devart.Data.Oracle.EFCore (상용) 등
 
   * EF Core Tools (Migration 등):
     dotnet add package Microsoft.EntityFrameworkCore.Tools
 
 * OPC UA Client Library (Opc.Ua.Client 추천): OPC Foundation 제공 공식 라이브러리
   dotnet add package Opc.Ua.Client
 
   (참고: OPC DA를 사용해야 한다면, 해당 라이브러리 (종종 상용)를 찾아 설치해야 합니다.)
 
 
4. 핵심 코드 구현 (기본 틀)
 * appsettings.json 설정:
   {
  "ConnectionStrings": {
    "MsSql": "Server=(localdb)\\mssqllocaldb;Database=YourMsSqlDb;Trusted_Connection=True;",
    "Oracle": "User ID=your_user;Password=your_password;Data Source=your_oracle_tns_alias_or_connect_string;"
  },
  "OpcServer": {
    "EndpointUrl": "opc.tcp://your_opc_server_address:port/path"
  }
}
 
   (실제 연결 정보로 수정해야 합니다.)
 * App.xaml.cs - DI 설정 및 애플리케이션 시작:
   using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Configuration;
using System.IO;
using System.Windows;
using YourProjectName.ViewModels;
using YourProjectName.Services.Interfaces;
using YourProjectName.Services.Implementations;
using YourProjectName.DataAccess; // EF Core 사용 시
using Microsoft.EntityFrameworkCore; // EF Core 사용 시
 
public partial class App : Application
{
    private IHost _host;
 
    public App()
    {
        _host = Host.CreateDefaultBuilder() // 기본 호스트 빌더 사용
            .ConfigureAppConfiguration((context, config) =>
            {
                // appsettings.json 로드
                config.SetBasePath(Directory.GetCurrentDirectory());
                config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
            })
            .ConfigureServices((context, services) =>
            {
                ConfigureServices(context.Configuration, services);
            })
            .Build();
    }
 
    private void ConfigureServices(IConfiguration configuration, IServiceCollection services)
    {
        // --- 데이터베이스 서비스 등록 (EF Core 예시) ---
        // MS SQL DbContext 등록
        services.AddDbContext<AppDbContext>(options =>
            options.UseSqlServer(configuration.GetConnectionString("MsSql")));
 
        // Oracle DbContext 등록 (별도 Context 또는 동일 Context 내 분기)
        // 예시: services.AddDbContext<OracleDbContext>(...);
 
        // --- 데이터 접근 서비스 등록 ---
        // 조건부 등록 또는 별도 서비스로 구현 가능
        services.AddTransient<IDataService, MsSqlDataService>(); // 기본값 또는 선택 로직 추가
        // services.AddTransient<IDataService, OracleDataService>();
 
        // --- OPC 서비스 등록 ---
        services.AddSingleton<IOpcService>(sp =>
        {
            var endpointUrl = configuration.GetValue<string>("OpcServer:EndpointUrl");
            return new OpcUaService(endpointUrl); // OPC UA 서비스 구현체
        });
 
 
        // --- ViewModels 등록 ---
        services.AddTransient<MainWindowViewModel>();
        services.AddTransient<ExampleViewModel>();
 
        // --- Views 등록 (MainWindow는 직접 생성) ---
        // services.AddTransient<ExampleView>(); // 필요시
    }
 
    protected override async void OnStartup(StartupEventArgs e)
    {
        await _host.StartAsync(); // 호스트 시작
 
        // MainWindow 시작
        var mainWindow = new MainWindow
        {
            // MainWindowViewModel을 DI 컨테이너에서 가져와 DataContext에 설정
            DataContext = _host.Services.GetRequiredService<MainWindowViewModel>()
        };
        mainWindow.Show();
 
        base.OnStartup(e);
    }
 
    protected override async void OnExit(ExitEventArgs e)
    {
        using (_host)
        {
            await _host.StopAsync(TimeSpan.FromSeconds(5)); // 호스트 정지
        }
        base.OnExit(e);
    }
}
 
 * ViewModelBase.cs (CommunityToolkit.Mvvm 사용 시 불필요):
   CommunityToolkit.Mvvm의 ObservableObject를 상속하면 됩니다. 직접 구현한다면 INotifyPropertyChanged 인터페이스를 구현합니다.
 * MainWindowViewModel.cs (예시):
   using CommunityToolkit.Mvvm.ComponentModel; // ObservableObject 사용
using CommunityToolkit.Mvvm.Input; // RelayCommand 사용
using System.Threading.Tasks;
using System.Windows.Input;
using YourProjectName.Services.Interfaces;
using YourProjectName.Models;
using System.Collections.ObjectModel;
 
namespace YourProjectName.ViewModels
{
    public partial class MainWindowViewModel : ObservableObject // ObservableObject 상속
    {
        private readonly IDataService _dataService;
        private readonly IOpcService _opcService;
 
        // ObservableProperty 특성: 자동으로 PropertyChanged 이벤트 발생시키는 코드 생성
        [ObservableProperty]
        private string _statusMessage = "Ready";
 
        [ObservableProperty]
        private ObservableCollection<DatabaseRecord> _databaseRecords = new();
 
        [ObservableProperty]
        private ObservableCollection<OpcTagData> _opcTags = new();
 
        // 생성자에서 서비스 주입 (DI)
        public MainWindowViewModel(IDataService dataService, IOpcService opcService)
        {
            _dataService = dataService;
            _opcService = opcService;
 
            LoadDataCommand = new AsyncRelayCommand(LoadDataAsync);
            ReadOpcDataCommand = new AsyncRelayCommand(ReadOpcDataAsync);
        }
 
        // RelayCommand 특성: ICommand 구현 코드를 자동으로 생성
        [RelayCommand]
        private async Task LoadDataAsync()
        {
            StatusMessage = "Loading data from database...";
            try
            {
                var data = await _dataService.GetAllRecordsAsync(); // 예시 메서드
                DatabaseRecords.Clear();
                foreach (var record in data)
                {
                    DatabaseRecords.Add(record);
                }
                StatusMessage = "Database data loaded successfully.";
            }
            catch (System.Exception ex)
            {
                StatusMessage = $"Error loading database data: {ex.Message}";
                // TODO: Log error
            }
        }
 
        [RelayCommand]
        private async Task ReadOpcDataAsync()
        {
            StatusMessage = "Reading OPC data...";
            try
            {
                // 예시: 특정 태그 목록 읽기
                var tagIdsToRead = new List<string> { "ns=2;s=YourTag1", "ns=2;s=YourTag2" };
                var opcData = await _opcService.ReadNodesAsync(tagIdsToRead); // 예시 메서드
 
                OpcTags.Clear();
                foreach (var tagData in opcData)
                {
                    OpcTags.Add(tagData);
                }
                StatusMessage = "OPC data read successfully.";
            }
            catch (System.Exception ex)
            {
                StatusMessage = $"Error reading OPC data: {ex.Message}";
                // TODO: Log error, handle OPC connection issues
            }
        }
    }
}
 
 * MainWindow.xaml (예시):
   <Window x:Class="YourProjectName.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:YourProjectName"
        xmlns:vm="clr-namespace:YourProjectName.ViewModels"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800"
        d:DataContext="{d:DesignInstance Type=vm:MainWindowViewModel, IsDesignTimeCreatable=True}">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
 
        <StackPanel Grid.Row="0" Orientation="Horizontal" Margin="5">
            <Button Content="Load DB Data" Command="{Binding LoadDataCommand}" Margin="5"/>
            <Button Content="Read OPC Data" Command="{Binding ReadOpcDataCommand}" Margin="5"/>
        </StackPanel>
 
        <TabControl Grid.Row="1">
            <TabItem Header="Database Data">
                <ListView ItemsSource="{Binding DatabaseRecords}">
                    <ListView.View>
                        <GridView>
                            <GridViewColumn Header="ID" DisplayMemberBinding="{Binding Id}"/>
                            <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"/>
                            </GridView>
                    </ListView.View>
                </ListView>
            </TabItem>
            <TabItem Header="OPC Data">
                 <ListView ItemsSource="{Binding OpcTags}">
                    <ListView.View>
                        <GridView>
                            <GridViewColumn Header="Tag ID" DisplayMemberBinding="{Binding NodeId}"/>
                            <GridViewColumn Header="Value" DisplayMemberBinding="{Binding Value}"/>
                            <GridViewColumn Header="Timestamp" DisplayMemberBinding="{Binding Timestamp}"/>
                            <GridViewColumn Header="Status" DisplayMemberBinding="{Binding StatusCode}"/>
                        </GridView>
                    </ListView.View>
                </ListView>
            </TabItem>
        </TabControl>
 
 
        <StatusBar Grid.Row="2">
            <StatusBarItem>
                <TextBlock Text="{Binding StatusMessage}" />
            </StatusBarItem>
        </StatusBar>
    </Grid>
</Window>
 
 * 서비스 인터페이스 및 구현:
   * IDataService, IOpcService 인터페이스 정의 (메서드 시그니처 포함)
   * MsSqlDataService, OracleDataService, OpcUaService 클래스 구현
     * DataService: 생성자에서 AppDbContext (또는 해당 DB Context)를 주입받아 사용. EF Core 메서드 (Add, Update, Remove, ToListAsync 등) 활용.
     * OpcService: 생성자에서 OPC 서버 Endpoint URL을 받음. Opc.Ua.Client 라이브러리를 사용하여 연결(ConnectAsync), 노드 읽기(ReadValueAsync, ReadNodesAsync), 쓰기 등 구현. 세션 관리 및 오류 처리 중요.
 * EF Core DbContext (AppDbContext.cs):
   using Microsoft.EntityFrameworkCore;
using YourProjectName.Models;
 
namespace YourProjectName.DataAccess
{
    public class AppDbContext : DbContext
    {
        public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
 
        public DbSet<DatabaseRecord> DatabaseRecords { get; set; } // 예시 모델
 
        // Oracle/MS SQL 모델 분리 필요 시 별도 DbContext 사용 또는 OnModelCreating에서 테이블/스키마 분리
 
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            // Fluent API 설정 (필요시)
            // modelBuilder.Entity<DatabaseRecord>().ToTable("YOUR_TABLE_NAME");
            base.OnModelCreating(modelBuilder);
        }
    }
}
 
   (Oracle과 MS SQL의 스키마나 테이블 구조가 다르다면, 별도의 DbContext를 사용하거나 OnModelCreating에서 조건을 분기하여 모델을 구성하는 것이 좋습니다.)
 
 
5. 빌드 및 실행
 * 빌드: VS Code 터미널에서 dotnet build 명령 실행
 * 실행/디버깅:
   * VS Code의 '실행 및 디버그'(Ctrl+Shift+D) 탭 사용. C# Dev Kit이 설치되어 있다면 자동으로 .NET Core Launch (console) 구성이 생성됩니다. F5 키를 눌러 디버깅 시작.
   * 터미널에서 dotnet run 명령 실행
 
 
6. 배포
애플리케이션 배포 방법은 여러 가지가 있습니다.
 * Framework-Dependent Deployment (FDD):
   * 대상 PC에 .NET 런타임이 설치되어 있어야 합니다.
   * 배포 파일 크기가 작습니다.
   * 명령어: dotnet publish -c Release -o ./publish_fdd
   * publish_fdd 폴더의 내용을 배포합니다.
 * Self-Contained Deployment (SCD):
   * 대상 PC에 .NET 런타임이 없어도 실행됩니다.
   * 배포 파일 크기가 큽니다.
   * Runtime Identifier (win-x64, win-x86 등)를 지정해야 합니다.
   * 명령어: dotnet publish -c Release -r win-x64 --self-contained true -o ./publish_scd
   * publish_scd 폴더의 내용을 배포합니다.
 * Single File Deployment: (SCD와 함께 사용 가능)
   * 실행 파일 하나로 묶어 배포 편의성을 높입니다.
   * 명령어: dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -o ./publish_single
 * 설치 프로그램 제작: MSIX, Inno Setup, WiX Toolset 등을 사용하여 사용자 친화적인 설치 프로그램을 만들 수 있습니다.
추가 고려 사항:
 * 오류 처리 및 로깅: try-catch 블록을 적절히 사용하고, NLog나 Serilog 같은 로깅 라이브러리를 도입하여 오류 및 상태를 기록하는 것이 좋습니다.
 * 비동기 프로그래밍: UI 응답성을 유지하기 위해 데이터베이스 접근, OPC 통신 등 I/O 작업은 async/await를 적극적으로 사용해야 합니다.
 * Configuration 관리: appsettings.json 외에 사용자별 설정, 환경별 설정 분리 등을 고려할 수 있습니다.
 * 테스트: 유닛 테스트 및 통합 테스트 프로젝트를 추가하여 코드의 안정성을 확보하는 것이 중요합니다.
 * OPC DA: 만약 OPC DA 서버에 연결해야 한다면, .NET Framework 기반의 라이브러리나 상용 OPC DA 클라이언트 라이브러리를 찾아 사용해야 할 수 있습니다. (COM Interop 필요 가능성)
이 기본 틀을 바탕으로 실제 요구사항에 맞게 코드를 확장하고 상세화하면 됩니다. 궁금한 점이 있다면 언제든지 다시 질문해주세요.
 























* 두 개의 데이터 서비스 구현체 유지: MsSqlDataService와 OracleDataService를 모두 유지합니다.
* DbContext 구성 분리: 각 서비스가 해당 데이터베이스에 맞게 구성된 DbContext 인스턴스를 받도록 Dependency Injection(DI) 설정을 수정합니다.
* 서비스 선택 메커니즘 구현: 애플리케이션이 실행될 때 또는 특정 시점에 어떤 데이터 서비스(MsSqlDataService 또는 OracleDataService)를 사용할지 결정하는 방법을 만듭니다.
여기서는 appsettings.json의 설정 값을 통해 시작 시 사용할 데이터베이스를 결정하는 방법과, 런타임에 필요에 따라 서비스를 선택할 수 있는 팩토리(Factory) 패턴을 사용하는 방법을 보여드리겠습니다.
1. appsettings.json에 설정 추가
어떤 데이터베이스를 기본으로 사용할지 지정하는 설정을 추가합니다.
{
  "ConnectionStrings": {
    "MsSql": "Server=(localdb)\\mssqllocaldb;Database=YourMsSqlDb;Trusted_Connection=True;",
    "Oracle": "User ID=your_user;Password=your_password;Data Source=your_oracle_tns_alias_or_connect_string;"
  },
  "OpcServer": {
    "EndpointUrl": "opc.tcp://your_opc_server_address:port/path"
  },
  // 추가: 사용할 데이터베이스 제공자 지정 (MsSql 또는 Oracle)
  "DatabaseProvider": "MsSql"
}

2. App.xaml.cs 수정 (DI 설정 변경)
ConfigureServices 메서드에서 appsettings.json의 DatabaseProvider 값을 읽어 조건부로 IDataService를 등록하거나, 두 서비스를 모두 등록하고 팩토리를 설정합니다.
방법 A: 시작 시 설정에 따라 하나만 선택하여 등록 (간단)
이 방법은 앱 실행 중 데이터베이스를 변경하지 않고, 시작 시 설정된 하나만 사용하는 경우에 적합합니다.
// App.xaml.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using YourProjectName.DataAccess;
using YourProjectName.Services.Implementations;
using YourProjectName.Services.Interfaces;
// ... other usings

private void ConfigureServices(IConfiguration configuration, IServiceCollection services)
{
    // --- 로깅, ViewModel 등 다른 서비스 등록 ---
    services.AddLogging(logging =>
    {
        logging.ClearProviders();
        logging.AddConsole();
        logging.AddDebug();
    });
    services.AddTransient<MainWindowViewModel>();
    services.AddSingleton<IOpcService>(sp => /* ... OPC Service setup ... */);

    // --- DbContext 옵션 구성 (Db Provider 선택) ---
    var dbProvider = configuration.GetValue<string>("DatabaseProvider")?.ToUpperInvariant();

    // DbContext 옵션을 빌드하는 팩토리 함수 정의
    Action<DbContextOptionsBuilder> configureDbOptions = options =>
    {
        if (dbProvider == "MSSQL")
        {
            options.UseSqlServer(configuration.GetConnectionString("MsSql"));
        }
        else if (dbProvider == "ORACLE")
        {
            options.UseOracle(configuration.GetConnectionString("Oracle"));
        }
        else
        {
            // 기본값 또는 오류 처리
            options.UseSqlServer(configuration.GetConnectionString("MsSql"));
            // 또는 throw new InvalidOperationException("Invalid DatabaseProvider configured.");
        }
    };

    // DbContext 등록 (Pool 사용 권장 - 성능 향상)
    services.AddDbContextPool<AppDbContext>(configureDbOptions);
    // 또는 AddDbContext<AppDbContext>(configureDbOptions);


    // --- 조건부 데이터 서비스 등록 ---
    if (dbProvider == "ORACLE")
    {
        services.AddTransient<IDataService, OracleDataService>();
        _logger.LogInformation("Using Oracle Database Provider."); // App 클래스에 _logger 필드 추가 필요
    }
    else // 기본값 또는 MsSql 명시 시
    {
        services.AddTransient<IDataService, MsSqlDataService>();
        _logger.LogInformation("Using MS SQL Server Database Provider."); // App 클래스에 _logger 필드 추가 필요
    }

    // (선택) 구체 타입도 필요하면 등록
    services.AddTransient<MsSqlDataService>();
    services.AddTransient<OracleDataService>();
}

// App 클래스에 로거 필드 추가 (ConfigureServices 에서 사용하기 위함)
private readonly ILogger<App> _logger;

public App()
{
     // ... Host 빌드 코드 ...
     _logger = _host.Services.GetRequiredService<ILogger<App>>(); // 호스트 생성 후 로거 가져오기
     // ... 기존 코드 ...
}

방법 B: 두 서비스를 모두 등록하고 팩토리 패턴 사용 (유연)
런타임에 데이터베이스를 전환하거나 동시에 두 데이터베이스에 접근해야 할 때 유용합니다.
B-1. IDataServiceFactory 인터페이스 및 구현체 생성
// Services/Interfaces/IDataServiceFactory.cs
using YourProjectName.Common; // DatabaseType Enum 정의 가정

namespace YourProjectName.Services.Interfaces
{
    // 선택: 사용할 데이터베이스 타입을 나타내는 Enum
    public enum DatabaseType { MsSql, Oracle }

    public interface IDataServiceFactory
    {
        IDataService GetService(DatabaseType dbType);
        IDataService GetConfiguredService(); // 설정 파일 기반 서비스 반환
    }
}

// Services/Implementations/DataServiceFactory.cs
using Microsoft.Extensions.Configuration;
using System;
using YourProjectName.Common;
using YourProjectName.Services.Interfaces;

namespace YourProjectName.Services.Implementations
{
    public class DataServiceFactory : IDataServiceFactory
    {
        private readonly MsSqlDataService _msSqlService;
        private readonly OracleDataService _oracleService;
        private readonly IConfiguration _configuration;

        // 생성자에서 두 서비스와 설정을 주입받음
        public DataServiceFactory(MsSqlDataService msSqlService, OracleDataService oracleService, IConfiguration configuration)
        {
            _msSqlService = msSqlService ?? throw new ArgumentNullException(nameof(msSqlService));
            _oracleService = oracleService ?? throw new ArgumentNullException(nameof(oracleService));
            _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
        }

        // 요청된 타입에 맞는 서비스 반환
        public IDataService GetService(DatabaseType dbType)
        {
            return dbType switch
            {
                DatabaseType.MsSql => _msSqlService,
                DatabaseType.Oracle => _oracleService,
                _ => throw new ArgumentOutOfRangeException(nameof(dbType), $"Unsupported database type: {dbType}")
            };
        }

        // appsettings.json 설정에 따라 서비스 반환
        public IDataService GetConfiguredService()
        {
            var dbProvider = _configuration.GetValue<string>("DatabaseProvider")?.ToUpperInvariant();
            if (dbProvider == "ORACLE")
            {
                return _oracleService;
            }
            // 기본값은 MsSql
            return _msSqlService;
        }
    }
}

// Common/DatabaseType.cs (새 폴더 및 파일 추가)
namespace YourProjectName.Common
{
    public enum DatabaseType
    {
        MsSql,
        Oracle
    }
}

B-2. App.xaml.cs에서 팩토리 설정
// App.xaml.cs 수정 (ConfigureServices)

private void ConfigureServices(IConfiguration configuration, IServiceCollection services)
{
    // ... 로깅, ViewModel, OPC 서비스 등 등록 ...

    // --- DbContext 옵션 구성 (각 Provider 별로 옵션 객체 생성) ---
    // MS SQL 용 옵션
    services.AddSingleton(sp =>
    {
        var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>();
        optionsBuilder.UseSqlServer(configuration.GetConnectionString("MsSql"));
        // 필요시 .UseLoggerFactory(sp.GetRequiredService<ILoggerFactory>()); 추가
        return optionsBuilder.Options;
    });

    // Oracle 용 옵션 (별도 등록) - 주의: DbContextOptions<AppDbContext>를 두 번 등록하면 문제 발생 가능성 있음
    // -> 더 나은 방법: 각 서비스 등록 시 팩토리 람다에서 직접 DbContext 생성 및 구성

    // --- 각 데이터 서비스를 구체 타입으로 등록 ---
    // MsSqlDataService 등록 시, SQL Server용 DbContext를 사용하도록 설정
    services.AddTransient<MsSqlDataService>(sp =>
    {
        var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>();
        optionsBuilder.UseSqlServer(configuration.GetConnectionString("MsSql"))
                      .UseLoggerFactory(sp.GetRequiredService<ILoggerFactory>()); // 로깅 설정
        var dbContext = new AppDbContext(optionsBuilder.Options); // 직접 DbContext 생성
        var logger = sp.GetRequiredService<ILogger<MsSqlDataService>>();
        return new MsSqlDataService(dbContext, logger);
    });

    // OracleDataService 등록 시, Oracle용 DbContext를 사용하도록 설정
    services.AddTransient<OracleDataService>(sp =>
    {
        var optionsBuilder = new DbContextOptionsBuilder<AppDbContext>();
        optionsBuilder.UseOracle(configuration.GetConnectionString("Oracle"))
                      .UseLoggerFactory(sp.GetRequiredService<ILoggerFactory>()); // 로깅 설정
        var dbContext = new AppDbContext(optionsBuilder.Options); // 직접 DbContext 생성
        var logger = sp.GetRequiredService<ILogger<OracleDataService>>();
        return new OracleDataService(dbContext, logger);
    });


    // --- 데이터 서비스 팩토리 등록 ---
    services.AddSingleton<IDataServiceFactory, DataServiceFactory>();

    // 중요: IDataService 인터페이스 자체를 직접 등록하지 않거나,
    // 기본 서비스(예: 설정 파일 기반)를 지정할 수 있음
    services.AddTransient<IDataService>(sp => sp.GetRequiredService<IDataServiceFactory>().GetConfiguredService());
}

B-3. ViewModel에서 팩토리 사용
// MainWindowViewModel.cs

// private readonly IDataService _dataService; // 기존 코드 제거
private readonly IDataServiceFactory _dataServiceFactory;
private IDataService _currentDataService; // 현재 사용할 서비스 인스턴스
private DatabaseType _selectedDatabaseType = DatabaseType.MsSql; // 기본값 또는 설정에서 로드

// 생성자에서 IDataServiceFactory 주입
public MainWindowViewModel(IDataServiceFactory dataServiceFactory, IOpcService opcService, ILogger<MainWindowViewModel> logger)
{
    _dataServiceFactory = dataServiceFactory ?? throw new ArgumentNullException(nameof(dataServiceFactory));
    _opcService = opcService ?? throw new ArgumentNullException(nameof(opcService));
    _logger = logger ?? throw new ArgumentNullException(nameof(logger));

    // 설정 파일 또는 사용자 선택에 따라 초기 서비스 설정
    InitializeCurrentDataService();

    // ... (나머지 생성자 로직 및 명령) ...
}

private void InitializeCurrentDataService()
{
     // 예시: 설정 파일 기반 초기화
    // _currentDataService = _dataServiceFactory.GetConfiguredService();
    // 또는 ViewModel 내 선택된 타입 기반
    _currentDataService = _dataServiceFactory.GetService(_selectedDatabaseType);
    StatusMessage = $"현재 데이터베이스: {_selectedDatabaseType}";
}

// 필요시 DB 변경 메서드 및 커맨드 추가
// 예:
// public DatabaseType SelectedDatabaseType
// {
//     get => _selectedDatabaseType;
//     set
//     {
//         if (SetProperty(ref _selectedDatabaseType, value))
//         {
//              InitializeCurrentDataService(); // 선택 변경 시 현재 서비스 업데이트
//         }
//     }
// }

// LoadDataAsync 및 다른 DB 관련 메서드에서 _dataService 대신 _currentDataService 사용
[RelayCommand]
private async Task LoadDataAsync()
{
    if (IsBusy) return;
    IsBusy = true;
    // ...
    try
    {
        // _currentDataService 를 사용!
        var data = await _currentDataService.GetAllRecordsAsync();
        // ...
    }
    // ...
    finally
    {
        IsBusy = false;
    }
}

요약:
* 방법 A (조건부 등록): 설정 파일(appsettings.json)에 DatabaseProvider를 추가하고, App.xaml.cs에서 이 값을 읽어 IDataService에 해당하는 구현체(MsSqlDataService 또는 OracleDataService) 중 하나만 등록합니다. DbContext도 이 설정에 따라 구성합니다. 앱 실행 중 DB 변경은 불가합니다.
* 방법 B (팩토리 패턴): IDataServiceFactory를 만들고 DI에 등록합니다. App.xaml.cs에서는 각 서비스(MsSqlDataService, OracleDataService)가 올바른 DbContext 옵션으로 생성되도록 팩토리 람다를 사용하여 등록합니다. ViewModel에서는 IDataServiceFactory를 주입받아 필요에 따라 적절한 IDataService 구현체를 얻어 사용합니다. 런타임 변경이 가능하고 더 유연합니다.
프로젝트의 요구사항에 맞는 방법을 선택하시면 됩니다. 일반적으로 시작 시 DB를 결정하고 변경하지 않는다면 방법 A가 더 간단하고, 런타임 전환이 필요하다면 방법 B가 적합합니다.