C# MVVM 1
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가 적합합니다.