별Link : How to Integrate Excel in a Windows Form Application using the WebBrowser

 

 

Sample Image - Embedding_Excel.jpg

Introduction

With Automation, you are able to drive any Office application from your .NET application. This is really powerful. It may happen that one time, you would like to integrate such an application (Excel, for example) in your own application, and handle it like a control. A first approach has already been published on The Code Project (see the Background section in this article). The other method I will describe here uses the Microsoft WebBrowser control as a host for the document.

Background

You can study Anup Shinde's article. His method works fine. Instead of the WebBrowser control, it is based on Windows Win32 API.

If you are interested in the original publication of the WebBrowser method, you can see the Microsoft KB.

Starting from scratch

Create a new form MyForm, and add a new WebBrowser control, named webBrowser1. Add the m_ExcelFileNamefield :

Collapse | Copy Code

// Contains the path to the workbook file
private string m_ExcelFileName="test.xls"; // Replace here with an existing file

Then create a function OpenFile like this :

Collapse | Copy Code

public void OpenFile(string filename) 
{
    // Check the file exists
    if(!System.IO.File.Exists(filename)) throw new Exception();
        m_ExcelFileName=filename;
    // Load the workbook in the WebBrowser control
    this.webBrowser1.Navigate(filename,false);
}

You can try and run your application, giving the filename an existing excel file path. You'll see that it works perfectly. Really easy, don't you think?

In fact, you will quickly get into trouble if you try to run the application a second time with the same Excel file. An error message tells you that your file is already in use. This may be strange because you think that you closed your application, and you did. So where is the problem?

Let's see what happened in the background. While the WebBrowser was navigating, it opened an invisible Excel application, and loaded the workbook inside it. And when you closed your application, the WebBrowser didn't close either its Excel application or the workbook. So we must do it, and this is the most difficult part of our job.

Solving the problem step by step

Before further reading, you have to load the following Office COM library references for Office Automation :

  • Microsoft Excel 11.0 Object Library
  • Microsoft Office 11.0 Object Library

and use them in your file :

Collapse | Copy Code

using Microsoft.Office.Core;
using Microsoft.Office.Interop.Excel;

You'll need these assemblies too :

Collapse | Copy Code

using System.Runtime.InteropServices;
using System.Reflection;
using System.Runtime.InteropServices.ComTypes;

Declare these two Excel fields :

Collapse | Copy Code

// Contains a reference to the hosting application
private Microsoft.Office.Interop.Excel.Application m_XlApplication=null;
// Contains a reference to the active workbook
private Workbook m_Workbook=null;

Before trying to close the workbook, we need a handle on it. For convenience, the best moment to do this is just after the document has been loaded in the WebBrowser. So we have to generate a webBrowser1_Navigated event handler and its matching function, like this :

Collapse | Copy Code

private void webBrowser1_Navigated(object sender,WebBrowserNavigatedEventArgs e) 
{
    // Creation of the workbook object
    if((m_Workbook=RetrieveWorkbook(m_ExcelFileName))==null)return;
    // Create the Excel.Application
    m_XlApplication=(Microsoft.Office.Interop.Excel.Application)m_Workbook.Application;
}

Then we define the RetrieveWorkbook function. It is based on two imported Win32 API functions, that retrieve all the programs that are running on our computer. Our job is to search among them the one that is working with the workbook that names xlfile. The code is like this :

Collapse | Copy Code

[DllImport("ole32.dll")] static extern int GetRunningObjectTable
                (uint reserved,out IRunningObjectTable pprot);
[DllImport("ole32.dll")] static extern int CreateBindCtx(uint reserved,out IBindCtx pctx);

public Workbook RetrieveWorkbook(string xlfile) 
{
        IRunningObjectTable prot=null;
        IEnumMoniker pmonkenum=null;
        try 
        {
            IntPtr pfetched=IntPtr.Zero;
            // Query the running object table (ROT)
            if(GetRunningObjectTable(0,out prot)!=0||prot==null) return null;
            prot.EnumRunning(out pmonkenum); pmonkenum.Reset();
            IMoniker[] monikers=new IMoniker[1];
            while(pmonkenum.Next(1,monikers,pfetched)==0) 
            {
                IBindCtx pctx; string filepathname;
                CreateBindCtx(0,out pctx);
                 // Get the name of the file
                 monikers[0].GetDisplayName(pctx,null,out filepathname);
                 // Clean up
                 Marshal.ReleaseComObject(pctx);
                 // Search for the workbook
                 if(filepathname.IndexOf(xlfile)!=-1) 
                 {
                     object roval;
                     // Get a handle on the workbook
                     prot.GetObject(monikers[0],out roval);
                     return roval as Workbook;
                 }
              }
         } 
         catch 
         {
             return null;
         } 
         finally 
         {
             // Clean up
             if(prot!=null) Marshal.ReleaseComObject(prot);
             if(pmonkenum!=null) Marshal.ReleaseComObject(pmonkenum);
         }
         return null;
}

Now we can write the code involved to close the background Excel application, while overriding the OnClose() event :

Collapse | Copy Code

protected override void OnClosed(object sender, EventArgs e) 
{
    try 
    {
        // Quit Excel and clean up.
        if(m_Workbook!=null) 
        {
            m_Workbook.Close(true,Missing.Value,Missing.Value);
            System.Runtime.InteropServices.Marshal.ReleaseComObject
                                    (m_Workbook);
            m_Workbook=null;
        }
        if(m_XlApplication!=null) 
        {
            m_XlApplication.Quit();
            System.Runtime.InteropServices.Marshal.ReleaseComObject
                                (m_XlApplication);
            m_XlApplication=null;
            System.GC.Collect();
        }
    } 
    catch 
    {
        MessageBox.Show("Failed to close the application");
    }
}

Using the code

You can use the code as written upper. Otherwise, it may be interesting to embed all the stuff in a .NET control. You'll be able to manage CommandBars, Menus, etc. inside the control. You will find some code in the downloadable package section.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Temporary Directory

string strTempPath = System.IO.Path.GetTempPath();

 

Temporary FileName

string strTempFileName = System.IO.Path.GetTempFileName();

 

해당 실행 파일에 본 첨부파일을 풀어 주면 클라이언트 없이도 배포 가능합니다.

별도로  TNS_ADMIN 경로를 환경설정에서 해줘도 되나, 10g Client 버전부터는 TNS를 이용안하고 설정 가능합니다.

실행파일과 같은 경로에 배포하시면 쉽게 사용 가능합니다.

DataGridView를 이용하였습니다.

OracleConnection oraConn = new OracleConnection();
oraConn.ConnectionString = string.Format("USER ID={0};PASSWORD={1};DATA SOURCE={2};PERSIST SECURITY INFO=false", "{오라클UserID}", "{오라클UserPassword}", "{Oracle Host Name(Domain) or Oracle Host IP}:{Oracle Service Port}/{Oracle Service Name(SID)");
//기본포트 1521를 쓸경우 ":{Oracle Service Port}" 생략 가능
oraConn.Open();
string SQL = "SELECT * FROM TAB";
OracleCommand oraCmd = new OracleCommand(SQL, oraConn);
OracleDataAdapter oraAdapter = new OracleDataAdapter(oraCmd);
DataTable dt = new DataTable();
oraAdapter.Fill(dt);
dataGridView1.DataSource = dt;

ODP.NET 버전은 11.2.0.3.0 32bit 버전을 이용하였습니다.

Oracle.DataAccess.dll 파일 버전은 2.112.3.0 입니다.

 .NET 2.0 기준 DLL파일입니다.

.NET 4.0으로 프로젝트 세팅을 하셔도 참조시 Oracle.DataAccess.dll버전을 2.x대를 추가하면 .NET 4.0에서도 사용 가능합니다.

(최대 파일당 10M라서 분할하여 올립니다.)

================================================================================================================

ODP.NET(v2).zip

 

ODP.NET(v2).z01

 

ODP.NET(v2).z02

 

ODP.NET(v2).z03

 

ODP.NET(v2).z04

 

 

추가로 Oracle.DataAccess.dll 4.112.3.0 버전도 올립니다.

 

Oracle.DataAccess(v4).zip

사전에 Exchange Web Services Managed API(SDK)가 설치 되어져 있어야 하며

프로젝트 참조에 Microsoft.Exchange.WebServices (C:\Program Files\Microsoft\Exchange\Web Services\1.2\Microsoft.Exchange.WebServices.dll)를 추가하시기 바랍니다.

using Microsoft.Exchange.WebServices.Data;

본 샘플은 제목만 읽어온것이며, 경우에 따라서는 메소드를 잘 활용하시면 여러가지 정보를 읽어 올 수 있습니다.

소스만 보셔도 쉽게 이해가 가능 할 실 겁니다.

[????] 된 부분은 사용자에 맞게 변경하시기 바랍니다.

Exchange 서버 버전은 2010 SP1입니다.

8라인에서 서버에 맞는 버전을 선택하여 주세요. (2010 SP1은 ExchangeVersion.Exchange2010_SP1)

16라인에서 1개만 가져올경우 샘플이며, 크기는 알아서 조절 하세요(new ItemView(1))

string strExchangeDomain = @"[Domain]"; //ex) userpark.net
string strExchangeServerName = string.Format("exchange.{0}", strExchangeDomain); // exchange.userpark.net
string strExchangeUserID = @"[UserID]"; //사용자 ID
string strExchangeUserPs = @"[Password]"; //사용자 Password
string strExchangeMail = string.Format("{0}@{1}", strExchangeUserID, strExchangeDomain);  //ex)userpark@userpark.net
string strExchangeEwsUrl = string.Format("{0}://{1}/EWS/Exchange.asmx", "https", strExchangeServerName);

ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010_SP1);

//두개중 하나 NetworkCredential 또는 WebCredentials
//service.Credentials = new System.Net.NetworkCredential(strExchangeUserID, strExchangeUserPs, strExchangeDomain);
service.Credentials = new WebCredentials(strExchangeUserID, strExchangeUserPs, strExchangeDomain);

service.Url = new Uri(strExchangeEwsUrl);

Microsoft.Exchange.WebServices.Data.FindItemsResults<Microsoft.Exchange.WebServices.Data.Item> findResults = service.FindItems(Microsoft.Exchange.WebServices.Data.WellKnownFolderName.Inbox, new ItemView(1));
foreach (Microsoft.Exchange.WebServices.Data.Item item in findResults.Items)
{
	//if (item.IsNew == true)  //읽지 않은 경우만 가져올 경우
	Console.WriteLine(item.Subject);
}

 

2012/06/25 - [자료실] - Microsoft Exchange Web Service Managed API SDK 1.2.1

출처 : 자작(userpark)

DataTable간 Join하는 방법입니다.
소스 보시면 쉽게 이해가 가능 하 실 겁니다.
DataGridView를 통해서 최종 결과를 뿌려줍니다.
UI디자인에서 미리 만들어 놓으시기 바랍니다.
그럼 소스는…
DataTable dtUser = new DataTable();
DataTable dtDept = new DataTable();
DataTable dtUserInfo = null;
DataRow drTemp;
//사용자 정보 테이블 - DataTable Column을 추가(Range)
dtUser.Columns.AddRange(new DataColumn[] { 
		new DataColumn {ColumnName = "no", Caption = "Increment", DataType = typeof(int), Unique = true, AutoIncrement = true}
		, new DataColumn {ColumnName = "user_id", Caption = "User ID", DataType = typeof(string), Unique = true, AllowDBNull = false}
		, new DataColumn {ColumnName = "user_name", Caption = "User Name", DataType = typeof(string), AllowDBNull = false}
		, new DataColumn {ColumnName = "dept_no", Caption = "Dept. Code", DataType = typeof(int), DefaultValue = -1}
		, new DataColumn {ColumnName = "is_use", Caption = "IS Use?", DataType = typeof(bool), DefaultValue = true}
	});

/*
 * BEGIN DataRow를 이용한 사용자 정보 추가
 * */
drTemp = dtUser.NewRow();
//AutoIncrement가 0부터 시작하므로 처음값만 1로 강제 부여
drTemp["no"] = 1;
drTemp["user_id"] = "test1";
drTemp["user_name"] = "Tester 1";
drTemp["dept_no"] = 1;
drTemp["is_use"] = true;
dtUser.Rows.Add(drTemp);

drTemp = dtUser.NewRow();
drTemp["user_id"] = "test2";
drTemp["user_name"] = "Tester 2";
drTemp["dept_no"] = 100;
dtUser.Rows.Add(drTemp);

drTemp = dtUser.NewRow();
drTemp["user_id"] = "test3";
drTemp["user_name"] = "Tester 3";
drTemp["dept_no"] = 3;
drTemp["is_use"] = false;
dtUser.Rows.Add(drTemp);

drTemp = dtUser.NewRow();
drTemp["user_id"] = "test4";
drTemp["user_name"] = "Tester 4";
dtUser.Rows.Add(drTemp);
/*
 * END DataRow를 이용한 사용자 정보 추가
 * */

//부서 정보 테이블 - DataTable Column을 추가(Range)
dtDept.Columns.AddRange(new DataColumn[] {
		new DataColumn {ColumnName = "dept_no", Caption = "Dept. Code", DataType = typeof(int), Unique = true, AutoIncrement = true}
		, new DataColumn {ColumnName = "dept_name", Caption = "Dept. Name", DataType = typeof(string), DefaultValue = string.Empty, AllowDBNull = true}
	});

/*
 * BEGIN DataRow를 이용한 부서 정보 추가
 * */
drTemp = dtDept.NewRow();
//AutoIncrement가 0부터 시작하므로 처음값만 1로 강제 부여
drTemp["dept_no"] = 1;
drTemp["dept_name"] = "IT Team";
dtDept.Rows.Add(drTemp);

drTemp = dtDept.NewRow();
drTemp["dept_name"] = "Management Team";
dtDept.Rows.Add(drTemp);

drTemp = dtDept.NewRow();
drTemp["dept_name"] = "Project Management Team";
dtDept.Rows.Add(drTemp);
/*
 * END DataRow를 이용한 부서 정보 추가
 * */

/*
 * BEGIN JOIN 이후 DataView등에 사용할 최종 사용자 정보 DataTable 설정
 * */
//기존 사용자 정보 테이블 구조 및 스키마만 복사(데이터는 복사하지 않음)
dtUserInfo = dtUser.Clone();
//Join으로 추가할 부서 테이블에서 부서명에 해당되는 컬럼 추가
dtUserInfo.Columns.Add(new DataColumn { ColumnName = "dept_name", Caption = "Dept. Name", DataType = typeof(string), DefaultValue = string.Empty, AllowDBNull = true });
//Join 성공 여부 컬럼 추가
dtUserInfo.Columns.Add("is_join_dept", typeof(bool));
/*
 * END JOIN 이후 DataView등에 사용할 최종 사용자 정보 DataTable 설정 
 * */

/*
 * BEGIN 사용자 테이블과 부서 테이블간 JOIN후 최종 사용자 정보 DataTable에 반영
 * */
//var : Variable의 약자, C# 3.0부터 추가된 타입이 없는 변수, 초기에 대입되는 값에 의하여 변수의 형식이 결정 됨(int, string, double ... 등 활용 가능)
//      여기서는 IEnumerable타입으로 이용
var query =
			from u in dtUser.AsEnumerable()
			join d in dtDept.AsEnumerable()
				//on (int)o["area_index_no"] equals (int)(decimal)e["area_index_no"] into j
			on
				u["dept_no"] equals d["dept_no"]
			into j
			from jList in j.DefaultIfEmpty()
			select new
			{
				no = u["no"]
				,
				user_id = u["user_id"].ToString()
				,
				user_name = u["user_name"].ToString()
				,
				dept_no = u["dept_no"]
				,
				dept_name = (jList != null ? jList["dept_name"] : string.Empty)
				,
				is_use = Convert.ToBoolean(u["is_use"])
				,
				is_join_dept = (bool)(jList == null ? true : false)
			};

foreach (var v in query)
{
	drTemp = dtUserInfo.NewRow();
	drTemp.BeginEdit(); //생략가능
	drTemp["no"] = v.no;
	drTemp["user_id"] = v.user_id;
	drTemp["user_name"] = v.user_name;
	drTemp["dept_no"] = v.dept_no;
	drTemp["dept_name"] = v.dept_name;
	drTemp["is_use"] = v.is_use;
	drTemp["is_join_dept"] = v.is_join_dept;
	drTemp.EndEdit(); //생략가능
	dtUserInfo.Rows.Add(drTemp);
}
/*
 * END 사용자 테이블과 부서 테이블간 JOIN후 최종 사용자 정보 DataTable에 반영
 * */

//DataGridView에 반영
dataGridView1.DataSource = dtUser;
dataGridView2.DataSource = dtDept;
dataGridView3.DataSource = dtUserInfo;

출처 : 자작(userpark)

그냥 설명은 무시하고 예제 소스로 그냥 진행합니다.

DataSet이 아닌 일반 DataTable을 이용하는 방법도 동일합니다.

 

DataSet 생성

DataSet ds = new DataSet();
DataRow dr = null;
DataTable dt01 = new DataTable();
dt01.TableName = "Test01";
dt01.Columns.AddRange(new DataColumn[] {
        new DataColumn { ColumnName ="no", Caption = "일련번호", DataType=typeof(int), DefaultValue = 0}
        , new DataColumn { ColumnName ="col", Caption = "컬럼명", DataType=typeof(string), DefaultValue = string.Empty}
        , new DataColumn { ColumnName ="val", Caption = "값", DataType=typeof(string), DefaultValue = string.Empty}
        , new DataColumn { ColumnName ="etc", Caption = "비고", DataType=typeof(string), DefaultValue = string.Empty}
    });
dr = dt01.NewRow();
dr["no"] = 1;
dr["col"] = "COL1";
dr["val"] = "A";
dr["etc"] = "비고1";
dt01.Rows.Add(dr);
dr = dt01.NewRow();
dr["no"] = 2;
dr["col"] = "COL2";
dr["val"] = "B";
dt01.Rows.Add(dr);
dr = dt01.NewRow();
dr["no"] = 3;
dr["col"] = "COL3";
dr["val"] = "C";
dt01.Rows.Add(dr);
dr = dt01.NewRow();
dr["no"] = 4;
dr["col"] = "COL4";
dr["val"] = "D";
dt01.Rows.Add(dr);
dr = dt01.NewRow();
dr["no"] = 5;
dr["col"] = "COL5";
dr["val"] = "가";
dt01.Rows.Add(dr);
dr = dt01.NewRow();
dr["no"] = 6;
dr["col"] = "COL6";
dr["val"] = "나※";
dt01.Rows.Add(dr);
dr = dt01.NewRow();
dr["no"] = 7;
dr["col"] = "COL7";
dr["val"] = "~!@#$%^&*()_+`-={}[]:\";'<>?,./|\\";
dt01.Rows.Add(dr);
dr = dt01.NewRow();
dr["no"] = 8;
dr["col"] = "COL8";

dt01.Rows.Add(dr);
dr = dt01.NewRow();
dt01.Rows.Add(dr);
 
 
DataTable dt02 = new DataTable();
dt02.TableName = "Test02";
dt02.Columns.AddRange(new DataColumn[] {
        new DataColumn { ColumnName ="no", Caption = "일련번호", DataType=typeof(int), AutoIncrement = true}
        , new DataColumn { ColumnName ="etc", Caption = "비고", DataType=typeof(string), DefaultValue = string.Empty}
    });
dr = dt02.NewRow();
dr["etc"] = "비고1";
dt02.Rows.Add(dr);
dr = dt02.NewRow();
dr["etc"] = "비고2";
dt02.Rows.Add(dr);
dr = dt02.NewRow();
dr["etc"] = "비고3";
dt02.Rows.Add(dr);
dr = dt02.NewRow();
dr["etc"] = "비고4";
dt02.Rows.Add(dr);
dr = dt02.NewRow();
dr["etc"] = "비고5";
dt02.Rows.Add(dr);
dr = dt02.NewRow();
dr["etc"] = "비고6";
dt02.Rows.Add(dr);
 
ds.Tables.Add(dt01);
ds.Tables.Add(dt02);

XML 파일로 저장

ds.WriteXmlSchema(@"c:\TestForDS.xsd");
ds.WriteXml(@"c:\TestForDS.xml");

 

XML Stream을 이용하여 String에 담기

System.IO.MemoryStream mStreamXSD = new System.IO.MemoryStream();
System.IO.MemoryStream mStreamXML = new System.IO.MemoryStream();
ds.WriteXmlSchema(mStreamXSD);
mStreamXSD.Seek(0, System.IO.SeekOrigin.Begin);
ds.WriteXml(mStreamXML);
mStreamXML.Seek(0, System.IO.SeekOrigin.Begin);
System.IO.StreamReader srXSD = new System.IO.StreamReader(mStreamXSD);
System.IO.StreamReader srXML = new System.IO.StreamReader(mStreamXML);
string strXSD = srXSD.ReadToEnd();
string strXML = srXML.ReadToEnd();

 

이 방법을 이용하여 Oracle Clob 저장 및 활용

 

=================================================================

참고(출력 파일)

XSD

<?xml version="1.0" standalone="yes"?>
<xs:schema id="NewDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
  <xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
    <xs:complexType>
      <xs:choice minOccurs="0" maxOccurs="unbounded">
        <xs:element name="Test01">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="no" msdata:Caption="일련번호" type="xs:int" default="0" minOccurs="0" />
              <xs:element name="col" msdata:Caption="컬럼명" type="xs:string" default="" minOccurs="0" />
              <xs:element name="val" msdata:Caption="값" type="xs:string" default="" minOccurs="0" />
              <xs:element name="etc" msdata:Caption="비고" type="xs:string" default="" minOccurs="0" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element name="Test02">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="no" msdata:AutoIncrement="true" msdata:Caption="일련번호" type="xs:int" minOccurs="0" />
              <xs:element name="etc" msdata:Caption="비고" type="xs:string" default="" minOccurs="0" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:choice>
    </xs:complexType>
  </xs:element>
</xs:schema>

XML

<?xml version="1.0" standalone="yes"?>
<NewDataSet>
  <Test01>
    <no>1</no>
    <col>COL1</col>
    <val>A</val>
    <etc>비고1</etc>
  </Test01>
  <Test01>
    <no>2</no>
    <col>COL2</col>
    <val>B</val>
    <etc />
  </Test01>
  <Test01>
    <no>3</no>
    <col>COL3</col>
    <val>C</val>
    <etc />
  </Test01>
  <Test01>
    <no>4</no>
    <col>COL4</col>
    <val>D</val>
    <etc />
  </Test01>
  <Test01>
    <no>5</no>
    <col>COL5</col>
    <val>가</val>
    <etc />
  </Test01>
  <Test01>
    <no>6</no>
    <col>COL6</col>
    <val>나※</val>
    <etc />
  </Test01>
  <Test01>
    <no>7</no>
    <col>COL7</col>
    <val>~!@#$%^&amp;*()_+`-={}[]:";'&lt;&gt;?,./|\</val>
    <etc />
  </Test01>
  <Test01>
    <no>8</no>
    <col>COL8</col>
    <val />
    <etc />
  </Test01>
  <Test01>
    <no>0</no>
    <col />
    <val />
    <etc />
  </Test01>
  <Test02>
    <no>0</no>
    <etc>비고1</etc>
  </Test02>
  <Test02>
    <no>1</no>
    <etc>비고2</etc>
  </Test02>
  <Test02>
    <no>2</no>
    <etc>비고3</etc>
  </Test02>
  <Test02>
    <no>3</no>
    <etc>비고4</etc>
  </Test02>
  <Test02>
    <no>4</no>
    <etc>비고5</etc>
  </Test02>
  <Test02>
    <no>5</no>
    <etc>비고6</etc>
  </Test02>
</NewDataSet>

출처 : 자작(userpark)

출처 : http://blog.naver.com/lcg2004?Redirect=Log&logNo=60049785458

참고 : http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=17&MAEULNo=8&no=117409&ref=117385

Writing to Oracle CLOB fields
using System.Data.OracleClient

만약 당신이 .NET 코더고 오라클을 갖다가 데이터베이스 작업을 한다면, 이번이 적절하게 사용할 수 있는 기회가 될 것이다.^^ 읽으면서 커멘트도 달아주기 바란다!

우리 현재 프로젝트에서 우리는 ASP.NET 1.1 애플리케이션으로부터 상당한 량의 텍스트를 오라클 CLOB 필드로 쓸 필요가 있다. 우리는 이제껏 표준 MS System.Data.OracleClient 라이브러리를 (더 작은 량의 데이터를 쓰기위해) 사용해왔고, 그것은 우리에게 아주 유용했다. 나는 오라클 ODP.NET과 같은 더 최신버전의 우수한 팩이 있더라도 다른 라이브러리 팩을 소개하고 싶지 않았다.

예전의 자바 프로젝트에서 나는 CLOB를 쓰고 읽는 작업을 해왔고, 또한 나는 당신이 일반 VARCHAR과 같은 필드를 핸들해야 했다는 것을 안다.

따라서 나는 자리잡고 앉아서 .NET 동적Help과 가능하다면 가능한 모든 인터넷을 뒤지기 시작했고, 그 결과 흥미로운 것이 있었고, 나는 세가지 각각 다른 방법을 발견했다. 두가지는 정말로 단순하고 하나는 그리 단순하지 않다. 희안한 것은 내가 두 옵션은 제대로 작동할 것이라고 생각하지 않고-그러나 내가 테스트 했을 때 제대로 작동했다.
나는 3가지 방법을 여기서 공개할 것이다. 그리고 바라건데 .NET과 Oracle로 지가 머하는가를 아는 누군가는 나를 불꽃으로 격추시킬 수 있길 바란다.(청출어람하길 바란다..)

이것을 테스트 하기위해, 나는 오라클에다가 단순한 테이블을 생성하고, 그 테이블은 2개의 필드로 구성되어있다; ID(INT)와 TEXT(CLOB). 바로 그거다. 나는 노트패드로 생성된 단순 텍스트 파일을 읽을 것이며, 그것은 499KB다.

주: 이것은 테스트코드일 뿐, 당신이 실무에서 하고 싶은 것과는 별개이다. 만약 이코드를 사용하고 싶다면, 적절한 에러나 예외, 핸들링 등을 추가하라.

그래서, OracleDataAdapter를 이용하는 첫번째 방법은 안돌아 갈 것이다.(내가 클래스 라이브러리 문서들을 이해한 기준에서는..). 그러나, 맞긴맞다.

public void writeDataWithDA()

{

           FileInfo fi = new FileInfo("c:/temp/testfile.txt");

           StreamReader sr = new StreamReader(fi.FullName);

           String clob = sr.ReadToEnd();

           sr.Close();

 

           OracleDataAdapter da = new OracleDataAdapter(

               "SELECT ID, TEXT FROM CLOBTEST", ConnectionString);

           DataTable dt = new DataTable();

           // get the schema

           da.FillSchema(dt, SchemaType.Source);

 

           OracleCommandBuilder cb = new OracleCommandBuilder(da);

 

           int id = 2;

 

           // create a row containing the data

           DataRow row = dt.NewRow();

           row["ID"] = id;

           row["TEXT"] = clob;

           dt.Rows.Add(row);

 

           // update the table

           da.Update(dt);

}

두번째 방법은 OracleCommand 를 이용하는 것이며, 이것도 안돌아간다(내 생각엔). 근데 원래는 되는 방법이다.

public void writeDataWithCommand()

{

           FileInfo fi = new FileInfo("c:/temp/testfile.txt");

           StreamReader sr = new StreamReader(fi.FullName);

           String tempBuff = sr.ReadToEnd();

           sr.Close();

           

           using(OracleConnection conn = new OracleConnection(ConnectionString))

           {

                      conn.Open();

                      Console.WriteLine("Connected...") ;

                      String strSQL = 

                          "INSERT INTO CLOBTEST (ID,TEXT) VALUES (1,:TEXT_DATA) ";

 

                      OracleParameter parmData = new OracleParameter();

                      parmData.Direction = ParameterDirection.Input;

                      parmData.OracleType = OracleType.Clob;

                      parmData.ParameterName = "TEXT_DATA";

                      parmData.Value = tempBuff;

 

                      OracleCommand cm = new OracleCommand();

                      cm.Connection = conn;

                      cm.Parameters.Add(parmData);

                      cm.CommandText = strSQL;

                      cm.ExecuteNonQuery();

 

                      conn.Close();

           }

 

           Console.WriteLine("Done!") ;

}

이제 세번째 방법이다. 이것은 .NET 클래스 라이브러리 문서가 기술한 C/BLOB 핸들링 방법이며, 임시 LOB 객체를 오라클에 생성한 다음, insert작업을 하기전에, 그 객체를 쓰므로써 핸들링하는 방법이다. 이것은 역시나 잘돌아간다, 근데 이 방법은 트랜젝션과 다른 부분간에 약간 더 많은 혼란을 겪어야 한다.

public void writeWithTempBlob()

{

           FileInfo fi = new FileInfo("c:/temp/testfile.txt");

           StreamReader sr = new StreamReader(fi.FullName);

           String tempBuff = sr.ReadToEnd();

           sr.Close();

 

           using(OracleConnection conn = new OracleConnection(ConnectionString))

           {

                      conn.Open();

                      Console.WriteLine("Connected...") ;

                      OracleTransaction tx = conn.BeginTransaction();

 

                      OracleCommand tempcmd = conn.CreateCommand();

                      tempcmd.Transaction = tx;

                      tempcmd.CommandText = "declare xx clob; begin dbms_lob.createtemporary(xx, false, 0); :tempclob := xx; end;";

                      tempcmd.Parameters.Add(new OracleParameter("tempclob",

                                 OracleType.Clob)).Direction = ParameterDirection.Output;

                      tempcmd.ExecuteNonQuery();

 

                      //get the temp lob object

                      OracleLob tempLob = (OracleLob)tempcmd.Parameters[0].Value;

 

                      //transform into byte array

                      System.Text.Encoding enc = Encoding.Unicode;

                     //MUST be unicode encoded!

 

                      Byte[] b = enc.GetBytes(tempBuff);

 

                      tempLob.BeginBatch(OracleLobOpenMode.ReadWrite);

                      tempLob.Write(b,0,b.Length);

                      tempLob.EndBatch();

 

                      OracleCommand cmd = conn.CreateCommand();

                      cmd.Transaction = tx;

                      cmd.CommandText = 

                          "INSERT INTO CLOBTEST (ID, TEXT) VALUES (:ID, :TEXT)";

                      cmd.Parameters.Add("ID", 3);

                      cmd.Parameters.Add("TEXT", OracleType.Clob).Value = tempLob;

                      //insert the temp lob

 

                      cmd.ExecuteNonQuery();

 

                      tx.Commit(); 

           }

           Console.WriteLine("Done!") ;

}

임시 CLOB에 write할려면 텍스트는 유니코드여야 한다. 안그러면 당신은 완전 많은 문자들이 쓰여진 것을 경험하게 될 것이니 주의하라.^^

다른 참고자료

VS2008에서 User32.DLL 파일을 Import하여 함수를 호출하는 방법입니다.
이런 방식을 동적DLL호출(?) 이라고 하는지 모르겠습니다.
델파이에서는 동적호출이라고 명하는 걸로 기억하고 있습니다.
 
코드 설명은 간략히 기술하겠습니다.
길어봐야 별거 없다고 판단되어집니다. ^^ 
주의할 사항은 Import 해 올 DLL 파일을 반드시 아래 경로에 존재 하여야 한다.
  • [%SystemRoot%] (Windows 디렉토리)
  • [%SystemRoot%]\system32\ 경로 (Microsoft Windows XP 일 경우)
  • 실행파일(현재 작업) 디렉토리에 같이 위치
  • 환경변수 PATH 상의 경로(비추천)

  1: using System;
  2: using System.Runtime.InteropServices;
  3: using System.Collections.Generic;
  4: using System.ComponentModel;
  5: using System.Data;
  6: using System.Drawing;
  7: using System.Linq;
  8: using System.Text;
  9: using System.Windows.Forms;
 10: 
 11: namespace DLLImport
 12: {
 13:     public class UserImportDLL
 14:     {
 15:         [DllImport("User32.dll")]
 16:         public static extern int MessageBox(int hParent, string Message, string Caption, int Type);
 17:     }
 18: 
 19:     public partial class Form1 : Form
 20:     {
 21:         
 22: 
 23:         public Form1()
 24:         {
 25:             InitializeComponent();
 26:         }
 27: 
 28:         private void button1_Click(object sender, EventArgs e)
 29:         {
 30:             UserImportDLL.MessageBox(0, "모든 프로그램의 기본은 항상 '헬로우 월드!'", "Message Box Title", 0);    
 31:         }
 32:     }
 33: }

Line 2: using System.Runtime.InteropServices; // DLLImport를 정의 하고 있는 네임스페이스

Line 15, 16에서 Import 및 사용할 함수 정의

Line 30에서 호출하여 사용


※ 호출 시 Class안에 쌓아서 호출하였으나 그냥 호출도 가능 => 밑줄 친 소스만 잘 활용하면 가능함

출처 : 자작(http://userpark.net)

+ Recent posts