[WPF 문법] WPF MVVM 패턴 이용하여 오라클 DB 연동하기
- C#/WPF
- 2021. 9. 19. 18:56
소개
- 안녕하세요. 오늘은 WPF MVVM 패턴을 토대로 오라클 DB 연동하는 방법에 대해서 알려 드리려고 합니다.
- 크게 어려운 부분은 없기 때문에 각각의 Class들과 소스코드를 올려서 필요하신 부분들은 해당 내용들 참고해서 가져다가 쓰시면 되겠습니다!
- 그럼 바로 예제 코드를 작성해 보도록 하겠습니다.
예제코드
프로젝트 구성
- 다음은 WPF MVVM 패턴으로 오라클 DB 연동 프로젝트 구성도 입니다.
- 크게 Database, Model, ViewModel 폴더가 있습니다.
- 이제 각각 폴더 안에 있는 클래스들의 내용을 보여 드리겠습니다.
DataBase
OracleDBManager.cs
- 오라클 연동 관련 소스코드 입니다.
using System;
using System.Data;
using System.Data.OracleClient;
namespace Oracel_Connect_Program.Class
{
public sealed class OracleDBManager : Singleton<OracleDBManager>
{
public event ExceptionEventHandler ExceptionEvent;
public string LastExceptionString = string.Empty;
public string ConnectionString = string.Empty;
public string Address = string.Empty;
public string Port = string.Empty;
private OracleCommand LastExecutedCommand = null;
private int RetryCnt = 0;
public bool IsRunning
{
get
{
return CheckDBConnected();
}
}
public OracleConnection Connection { get; private set; }
public OracleDBManager()
{
}
public static OracleDBManager GetNewInstanceConnection()
{
if (Instance == null) return null;
OracleDBManager dbManager = new OracleDBManager
{
ConnectionString = Instance.ConnectionString
};
dbManager.GetConnection();
dbManager.ExceptionEvent = Instance.ExceptionEvent;
return dbManager;
}
public bool GetConnection()
{
try
{
if (this.Connection != null)
{
this.Connection.Close();
this.Connection.Dispose();
this.Connection = null;
}
if (ConnectionString == string.Empty)
SetConnectionString();
Connection = new OracleConnection(ConnectionString);
if (this.Address != string.Empty) //주소가 없을 경우 그냥 리턴
Connection.Open();
}
catch (Exception ex)
{
System.Reflection.MemberInfo info = System.Reflection.MethodInfo.GetCurrentMethod();
string id = string.Format("{0}.{1}", info.ReflectedType.Name, info.Name);
if (this.ExceptionEvent != null)
this.ExceptionEvent(id, ex);
return false;
}
if (Connection.State == ConnectionState.Open)
return true;
else
return false;
}
public int ExecuteNonQuery(string query)
{
lock (this)
{
RetryCnt = 0;
int result = Execute_NonQuery(query);
return result;
}
}
public int ExecuteNonQuery(string query, params object[] oParams)
{
lock (this)
{
RetryCnt = 0;
int result = Execute_NonQuery(query, oParams);
return result;
}
}
public bool HasRows(string query)
{
lock (this)
{
RetryCnt = 0;
OracleDataReader result = ExecuteReader(query);
return result.HasRows;
}
}
public OracleDataReader ExecuteReaderQuery(string query)
{
lock (this)
{
RetryCnt = 0;
OracleDataReader result = ExecuteReader(query);
return result;
}
}
public DataSet ExecuteDsQuery(DataSet ds, string query)
{
ds.Reset();
lock (this)
{
RetryCnt = 0;
return ExecuteDataAdt(ds, query);
}
}
public int ExecuteProcedure(string procName, params string[] pValues)
{
lock (this)
{
RetryCnt = 0;
return ExecuteProcedureAdt(procName, pValues);
}
}
public void Close()
{
Connection.Close();
}
public void QueryCancel()
{
if (this.LastExecutedCommand != null)
{
this.LastExecutedCommand.Cancel();
}
}
#region private..........................................................
private void SetConnectionString()
{
string user = XmlManager.GetValue("DATABASE", "USER");
string pwd = XmlManager.GetValue("DATABASE", "PWD");
string port = XmlManager.GetValue("DATABASE", "PORT");
string svr = XmlManager.GetValue("DATABASE", "SERVICE_NAME");
string addr01 = XmlManager.GetValue("DATABASE", "D_ADDR01");
string addr02 = XmlManager.GetValue("DATABASE", "D_ADDR02");
string address01 = string.Format("(ADDRESS = (PROTOCOL = TCP)(HOST = {0})(PORT = {1}))", addr01, port);
string address02 = string.Format("(ADDRESS = (PROTOCOL = TCP)(HOST = {0})(PORT = {1}))", addr02, port);
string dataSource = string.Format(@"(DESCRIPTION =(ADDRESS_LIST ={0}{1})(CONNECT_DATA =(", address01, address02);
dataSource += svr == string.Empty ? string.Format("SID = {0})))", svr) : string.Format("SERVICE_NAME = {0})))", svr);
this.Address = addr01;
this.Port = port;
this.ConnectionString = "User Id=" + user + ";Password=" + pwd + ";Data Source=" + dataSource;
}
private int Execute_NonQuery(string query)
{
int result = 0;
try
{
OracleCommand cmd = new OracleCommand
{
Connection = this.Connection,
CommandText = query
};
LastExecutedCommand = cmd;
result = cmd.ExecuteNonQuery();
}
catch (Exception ex)
{
//연결 해제 여부 확인 후 해제 시 재연결 후 다시 시도...
if (RetryCnt < 1 && CheckDBConnected() == false)
{
RetryCnt++;
GetConnection();
Exception ex02 = new Exception("Reconnect to database");
if (this.ExceptionEvent != null)
this.ExceptionEvent(string.Empty, ex02);
result = Execute_NonQuery(query);
return result;
}
//사용자 Cancel
if (ex.Message.Contains("ORA-01013"))
{
return -1;
}
System.Reflection.MemberInfo info = System.Reflection.MethodInfo.GetCurrentMethod();
string id = string.Format("{0}.{1}\n[{2}]", info.ReflectedType.Name, info.Name, query);
if (this.ExceptionEvent != null)
this.ExceptionEvent(id, ex);
this.LastExceptionString = ex.Message;
result = -1;
}
return result;
}
private int Execute_NonQuery(string query, params object[] oParams)
{
int result = 0;
try
{
OracleCommand cmd = new OracleCommand
{
Connection = this.Connection,
CommandText = query
};
for (int i = 0; i < oParams.Length; i += 2)
{
cmd.Parameters.Add(oParams[i].ToString(), oParams[i + 1]);
}
LastExecutedCommand = cmd;
result = cmd.ExecuteNonQuery();
}
catch (Exception ex)
{
//연결 해제 여부 확인 후 해제 시 재연결 후 다시 시도...
if (RetryCnt < 1 && CheckDBConnected() == false)
{
RetryCnt++;
GetConnection();
Exception ex02 = new Exception("Reconnect to database");
if (this.ExceptionEvent != null)
this.ExceptionEvent(string.Empty, ex02);
result = Execute_NonQuery(query, oParams);
return result;
}
//사용자 Cancel
if (ex.Message.Contains("ORA-01013"))
{
return -1;
}
System.Reflection.MemberInfo info = System.Reflection.MethodInfo.GetCurrentMethod();
string id = string.Format("{0}.{1}\n[{2}]", info.ReflectedType.Name, info.Name, query);
if (this.ExceptionEvent != null)
this.ExceptionEvent(id, ex);
this.LastExceptionString = ex.Message;
result = -1;
}
return result;
}
private OracleDataReader ExecuteReader(string query)
{
OracleDataReader result = null;
try
{
OracleCommand cmd = new OracleCommand
{
Connection = this.Connection,
CommandText = query
};
LastExecutedCommand = cmd;
result = cmd.ExecuteReader();
}
catch (Exception ex)
{
//연결 해제 여부 확인 후 해제 시 재연결 후 다시 시도...
if (RetryCnt < 1 && CheckDBConnected() == false)
{
RetryCnt++;
GetConnection();
Exception ex02 = new Exception("Reconnect to database");
if (this.ExceptionEvent != null)
this.ExceptionEvent(string.Empty, ex02);
result = ExecuteReader(query);
return result;
}
//사용자 Cancel
if (ex.Message.Contains("ORA-01013"))
{
return null;
}
System.Reflection.MemberInfo info = System.Reflection.MethodInfo.GetCurrentMethod();
string id = string.Format("{0}.{1}\n[{2}]", info.ReflectedType.Name, info.Name, query);
if (this.ExceptionEvent != null)
this.ExceptionEvent(id, ex);
this.LastExceptionString = ex.Message;
return null;
}
return result;
}
private DataSet ExecuteDataAdt(DataSet ds, string query)
{
try
{
OracleDataAdapter cmd = new OracleDataAdapter
{
SelectCommand = new OracleCommand()
};
cmd.SelectCommand.Connection = this.Connection;
cmd.SelectCommand.CommandText = query;
LastExecutedCommand = cmd.SelectCommand;
cmd.Fill(ds);
}
catch (Exception ex)
{
//연결 해제 여부 확인 후 해제 시 재연결 후 다시 시도...
if (RetryCnt < 1 && CheckDBConnected() == false)
{
RetryCnt++;
GetConnection();
Exception ex02 = new Exception("Reconnect to database");
if (this.ExceptionEvent != null)
this.ExceptionEvent(string.Empty, ex02);
ds = ExecuteDataAdt(ds, query);
return ds;
}
//사용자 Cancel
if (ex.Message.Contains("ORA-01013"))
{
return null;
}
System.Reflection.MemberInfo info = System.Reflection.MethodInfo.GetCurrentMethod();
string id = string.Format("{0}.{1}\n[{2}]", info.ReflectedType.Name, info.Name, query);
if (this.ExceptionEvent != null)
this.ExceptionEvent(id, ex);
this.LastExceptionString = ex.Message;
return null;
}
return ds;
}
private int ExecuteProcedureAdt(string procName, params string[] pValues)
{
int result = -1;
try
{
OracleCommand cmd = new OracleCommand(procName, this.Connection)
{
CommandType = CommandType.StoredProcedure
};
for (int i = 0; i < pValues.Length; ++i)
{
string par = string.Format("PARAM{0}", i + 1);
cmd.Parameters.Add(par, OracleType.VarChar).Value = pValues[i];
}
cmd.Parameters.Add("execResult", OracleType.Int32);
cmd.Parameters["execResult"].Direction = ParameterDirection.Output;
LastExecutedCommand = cmd;
cmd.ExecuteNonQuery();
result = int.Parse(cmd.Parameters["execResult"].Value.ToString());
}
catch (Exception ex)
{
//연결 해제 여부 확인 후 해제 시 재연결 후 다시 시도...
if (RetryCnt < 1 && CheckDBConnected() == false)
{
RetryCnt++;
GetConnection();
Exception ex02 = new Exception("Reconnect to database");
if (this.ExceptionEvent != null)
this.ExceptionEvent(string.Empty, ex02);
result = ExecuteProcedureAdt(procName, pValues);
return result;
}
//사용자 Cancel
if (ex.Message.Contains("ORA-01013"))
{
return result;
}
System.Reflection.MemberInfo info = System.Reflection.MethodInfo.GetCurrentMethod();
string id = string.Format("{0}.{1}\n[{2}]", info.ReflectedType.Name, info.Name, procName);
if (this.ExceptionEvent != null)
this.ExceptionEvent(id, ex);
this.LastExceptionString = ex.Message;
}
return result;
}
private bool CheckDBConnected()
{
string query = "SELECT 1 FROM DUAL";
OracleDataReader result = null;
try
{
OracleCommand cmd = new OracleCommand
{
Connection = this.Connection,
CommandText = query
};
result = cmd.ExecuteReader();
}
catch { }
if (result != null && result.HasRows)
return true;
return false;
}
#endregion private..................................................................
}
}
Singleton.cs
- 싱글톤 클래스 입니다.
using System;
namespace Oracel_Connect_Program.Class
{
public class Singleton<T> where T : class, new()
{
private static object _syncobj = new object();
private static volatile T _instance = null;
public static T Instance
{
get
{
if (_instance == null)
{
lock (_syncobj)
{
if (_instance == null)
{
_instance = new T();
}
}
}
return _instance;
}
}
public delegate void ExceptionEventHandler(string LocationID, Exception ex);
}
}
XmlManager.cs
- Config.xml 파일을 읽기 위한 XmlManager.cs 입니다.
- 오라클 DB 정보를 Config.xml에 입력하여 해당 내용을 읽어서 파싱후, 가져다 쓰도록 하였습니다.
using System;
using System.Xml.Linq;
namespace Oracel_Connect_Program.Class
{
public class XmlManager : Singleton<XmlManager>
{
#region " Properties & Variables "
private static string configFile = @"Config.xml";
public static string ConfigFile { get { return configFile; } set { configFile = value; } }
#endregion " Properties & Variables End"
#region " Public methode "
public static string GetValue(params string[] args)
{
string result = string.Empty;
try
{
XDocument xDoc = XDocument.Load(configFile);
result = GetNodeValue(xDoc.FirstNode as XElement, 0, args);
}
catch (Exception ex)
{
result = ex.Message;
result = string.Empty;
}
return result;
}
public static bool SetValue(params string[] args)
{
bool result = false;
try
{
XDocument xDoc = XDocument.Load(configFile);
result = SetNodeValue(xDoc.FirstNode as XElement, 0, args);
xDoc.Save(configFile);
}
catch
{
result = false;
}
return result;
}
#endregion " Public methode End"
#region " Private methode "
private static string GetNodeValue(XElement node, int idx, params string[] args)
{
string result = string.Empty;
if (args.Length > idx + 1)
result = GetNodeValue(node.Element(args[idx]), ++idx, args);
else
result = node.Element(args[idx]).Value.ToString();
return result;
}
private static bool SetNodeValue(XElement node, int idx, params string[] args)
{
if (args.Length > idx + 1)
{
SetNodeValue(node.Element(args[idx]), ++idx, args);
}
else
{
node.SetValue(args[idx]);
}
return true;
}
#endregion " Private methode End"
}
}
Config.xml
- 오라클 접속 정보 내용입니다.
<Configuration>
<DATABASE>
<D_ADDR01>127.0.0.1</D_ADDR01>
<D_ADDR02>127.0.0.1</D_ADDR02>
<PORT>1525</PORT>
<USER>system</USER>
<PWD>oracle</PWD>
<SERVICE_NAME>xe</SERVICE_NAME>
</DATABASE>
</Configuration>
Model
Sutdent.cs
- Model 클래스 입니다.
- 오라클 DB에 테스트로 Student 테이블을 하나 만들었고, 해당 내용을 WPF 바인딩 하기 위한 모델 클래스 입니다.
namespace WPFDBTest.Model
{
public class Student
{
public string NAME
{
get;
set;
}
public string AGE
{
get;
set;
}
public string Class
{
get;
set;
}
}
}
ViewModel
DelegateCommand.cs
- WPF Command 바인딩을 하기 위한 Command 클래스입니다.
using System;
using System.Windows.Input;
namespace WPFDBTest.ViewModel
{
public class DelegateCommand : ICommand
{
private readonly Func<bool> canExecute;
private readonly Action execute;
public DelegateCommand(Action exectue) : this(exectue, null)
{
}
public DelegateCommand(Action execute, Func<bool> canExecute)
{
this.execute = execute;
this.canExecute = canExecute;
}
/// <summary>
/// can executes event handler
/// </summary>
public event EventHandler CanExecuteChanged;
/// <summary>
/// implement of icommand can execute method
/// </summary>
/// <param name="o">parameter by default of icomand interface</param>
/// <returns>can execute or not</returns>
public bool CanExecute(object o)
{
if (this.canExecute == null)
{
return true;
}
return this.canExecute();
}
/// <summary>
/// implement of icommand interface execute method
/// </summary>
/// <param name="o">parameter by default of icomand interface</param>
public void Execute(object o)
{
this.execute();
}
/// <summary>
/// raise ca excute changed when property changed
/// </summary>
public void RaiseCanExecuteChanged()
{
if (this.CanExecuteChanged != null)
{
this.CanExecuteChanged(this, EventArgs.Empty);
}
}
}
}
MainViewModel.cs
- 실제 DB연동 관련 비지니스 로직을 수행하는 ViewModel 클래스입니다.
using Oracel_Connect_Program.Class;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Windows;
using System.Windows.Input;
using WPFDBTest.Model;
namespace WPFDBTest.ViewModel
{
public class MainViewModel : INotifyPropertyChanged
{
readonly MainWindow ownerWindow = null; // MainWindow 객체 선언
Student _stu = new Student();
public MainViewModel(MainWindow win)
{
ownerWindow = win;
}
public string NAME
{
get
{
return _stu.NAME;
}
set
{
_stu.NAME = value;
OnPropertyChanged("NAME");
}
}
public string AGE
{
get
{
return _stu.AGE;
}
set
{
_stu.AGE = value;
OnPropertyChanged("AGE");
}
}
ObservableCollection<Student> _sampleDatas = null;
public ObservableCollection<Student> SampleDatas
{
get
{
if (_sampleDatas == null)
{
_sampleDatas = new ObservableCollection<Student>();
}
return _sampleDatas;
}
set
{
_sampleDatas = value;
}
}
//PropertyChaneged 이벤트 선언 및 이벤트 핸들러
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
/// <summary>
/// Connect Command 선언
/// </summary>
private ICommand connectCommand;
public ICommand ConnectCommand
{
get
{
return (this.connectCommand) ?? (this.connectCommand = new DelegateCommand(Connect));
}
}
private ICommand selectCommand;
public ICommand SelectCommand
{
get
{
return (this.selectCommand) ?? (this.selectCommand = new DelegateCommand(DataSearch));
}
}
private ICommand _insertCommand;
public ICommand InsertCommand
{
get
{
return (this._insertCommand) ?? (this._insertCommand = new DelegateCommand(DataInsert));
}
}
private ICommand loadedCommand;
public ICommand LoadedCommand
{
get
{
return (this.loadedCommand) ?? (this.loadedCommand = new DelegateCommand(LoadEvent));
}
}
private void LoadEvent()
{
//Connect to DB
if (OracleDBManager.Instance.GetConnection() == false)
{
string msg = $"Failed to Connect to Database";
MessageBox.Show(msg, "Error");
return;
}
else
{
string msg = $"Success to Connect to Database";
MessageBox.Show(msg, "Inform");
}
}
private void DataSearch()
{
DataSet ds = new DataSet();
string query = @"
SELECT NAME, AGE
FROM STUDENT
";
OracleDBManager.Instance.ExecuteDsQuery(ds, query);
for(int idx = 0; idx < ds.Tables[0].Rows.Count; idx++)
{
Student obj = new Student
{
NAME = ds.Tables[0].Rows[idx]["NAME"].ToString(),
AGE = ds.Tables[0].Rows[idx]["AGE"].ToString()
};
SampleDatas.Add(obj);
}
}
private void DataInsert()
{
var studentList = SampleDatas;
foreach (var stu in studentList)
{
string query = @"
MERGE
INTO STUDENT
USING dual
ON (NAME = '#NAME' AND AGE = '#AGE')
WHEN MATCHED THEN
UPDATE
SET NAME = '#NAME',
AGE = '#AGE'
WHEN NOT MATCHED THEN
INSERT (NAME, AGE)
VALUES ('#NAME', '#AGE');
";
query = query.Replace("#NAME", stu.NAME);
query = query.Replace("#AGE", stu.AGE);
OracleDBManager.Instance.ExecuteNonQuery(query);
}
}
/// <summary>
/// DB Connect
/// </summary>
private void Connect()
{
//Connect to DB
if (OracleDBManager.Instance.GetConnection() == false)
{
string msg = $"Failed to Connect to Database";
MessageBox.Show(msg, "Error");
return;
}
else
{
string msg = $"Success to Connect to Database";
MessageBox.Show(msg, "Inform");
}
}
}
}
UI
MainWindow.xaml
<Window x:Class="WPFDBTest.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:WPFDBTest"
xmlns:ViewModel="clr-namespace:WPFDBTest.ViewModel"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="70"/>
</Grid.RowDefinitions>
<DataGrid x:Name="uiGrid_Main" Grid.ColumnSpan="3" Margin="5"
AutoGenerateColumns="False"
ItemsSource="{Binding Path= SampleDatas, Mode=TwoWay,
NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True}">
<DataGrid.Columns>
<DataGridTextColumn x:Name="grid_Name" MinWidth="10" Width="Auto" Header="NAME" Binding="{Binding NAME}"/>
<DataGridTextColumn x:Name="grid_AGE" MinWidth ="100" Width="Auto" Header="AGE" Binding="{Binding AGE}"/>
<!--<DataGridTextColumn MinWidth="100" Width="Auto" Header="GRADE" Binding="{Binding GRADE}"/>
<DataGridTextColumn MinWidth="200" Width="*" Header="SCORE" Binding="{Binding SCORE}"/>-->
</DataGrid.Columns>
</DataGrid>
<Button Grid.Row="1" Grid.Column="0" Margin="5"
Content="Connect"
Command="{Binding ConnectCommand}"/>
<Button Grid.Row="1" Grid.Column="1" Margin="5"
Content="조회"
Command="{Binding SelectCommand}"/>
<Button Grid.Row="1" Grid.Column="2" Margin="5"
Content="Insert"
Command="{Binding InsertCommand}"/>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
using WPFDBTest.ViewModel;
namespace WPFDBTest
{
/// <summary>
/// MainWindow.xaml에 대한 상호 작용 논리
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel(this);
}
}
}
실행 결과
- 위의 내용을 실제로 실행 시키면 아래와 같이 WPF 실행이 되면서, Oracle 에 Student 테이블에 저장된 내용을 제대로 가져와 사용자에게 보여지는 것을 확인할 수 있습니다.
WPF 화면
실제 Oracle Student 테이블
728x90
'C# > WPF' 카테고리의 다른 글
[WPF] WPF FolderBrowserDialog 네트워크 드라이브 보여주기 (0) | 2021.10.09 |
---|---|
[WPF] WPF 관리자 권한으로 실행하기 (0) | 2021.09.28 |
[WPF] GridSplitter 컨트롤 Position 값 가져오기 (0) | 2021.05.20 |
20장. WPF 계산기, MVVM, Command, 데이터바인딩이용 (2) | 2021.05.17 |
19장. WPF MVVM, ListBox의 컬렉션 정렬, 필터링, 탐색 실습(ListCollectionView) (0) | 2021.05.17 |
이 글을 공유하기