5.2.1 DBM ForAll 예광탄

DBM ForAll 라이브러리를 제작하기 전에 Windows Forms 응용 프로그램으로 예광탄 프로그램을 작성합시다. 프로젝트 명은 DBM ForAll Tracer라고 정할게요.

DBM ForAll Tracer는 앞에서 작성한 저장 프로시저를 이용하는 DBM ForAll 라이브러리를 만들기 위한 것으로 하나의 폼에서 사용하는 것이 아니라 하나씩 사용하면서 추가하기로 합시다.

예광탄에서는 수집할 웹 사이트 주소를 등록하는 부분을 테스트해야 합니다. 그리고 수집 대상 사이트 중에 맨 앞의 항목을 얻어올 수 있어야 합니다. 수집 대상 사이트 목록을 얻어올 수도 있어야 합니다.

수집한 웹 페이지를 등록하는 부분도 테스트할 수 있어야 합니다. 역 파일 테이블에 있는 형태소 항목을 얻어올 수 있어야 합니다. 웹 페이지에 있는 전체 형태소 항목 개수를 등록할 수 있어야 합니다. 역 파일의 항목을 등록할 수 있어야 합니다.

위와 같은 테스트를 할 수 있게 예광탄의 자식 컨트롤을 배치합시다.

여기에서는 메인 폼에서 웹 사이트 등록하는 부분과 형태소 및 역 파일에 관한 정보를 등록 및 확인하는 부분을 테스트 할 수 있게 배치할게요. 메인 폼은 두 개의 탭 페이지를 갖는 탭 컨트롤을 두어 하나의 페이지에는 웹 사이트 등록에 관한 부분을 두 번째 페이지에서는 형태소 및 역 파일에 관한 정보를 등록 및 확인할 수 있게 합시다.

첫 번째 탭 페이지에는 Seed 사이트 주소를 추가하기 위한 컨트롤과 수집 대상 사이트 정보를 확인하기 위한 컨트롤 및 수집한 웹 페이지 등록 버튼을 제공합니다. 수집한 웹 페이지 등록 버튼을 누르면 새로운 폼을 통해 정보를 입력하게 합시다.

DBM ForAll 메인 폼의 컨트롤 배치
[그림 5.4] DBM ForAll 메인 폼의 컨트롤 배치
DBM ForAll 메인 폼의 컨트롤 배치
[그림 5.4] DBM ForAll 메인 폼의 컨트롤 배치

 두번째 탭 페이지에는 형태소 항목을 조회하기 위한 컨트롤과 웹 페이지의 전체 형태소 개수를 등록하고 역 파일의 항목을 추가하는 컨트롤이 필요합니다.

DBM ForAll 메인 폼의 두번째 탭 페이지 컨트롤 배치

[그림 5.5] DBM ForAll 메인 폼의 두번째 탭 페이지 컨트롤 배치
DBM ForAll 메인 폼 두번째 탭 페이지의 자식 컨트롤
[표 5.2] DBM ForAll 메인 폼 두번째 탭 페이지의 자식 컨트롤

DBM ForAll의 첫 번째 탭 페이지의 수집한 웹 페이지 등록 버튼을 누르면 정보를 입력하는 폼을 띄워서 등록하게 합시다. 그리고 수집한 웹 페이지 정보를 입력하기 위한 폼 이름을 AddPostedUrlForm이라고 할게요.

AddPostedUrlForm에는 웹 페이지 주소, 요청한 원래 주소, 페이지 제목, 상대적 깊이, 수집한 시각과 페이지 내용을 입력할 수 있는 컨트롤과 등록 버튼이 필요합니다.

AddPostedUrlForm의 컨트롤 배치

[그림 5.6] AddPostedUrlForm의 컨트롤 배치
AddPostedUrlForm의 자식 컨트롤
[표 5.3] AddPostedUrlForm의 자식 컨트롤

 먼저 DBM ForAll 라이브러리에 만들 클래스를 생각하며 하나씩 추가해 나갑시다. 먼저 라이브러리를 사용하는 곳에서 접근할 퍼샤드 역할을 하는 정적 클래스를 추가하세요. 클래스 이름은 EHDbmForAll로 정할게요.

public static class EHDbmForAll
{
}

먼저 Seed 사이트를 추가하는 부분을 구현합시다. Seed 사이트 추가 버튼을 누르면 입력한 Seed 사이트를 추가해야 합니다. 먼저 btn_addseed 버튼의 클릭 이벤트 핸들러를 추가합니다.

그리고 이벤트 핸들러에서는 tbox_seed의 입력 내용을 얻어옵니다. 얻어온 입력 정보를 EHDbmForAll 정적 메서드 AddSeedSite를 호출하며 전달합니다. 물론 EHDbmForAll 클래스에 정적 메서드 AddSeedSite를 추가해야겠죠.

private void btn_addseed_Click(object sender, EventArgs e)
{
    EHDbmForAll.AddSeedSite(tbox_seed.Text);
}

EHDbmForAll의 AddSeedSite 메서드를 작성합시다. EHDbmForAll의 여러 메서드는 EH데이터 베이스를 사용해야 하므로 연결 문자열을 멤버 필드로 설정합시다.

static string constr =
            @"Data Source=[DBMS 인스턴스 명];Initial Catalog=[DB 명];Persist Security Info=True;User ID=[계정];Password=[비밀번호];Pooling=False";

Seed 사이트는 수집 대상 사이트입니다. 수집 대상 사이트는 Seed 사이트 외에도 수집한 웹 페이지 내의 링크도 추가해야 합니다. 수집 대상 사이트를 추가할 때는 Seed 사이트에서 상대적 깊이도 전달해야 합니다. 따라서 AddSeedSite에서는 수집 대상 사이트를 추가하는 AddCandidate 메서드를 호출하기로 할게요. 물론 AddCandidate 메서드를 추가해야겠죠.

public static void AddSeedSite(string url)
{
    AddCandidate(url, 0);
}

수집 대상 사이트를 추가하는 AddCandidate 메서드를 작성합시다.

public static void AddCandidate(string url, int depth)

먼저 DBMS에 연결하는 SqlConnect 개체를 생성합니다. 그리고 AddCandidate 저장 프로시저를 명령으로 하는 SqlCommand 개체를 생성합니다. 앞으로 이와 같은 형태의 작업이 계속 필요하므로 특정 명령을 수행하기 위한 SqlCommand 개체를 만드는 메서드를 정의하여 사용합시다. 메서드 이름은 MakeSPCommand로 정할게요.

MakeSPCommand에서는 연결 문자열을 인자로 SqlConnection 개체를 생성합니다. 그리고 입력 인자로 받은 명령 문자열과 생성한 SqlConnection 개체로 SqlCommand 개체를 생성합니다. 그리고 SqlCommand 개체의 CommandType을 입력 인자로 받은 값으로 설정한 후 SqlCommand 개체를 반환합니다.

static SqlCommand MakeSPCommand(string cmdtext, CommandType cmdtype)
{
    SqlConnection scon = new SqlConnection(constr);
    SqlCommand scom = new SqlCommand(cmdtext, scon);
    scom.CommandType = cmdtype;
    return scom;
}

여러분께서 ADO.NET 기술을 사용해 보거나 학습한 경험이 있으면 어렵지 않게 이해할 것입니다. 그렇지 않다면 부분적으로 ADO.NET 기술 학습이 필요합니다.

AddCandidate 메서드에서는 먼저 저장 프로시저 AddCandidate를 명령 문자열로 하는 SqlCommand 개체를 생성합니다.

SqlCommand scom = MakeSPCommand("AddCandidate",
                                                      CommandType.StoredProcedure);

그리고 입력 인자로 전달받은 인자로 SqlParameter 개체를 생성합니다. 생성한 SqlParameter 개체는 SqlCommand 개체의 Parameters 컬렉션에 추가합니다.

SqlParameter sp_url = new SqlParameter("@Url", url);
SqlParameter sp_depth = new SqlParameter("@Depth", depth);
scom.Parameters.Add(sp_url);
scom.Parameters.Add(sp_depth);

그리고 DBMS에 연결하고 명령을 수행한 후에 DBMS 연결을 끊습니다.

scom.Connection.Open();
scom.ExecuteNonQuery();
scom.Connection.Close();

기본적으로 ADO.NET 기술로 DBMS에 명령을 내리는 절차는 이와 같습니다. 다양한 프로그램 경험을 갖고 계시면 이 책을 학습하면서 필요할 때 ADO.NET 기술을 살펴봐도 어렵지 않게 사용할 수 있을거예요. 하지만 경험이 별로 없고 아직 ADO.NET 기술을 사용하거나 학습 경험이 없으면 별도의 레퍼런스를 먼저 보는 것이 나을 수 있습니다. 참고로 다른 개발 환경에서 제공하는 비슷한 기술(ODBC, JDBC)을 사용해 본 경험이 있다면 어렵지 않게 이해할 수도 있습니다.

프로젝트를 빌드한 후에 테스트를 해 보세요. Seed 사이트 입력 창에 주소를 입력한 후에 추가 버튼을 누르세요. 그리고 DBMS의 CandidateTable의 컨텍스트 메뉴에서 테이블 데이터 표시를 선택하여 잘 추가되었는지 확인하세요.

Seed 사이트 추가 테스트 결과 확인
[그림 5.7] Seed 사이트 추가 테스트 결과 확인

이번에는 수집 대상 사이트 항목에서 맨 앞에 저장한 항목을 얻어오는 테스트를 구현합시다. 가져오기 버튼인 btn_get_fcandi의 클릭 이벤트 핸들러를 추가합니다.

private void btn_get_fcandi_Click(object sender, EventArgs e)

그리고 정적 클래스 EHDBMForAll에는 Candidate 형식 개체를 반환하는 GetFrontCandidate메서드를 추가합니다. 이를 위해 WSECore 라이브러리를 참조 추가해야 합니다.

Candidate candidate = EHDbmForAll.GetFrontCandidate();

클릭 이벤트 핸들러에서는 EHDBMForAll 클래스의 정적 메서드 GetFrontCandidate를 호출하여 Candidate 형식 개체를 얻어옵니다. 그리고 Url 정보와 Depth 정보를 Label 컨트롤의 Text 속성에 설정합니다. 수집 대상 사이트가 없을 때는 없다는 정보로 설정합니다.

if (candidate == null)
{
    lb_fcandi_url.Text = "없음";
    lb_depth.Text = "N/A";
}
else
{
    lb_fcandi_url.Text = candidate.Url;
    lb_depth.Text = candidate.Depth.ToString();
}

이번에는 EHDBMForAll 클래스의 정적 메서드 GetFrontCandidate를 구현합시다. 먼저 저장 프로시저 GetFrontCandidate를 호출하기 위한 SqlCommand 개체를 만듭니다.

SqlCommand scom = MakeSPCommand("GetFrontCandidate",
                                                      CommandType.StoredProcedure);

그리고 파라미터 개체를 생성합니다. GetFrontCandidate에는 수집 대상 페이지 주소와 상대적 깊이, 수집 대상 정보를 얻어왔는지를 확인하기 위한 OUTPUT 유형의 인자가 있습니다. OUTPUT 유형의 인자와 매핑하는 파라미터 개체는 Direction 속성을 Output 값으로 설정해야 합니다.

SqlParameter sp_url = new SqlParameter("@Url", SqlDbType.VarChar, 200);
sp_url.Direction = ParameterDirection.Output;
SqlParameter sp_depth = new SqlParameter("@Depth", SqlDbType.Int);
sp_depth.Direction = ParameterDirection.Output;
SqlParameter sp_getted = new SqlParameter("@Getted", SqlDbType.Int);
sp_getted.Direction = ParameterDirection.Output;

SqlCommand 개체의 Parameters 컬렉션에 파라미터 개체를 추가합니다.

scom.Parameters.Add(sp_url);
scom.Parameters.Add(sp_depth);
scom.Parameters.Add(sp_getted);

이제 연결하고 쿼리를 실행한 후 연결을 닫습니다.

scom.Connection.Open();
scom.ExecuteNonQuery();
scom.Connection.Close();

실제 수집 대상 항목을 얻어왔을 때는 Candidate 개체를 생성하여 반환합니다.

if ((int)sp_getted.Value == 1)
{
    string url = sp_url.Value as string;
    int depth = (int)sp_depth.Value;
    return new Candidate(url, depth);
}
return null;
public static Candidate GetFrontCandidate()
{
    SqlCommand scom = MakeSPCommand("GetFrontCandidate",
                                                          CommandType.StoredProcedure);

    SqlParameter sp_url = new SqlParameter("@Url", SqlDbType.VarChar, 200);
    sp_url.Direction = ParameterDirection.Output;
    SqlParameter sp_depth = new SqlParameter("@Depth", SqlDbType.Int);
    sp_depth.Direction = ParameterDirection.Output;
    SqlParameter sp_getted = new SqlParameter("@Getted", SqlDbType.Int);
    sp_getted.Direction = ParameterDirection.Output;

    scom.Parameters.Add(sp_url);
    scom.Parameters.Add(sp_depth);
    scom.Parameters.Add(sp_getted);

    scom.Connection.Open();
    scom.ExecuteNonQuery();
    scom.Connection.Close();

    if ((int)sp_getted.Value == 1)
    {
        string url = sp_url.Value as string;
        int depth = (int)sp_depth.Value;
        return new Candidate(url, depth);
    }
    return null;
}

이제 정상적으로 동작하는지 테스트를 해 보세요.

이번에는 수집 대상 목록을 얻어오는 부분을 구현합시다. 수집 대상 목록 가져오기 버튼인 btn_get_candilist의 클릭 이벤트 핸들러를 추가합니다.

클릭 이벤트 핸들러에서는 Candidate 리스트 항목을 얻어오기 위해 EHDbmForAll의 정적 메서드 GetCandidates를 추가합니다. 그리고 클릭 이벤트 핸들러에서 호출합니다.

List<Candidate> list = null;
list = EHDbmForAll.GetCandidates();

얻어온 Candidate 리스트의 각 항목을 인자로 ListViewItem 개체를 생성하여 수집 항목 리스트 뷰인 lv_candidate에 Items 컬렉션에 추가합니다.

ListViewItem lvi = null;
string[] items = null;

foreach (Candidate cd in list)
{
    items = new string[] { cd.Url, cd.Depth.ToString() };
    lvi = new ListViewItem(items);
    lv_candidate.Items.Add(lvi);
}

다음은 btn_get_candilist의 클릭 이벤트 핸들러 코드입니다.

private void btn_get_candilist_Click(object sender, EventArgs e)
{
    List<Candidate> list = null;
    list = EHDbmForAll.GetCandidates();

    ListViewItem lvi = null;
    string[] items = null;
    foreach (Candidate cd in list)
    {
        items = new string[] { cd.Url, cd.Depth.ToString() };
        lvi = new ListViewItem(items);
        lv_candidate.Items.Add(lvi);
    }
}

EHDbmForAll의 정적 메서드 GetCandidates를 구현합시다. 먼저 반환할 Candidate 리스트를 생성합니다.

List<Candidate> list = new List<Candidate>();

그리고 CandidateTable에서 페이지 주소와 상대적 깊이를 선택하는 SqlCommand 개체를 생성합니다. 여기에서는 SQL 쿼리문을 이용하여 SqlCommand 개체를 생성할게요. 쿼리문으로 SqlCommand 개체를 생성할 때 CommandType은 Text입니다.

SqlCommand scom = MakeSPCommand("SELECT Url, Depth From CandidateTable",
                                                       CommandType.Text);

연결을 Open한 후에 명령을 실행합니다. SqlCommand 개체를 이용해 명령을 실행할 때는 명령 종류에 따라 사용하는 메서드가 다릅니다. 추가나 삭제, 변경 등과 같이 변경한 행의 수만 알아도 되는 명령은 ExecuteNonQuery를 호출합니다. 하지만 Select 쿼리처럼 명령의 결과로 여러 행을 얻어야 할 때는 ExecuteReader를 호출합니다. 그리고 명령 실행의 결과로 결과 행을 순차적으로 접근할 수 있는 SqlDataReader 개체를 받습니다. 이 때는 개체의 Read 메서드를 호출하여 결과 행을 하나씩 접근할 수 있으며 SqlDataReader 개체의 Close 메서드를 호출해야 합니다. 여기에서는 SqlDataReader 개체로 얻어온 각 행의 정보로 Candidate 개체를 생성하여 list에 추가합니다.

scom.Connection.Open();
SqlDataReader sdr = scom.ExecuteReader();
Candidate candidate = null;
while (sdr.Read())
{
    candidate = new Candidate();
    candidate.Url = sdr["Url"] as string;
    candidate.Depth = (int)sdr["Depth"];
    list.Add(candidate);
}
sdr.Close();
scom.Connection.Close();
public static List<Candidate> GetCandidates()
{
    List<Candidate> list = new List<Candidate>();
    SqlCommand scom = MakeSPCommand("SELECT Url, Depth From CandidateTable",
                                                           CommandType.Text);
    scom.Connection.Open();
    SqlDataReader sdr = scom.ExecuteReader();
    Candidate candidate = null;

    while (sdr.Read())
    {
        candidate = new Candidate();
        candidate.Url = sdr["Url"] as string;
        candidate.Depth = (int)sdr["Depth"];
        list.Add(candidate);
    }
    sdr.Close();
    scom.Connection.Close();
    return list;
}

정상적으로 동작하는지 테스트를 해 보세요. 그리고 ADO.NET 기술을 포함하여 보다 정확히 기술을 익힐 필요성이 느끼면 관련 레퍼런스를 참고하시기 바랍니다. 이 책에서는 세부적인 기술을 하나 하나 설명하지 않습니다. 하나의 솔루션을 만드는 과정을 단계 별로 진행하여 개발 공정을 이해하고 경험하는데 그 목적이 있습니다.

이번에는 수집한 웹 사이트 정보를 추가하는 부분을 구현합시다. 물론 여기서 만드는 것은 DBM ForAll 라이브러리의 예광탄입니다. 따라서 웹 로봇에 의해 수집한 웹 사이트 정보를 가상으로 입력하여 DBMS에 추가하는 부분을 구현하는 것입니다.

수집한 웹 페이지 등록 버튼인 btn_add_purl 클릭 이벤트 핸들러를 추가합니다. 그리고 수집한 웹 페이지 정보를 작성하여 추가하는 기능을 담당하는 AddPostedUrlForm을 생성하고 시각화합니다.

private void btn_add_purl_Click(object sender, EventArgs e)
{
    AddPostedUrlForm addform = new AddPostedUrlForm();
    addform.Show();
}

AddPostedUrlForm의 등록하기 버튼인 btn_add의 클릭 이벤트 핸들러를 추가합니다. 클릭 이벤트 핸들러에서는 추가할 PostedUrl 개체를 생성합니다. 그리고 컨트롤에 입력한 정보로 PostedUrl 개체의 속성을 설정합니다. EHDbmForAll 클래스에 StoredPostedUrl 정적 메서드를 추가하여 이를 호출합니다.

private void btn_add_Click(object sender, EventArgs e)
{
    PostedUrl purl = new PostedUrl();
    purl.Url = tb_url.Text;
    purl.OriginUrl = tb_org_url.Text;
    purl.Title = tb_title.Text;
    purl.Depth = (int)nud_depth.Value;
    purl.Content = tb_content.Text;
    purl.PostedTime = dtp_ptime.Value;

    EHDbmForAll.StorePostedUrl(purl);
}

EHDbmForAll 클래스의 정적 메서드 StorePostedUrl 메서드를 구현합시다. 먼저 수집한 페이지 정보를 추가할 때 사용하려고 작성한 AddPostedUrl 저장 프로시저를 실행할 수 있는 SqlCommand 개체를 만듭니다.

SqlCommand scom = MakeSPCommand("AddPostedUrl",
                                                       CommandType.StoredProcedure);

AddPostedUrl 저장 프로시저의 인자로 전달할 파라미터 개체를 생성합니다. 생성할 때 파라미터의 값은 입력 인자로 받은 PostedUrl 개체의 각 속성을 사용합니다.

SqlParameter sp_url = new SqlParameter("@Url", purl.Url);
SqlParameter sp_ourl = new SqlParameter("@OriginUrl", purl.OriginUrl);
SqlParameter sp_depth = new SqlParameter("@Depth", purl.Depth);
SqlParameter sp_ptime = new SqlParameter("@PostedTime", purl.PostedTime);
SqlParameter sp_content = new SqlParameter("@PostedContent", purl.Content);
SqlParameter sp_title = new SqlParameter("@Title", purl.Title);

생성한 파라미터 개체를 SqlCommand 개체의 Parameters 컬렉션에 추가합니다.

scom.Parameters.Add(sp_url);
scom.Parameters.Add(sp_ourl);
scom.Parameters.Add(sp_depth);
scom.Parameters.Add(sp_ptime);
scom.Parameters.Add(sp_content);
scom.Parameters.Add(sp_title);

연결을 개방한 후에 명령을 실행하고 연결을 닫습니다.

scom.Connection.Open();
scom.ExecuteNonQuery();
scom.Connection.Close();
public static void StorePostedUrl(PostedUrl purl)
{
    SqlCommand scom = MakeSPCommand("AddPostedUrl",
                                                           CommandType.StoredProcedure);

    SqlParameter sp_url = new SqlParameter("@Url", purl.Url);
    SqlParameter sp_ourl = new SqlParameter("@OriginUrl", purl.OriginUrl);
    SqlParameter sp_depth = new SqlParameter("@Depth", purl.Depth);
    SqlParameter sp_ptime = new SqlParameter("@PostedTime", purl.PostedTime);
    SqlParameter sp_content = new SqlParameter("@PostedContent", purl.Content);
    SqlParameter sp_title = new SqlParameter("@Title", purl.Title);

    scom.Parameters.Add(sp_url);
    scom.Parameters.Add(sp_ourl);
    scom.Parameters.Add(sp_depth);
    scom.Parameters.Add(sp_ptime);
    scom.Parameters.Add(sp_content);
    scom.Parameters.Add(sp_title);

    scom.Connection.Open();
    scom.ExecuteNonQuery();
    scom.Connection.Close();
}

빌드한 후에 정상적으로 동작하는지 테스트하세요.

이번에는 MainForm의 두 번째 탭 페이지인 형태소/역 파일에서의 상호 작용을 구현합시다.

먼저 역 파일 테이블 생성 및 항목 추가 부분을 구현합시다. btn_ad_inv 버튼 컨트롤의 클릭 이벤트 핸들러를 추가합니다. 이벤트 핸들러에서는 형태소 개체를 생성하여 형태소의 이름과 참조 개수를 설정합니다. 그리고 EHDbmForAll 클래스의 정적 메서드로 웹 페이지 주소와 형태소 개체를 입력 인자로 받는 형태소 정보 추가 메서드를 추가하여 이를 호출합니다.

private void btn_add_inv_Click(object sender, EventArgs e)
{
    Morpheme morpheme = new Morpheme();
    morpheme.Name = tbox_mopheme.Text;
    int rcnt = 0;
    if (int.TryParse(tbox_rcnt.Text, out rcnt) == false)
    {
        MessageBox.Show("참조 개수는 정수를 입력하세요.");
        return;
    }
    morpheme.Count = rcnt;

    string purl = tbox_url.Text;
    if (EHDbmForAll.AddMorphemeInfo(purl, morpheme))
    {
        MessageBox.Show("추가하였습니다.");
    }
}

EHDbmForAll 클래스의 정적 메서드 AddMorphemeInfo 에서는 형태소 이름을 추가한 후에 역 파일 요소를 추가합니다. 여기에서는 이 두 개의 기능을 별도의 메서드로 정의하여 호출합시다. 형태소 이름을 추가하는 메서드는 형태소 이름을 입력 인자로 전달받고 형태소 추가 여부를 반환합니다. 역 파일 요소를 추가하는 메서드에서는 형태소 이름과 형태소를 포함하는 웹 페이지 주소와 참조 개수를 입력 인자로 전달받습니다.

public static bool AddMorphemeInfo(string purl, Morpheme morpheme)
{
    bool re = AddMorphemeInfo(morpheme.Name);
    AddInvertedItem(morpheme.Name, purl, morpheme.Count);
    return re;
}

형태소 이름을 추가하는 메서드는 저장 프로시저 AddMorphemeInfo를 이용합니다. 이에 AddMorphemeInfo 저장 프로시저를 실행할 수 있는 SqlCommand  개체를 생성합니다.

SqlCommand scom = MakeSPCommand("AddMorphemeInfo",
                                 CommandType.StoredProcedure);

형태소 이름을 위한 SqlParameter 개체를 생성하고 이미 존재하는 형태소인지 확인할 수 있게 파라미터 방향이 Output인 개체를 생성하여 SqlCommand 개체의 Parameters 컬렉션에 추가합니다.

SqlParameter sp_mor = new SqlParameter("@Morpheme", mname);
SqlParameter sp_existed = new SqlParameter("@Existed", SqlDbType.Int);
sp_existed.Direction = ParameterDirection.Output;

scom.Parameters.Add(sp_mor);
scom.Parameters.Add(sp_existed);

연결을 개방하고 쿼리를 실행한 후 연결을 닫습니다. 그리고 추가 여부를 반환합니다.

scom.Connection.Open();
scom.ExecuteNonQuery();
scom.Connection.Close();

return (int)sp_existed.Value == 0;
private static bool AddMorphemeInfo(string mname)
{
    SqlCommand scom = MakeSPCommand("AddMorphemeInfo",
                                                           CommandType.StoredProcedure);

    SqlParameter sp_mor = new SqlParameter("@Morpheme", mname);
    SqlParameter sp_existed = new SqlParameter("@Existed", SqlDbType.Int);
    sp_existed.Direction = ParameterDirection.Output;

    scom.Parameters.Add(sp_mor);
    scom.Parameters.Add(sp_existed);

    scom.Connection.Open();
    scom.ExecuteNonQuery();
    scom.Connection.Close();

    return (int)sp_existed.Value == 0;
}

역 파일 요소를 추가하는 메서드는 AddInvertedItem 저장 프로시저를 이용합니다. 따라서 AddInvertedItem 저장 프리시저를 실행할 수 있는 SqlCommand 개체를 생성합니다.

SqlCommand scom = MakeSPCommand("AddInvertedItem", 
                                 CommandType.StoredProcedure);

형태소 이름과 웹 페이지 주소, 참조 개수를 전달하기 위한 SqlParameter 개체를 생성하고 SqlCommand 개체의 Parameters 컬렉션에 추가합니다.

SqlParameter sp_mor = new SqlParameter("@Morpheme", mname);
SqlParameter sp_url = new SqlParameter("@Url", purl);
SqlParameter sp_rcnt = new SqlParameter("@Refcnt", count);

scom.Parameters.Add(sp_mor);
scom.Parameters.Add(sp_url);
scom.Parameters.Add(sp_rcnt);

연결을 개방하고 쿼리를 실행한 후 연결을 닫습니다. 같은 기술을 같은 방법으로 사용하는 것을 느끼고 있을 것입니다.

scom.Connection.Open();
scom.ExecuteNonQuery();
scom.Connection.Close();

다음은 AddInvertedItem 메서드 구현 코드입니다.

private static void AddInvertedItem(string mname, string purl, int count)
{
    SqlCommand scom = MakeSPCommand("AddInvertedItem", 
                                                           CommandType.StoredProcedure);

    SqlParameter sp_mor = new SqlParameter("@Morpheme", mname);
    SqlParameter sp_url = new SqlParameter("@Url", purl);
    SqlParameter sp_rcnt = new SqlParameter("@Refcnt", count);

    scom.Parameters.Add(sp_mor);
    scom.Parameters.Add(sp_url);
    scom.Parameters.Add(sp_rcnt);

    scom.Connection.Open();
    scom.ExecuteNonQuery();
    scom.Connection.Close();
}

이제 테스트를 해 보세요. 형태소를 추가하면 IndexInvFileTable에 항목이 늘어나고 동적으로 테이블이 생성하는 것을 확인할 수 있습니다.

이번에는 형태소 항목 얻어오기 기능을 구현합시다. 먼저 btn_get_molist 버튼의 클릭 이벤트 핸들러를 추가합니다. EHDbmForAll의 정적 메서드 형태소 목록을 반환하는 정적 메서드를 추가하고 클릭 이벤트 핸들러에서 이를 호출합니다. 호출한 결과로 형태소 목록을 보여주는 lbox_morpheme의 DataSource 속성을 설정합니다.

private void btn_get_molist_Click(object sender, EventArgs e)
{
    lbox_morpheme.DataSource = EHDbmForAll.GetMorphemes();
}

EHDbmForAll의 GetMorphemes 메서드에서는 형태소 목록을 반환합니다. 반환 형식은 List<string>으로 합시다. 먼저 형태소 목록을 담기 위한 List 개체를 생성합니다.

List<string> list = new List<string>();

역파일 테이블에서 형태소 목록을 선태하는 쿼리문을 실행할 수 있는 SqlCommand 개체를 생성합니다.

SqlCommand scom = MakeSPCommand(
                 "SELECT Morpheme FROM IndexInvFileTable",CommandType.Text);

연결을 개방하고 쿼리를 실행합니다. SELECT 쿼리문을 실행하므로 ExecuteReader 메서드를 호출하고 SqlDataReader 개체를 반환받습니다.

scom.Connection.Open();
SqlDataReader sdr = scom.ExecuteReader();

SqlDataReader 개체의 모든 항목을 list에 추가하고 루프를 탈출하면 SqlDataReader 개체를 닫고 연결도 닫습니다.

while (sdr.Read())
{
    list.Add(sdr["Morpheme"] as string);
}
sdr.Close();
scom.Connection.Close();

마지막으로 형태소 목록을 보관한 list 개체를 반환합니다.

return list;
public static List<string> GetMorphemes()
{
    List<string> list = new List<string>();

    SqlCommand scom = MakeSPCommand(
                    "SELECT Morpheme FROM IndexInvFileTable",CommandType.Text);

    scom.Connection.Open();

    SqlDataReader sdr = scom.ExecuteReader();

    while (sdr.Read())
    {
        list.Add(sdr["Morpheme"] as string);
    }
    sdr.Close();

    scom.Connection.Close();
    return list;
}

정상적으로 동작하는지 테스트하세요.

마지막으로 수집한 웹 페이지의 전체 형태소 개수를 등록하는 기능을 구현합시다. 먼저 btn_add_tmo 버튼의 클릭 이벤트 핸들러를 추가합니다. 이벤트 핸들러에서는 입력한 웹 페이지 주소와 전체 형태소 개수를 얻어옵니다. 그리고 EHDbnForAll 클래스에 정적 메서드로 전체 형태소 개수를 등록하는 메서드를 추가하여 이를 호출합니다.

private void btn_add_tmo_Click(object sender, EventArgs e)
{
    string url = tbox_purl.Text;
    int tcnt = 0;

    if (int.TryParse(tbox_tcnt_mo.Text, out tcnt) == false)
    {
        MessageBox.Show("전체 형태소 개수는 정수여야 합니다.");
        return;
    }

    EHDbmForAll.AddMCPostedInfo(url, tcnt);
    MessageBox.Show("추가하였습니다.");
}

EHDbmForAll 클래스의 AddMCPostedInfo 메서드를 구현합시다. 여기에서는 저장 프로시저 AddMCPostedInfo를 이용하므로 이를 실행할 수 있는 SqlCommand 개체를 생성합니다.

 SqlCommand scom = MakeSPCommand("AddMCPostedUrlInfo",
                                 CommandType.StoredProcedure);

웹 페이지 주소와 전체 형태소 개수를 전달할 SqlParameter개체 생성 후 SqlCommand 개체의 Parameters 컬렉션에 추가합니다.

SqlParameter sp_url = new SqlParameter("@Url", url);
SqlParameter sp_tcnt = new SqlParameter("@TotalCount", tcnt);

scom.Parameters.Add(sp_url);
scom.Parameters.Add(sp_tcnt);

연결을 개방한 후에 쿼리를 실행하고 연결을 닫습니다.

scom.Connection.Open();
scom.ExecuteNonQuery();
scom.Connection.Close();

다음은 AddMCPostedInfo 메서드 코드입니다.

public static void AddMCPostedInfo(string url, int tcnt)
{
    SqlCommand scom = MakeSPCommand("AddMCPostedUrlInfo",
                                                           CommandType.StoredProcedure);

    SqlParameter sp_url = new SqlParameter("@Url", url);
    SqlParameter sp_tcnt = new SqlParameter("@TotalCount", tcnt);

    scom.Parameters.Add(sp_url);
    scom.Parameters.Add(sp_tcnt);

    scom.Connection.Open();
    scom.ExecuteNonQuery();
    scom.Connection.Close();
}

정상적으로 동작하는지 테스트하세요.

이상으로 DBM ForAll 예광탄 구현이 끝났습니다. MainForm과 AddPostedUrlForm의 소스 파일의 코드는 다음과 같습니다. EHDbmForAll.cs 파일의 코드는 DBM ForAll 라이브러리 만들기와 같으므로 여기에서는 코드 내용을 생략할게요.

▷ MainForm.cs

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using WSE_Core; 
namespace DBM_For_All_Tracer
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        } 
        private void btn_addseed_Click(object sender, EventArgs e)
        {
            EHDbmForAll.AddSeedSite(tbox_seed.Text);
        } 
        private void btn_get_fcandi_Click(object sender, EventArgs e)
        {
            Candidate candidate = EHDbmForAll.GetFrontCandidate();
            if (candidate == null)
            {
                lb_fcandi_url.Text = "없음";
                lb_depth.Text = "N/A";
            }
            else
            {
                lb_fcandi_url.Text = candidate.Url;
                lb_depth.Text = candidate.Depth.ToString();
            }
        } 
        private void btn_get_candilist_Click(object sender, EventArgs e)
        {
            List<Candidate> list = null;
            list = EHDbmForAll.GetCandidates(); 
            ListViewItem lvi = null;
            string[] items = null; 
            foreach (Candidate cd in list)
            {
                items = new string[] { cd.Url, cd.Depth.ToString() };
                lvi = new ListViewItem(items);
                lv_candidate.Items.Add(lvi);
            } 
        } 

        private void btn_add_purl_Click(object sender, EventArgs e)
        {
            AddPostedUrlForm addform = new AddPostedUrlForm();
            addform.Show();
        } 
        private void btn_add_inv_Click(object sender, EventArgs e)
        {
            Morpheme morpheme = new Morpheme();
            morpheme.Name = tbox_mopheme.Text;
            int rcnt = 0;
            if (int.TryParse(tbox_rcnt.Text, out rcnt) == false)
            {
                MessageBox.Show("참조 개수는 정수를 입력하세요.");
                return;
            }
            morpheme.Count = rcnt;
            string purl = tbox_url.Text; 
            if (EHDbmForAll.AddMorphemeInfo(purl, morpheme))
            {
                MessageBox.Show("추가하였습니다.");
            } 
        } 

        private void btn_get_molist_Click(object sender, EventArgs e)
        {
            lbox_morpheme.DataSource = EHDbmForAll.GetMorphemes();
        } 

        private void btn_add_tmo_Click(object sender, EventArgs e)
        {
            string url = tbox_purl.Text;
            int tcnt = 0;
 
            if (int.TryParse(tbox_tcnt_mo.Text, out tcnt) == false)
            {
                MessageBox.Show("전체 형태소 개수는 정수여야 합니다.");
                return;
            }
 
            EHDbmForAll.AddMCPostedInfo(url, tcnt);
            MessageBox.Show("추가하였습니다.");
 
        }
    }
}

▷ AddPostedUrlForm.cs

using System;
using System.Windows.Forms;
using WSE_Core;
 
namespace DBM_For_All_Tracer
{
    public partial class AddPostedUrlForm : Form
    {
        public AddPostedUrlForm()
        {
            InitializeComponent();
        }
 
        private void btn_add_Click(object sender, EventArgs e)
        {
            PostedUrl purl = new PostedUrl();
            purl.Url = tb_url.Text;
            purl.OriginUrl = tb_org_url.Text;
            purl.Title = tb_title.Text;
            purl.Depth = (int)nud_depth.Value;
            purl.Content = tb_content.Text;
            purl.PostedTime = dtp_ptime.Value;
 
            EHDbmForAll.StorePostedUrl(purl);
        }
    }
}