login register Sysop! about ME  
qrcode
    최초 작성일 :    2007년 12월 17일
  최종 수정일 :    2008년 01월 01일
  작성자 :    kobukii
  편집자 :    kobukii(김 경균)
  읽음수 :    29,967

강좌 목록으로 돌아가기

필자의 잡담~

kobukii 화이팅~

LINQ to SQL에 대하여 알아보자

이번 강좌에서는 LINQ to SQL을 사용하는 방법을 간단한 Blog 예제를 통해 알아보도록 하겠습니다. LINQ to SQL은 LINQ의 한 부분으로 SQL서버의 데이터를 좀 더 편리하고 직관적으로 다룰 수 있게 해주는 Microsoft의 새로운 기술입니다. 최신 버전의 LINQ는 .NET Framework3.5가 정식 릴리즈 되면서 약간 변경된 부분이 있으며 본 강좌에서는 새롭게 릴리즈된 .NET Framework3.5 기반으로 코드를 작성하였습니다.
또한 이 글의 모든 코드는 Visual Studio 2008 RTM버전으로 작성 되었습니다.

1. 웹프로젝트 생성 및 dbml(LINQ to SQL Classes)생성

가장 먼저 Web Project를 생성합니다.

웹 어플리케이션을 생성한 후 프로젝트 및 솔루션 이름을 Lec_Blog로 입력 합니다.

다음으로 LINQ to SQL Classes템플릿을 선택하여 Blog.dbml이라는 이름으로 파일을 생성합니다.

LINQ to SQL Classes를 생성하면 LINQ to SQL Designer화면이 나타나게 되며 이 영역에 데이터베이스를 표현하는 모델 클래스를 추가할 수 있습니다. 테이블, 저장프로시저, 사용자 정의 함수 등이 추가되면 LINQ to SQL Classes는 실제 데이터베이스의 각 테이블을 나타내는 프로퍼티와 저장 프로시저를 나타내는 매서드 등을 포함하는 DataContext클래스를 생성하게 됩니다. DataContext클래스는 변경된 데이터를 적용하거나 데이터베이스로부터 데이터를 쿼리 할 때 반드시 사 용하게 되는 LINQ to SQL의 핵심이 되는 클래스 입니다.

LINQ to SQL Classes 파일을 추가하면 다음과 같은 LINQ to SQL Designer 화면이 나타나게 됩니다.

LINQ to SQL Designer의 왼쪽 영역에 테이블을 끌어 놓게 되면 데이터 클래스를 자동으로 생성합니다. 오른쪽 영역은 저장프로시저 또는 사용자정의 함수를 끌어 놓을 경우 각 저장프로시저와 사용자 정의 함수에 해당되는 매서드를 자동으로 생성합니다.

LINQ to SQL Designer의 왼쪽 영역에 테이블을 추가 해 보도록 하겠습니다.

우선 Server Explorer을 열고 데이터베이스에 연결합니다.

미리 추가한 Lec_Blog 데이터베이스를 선택하고 ok버튼을 클릭합니다.
데이터베이스가 추가 되면 아래 이미지와 같은 형태로 Server Explorer가 구성됩니다.

Server Explorer에서 Category테이블과 Post테이블을 LINQ to SQL Designer의 왼쪽 영역 끌어 놓으면 끌어 놓은 각 테이블들이 시각적으로 표시 됩니다.

이제 LINQ to SQL을 이용하여 간단한 블로그 개발을 하기 위한 기본적인 준비가 완료 되었습니다.
테이블 스키마 및 소스코드는 여기서 다운로드 받으실 수 있습니다.

다운로드 받으시려면 클릭하세요

2. 데이터 가져오기(SELECT)

Post.aspx라는 웹페이지를 추가합니다. 이 페이지는 포스트를 작성, 수정 그리고 삭제 하는 기능을 갖고 있는 페이지 입니다. 페이지의 구성은 카테고리를 선택할 수 있는 DropDownLIst, 제목과 텍스트를 입력 받는 TextBox 그리고 저장을 위한 Button으로 구성되어 있습니다.

첫번째로 새 포스트를 작성할 때 카테고리를 선택할 수 있는 기능을 추가 하도록 하겠습니다.

BlogDataContext db;
protected void Page_Load(object sender, EventArgs e)
{
   db = new BlogDataContext();
   if (!IsPostBack)
    {
        cateogryBind();
    }
}
private void cateogryBind()
{
    IQueryable<Category> category = from c in db.Categories
                                        select c;
    //IQueryable<Category> category = db.Categories.Select(p => p);
    ddlCategory.DataSource = category;
    ddlCategory.DataBind();
}

위의 코드에서 가장 먼저 알아야 할 부분이 BlogDataContext입니다. 앞에서도 말한 것 처럼 DataContext클래스는 변경된 데이터를 적용하거나 데이터베이스로부터 데이터를 쿼리 할 때 반드시 사용하게 되는 LINQ to SQL의 핵심이 되는 클래스 입니다.

 코드의 categoryBind 매서드를 보면 쿼리구문을 이용하여 Category테이블의 모든 데이터를 가져와 IQueryable<Category>타입의 변수에 할당 합니다.
쿼리구문을 보면 from절에서 데이터를 가져오게 될 데이터소스를 지정합니다. BlogDataContext의 인스턴스인 db개체를 이용하여 Categories에 접근하며 이 Categories(Category테이블 클래스)의 entity를 c라 가정하고 특별한 조건(Where절) 없이 c를 Select합니다. 이는 카테고리 테이블의 모든 데이터를 가져온다는 의미와 같습니다.
쿼리구문 아래 주석으로 처리된 부분은 쿼리구문을 사용하는 대신 Lambda Expression을 사용하여 데이터를 가져오는 또 다른 방법입니다. 어떤 방법을 사용해도 결과는 동일합니다.

가져온 데이터를 DropDownList의 DataSource에 할당하고 바인드를 합니다.
DropDownList의 DataSource로 ListItemCollection타입을 사용하지 않을 경우에는 DataTextField와 DataValueField를 반드시 지정해야 합니다. 이를 각각 CategoryName과 CategoryID로 지정 해 주면 Category를 선택할 수 있는 DropDownList가 만들어 지게 됩니다.

데이터의 조회는 마지막 챕터에서 더 자세히 알아보도록 하겠습니다.

3. 데이터 저장(INSERT)

다음으로 포스트의 내용을 저장하는 방법에 대해 알아보겠습니다.

protected void btnSubmit_Click(object sender, EventArgs e)
{
    Post post = new Post();
    post.Title = txtTitle.Text;
    post.Content = txtContent.Text;
    post.PubDate = DateTime.Now;
    post.CategoryID = Convert.ToInt32(ddlCategory.SelectedItem.Value);
    db.Posts.InsertOnSubmit(post);
}

LINQ to SQL Classes에 Category와 Post테이블을 끌어 놓았기 때문에 각 테이블의 entity에 해당하는 Post 및 Category클래스가 생성 됩니다.
가장먼저 Post의 인스턴스를 만듭니다. LINQ는 테이블의 필드명을 쉽게 확인 할 수 있도록 완벽한 인텔리센스를 제공합니다. 생성된 post개체의 각 필드에 값을 할당합니다. 마지막으로 테이블에 해당하는 db.Posts의 InsertOnSubmit을 호출합니다. Posts의 InsertOnSubmit매서드는 매개변수로 Posts의 entity인 Post타입의 개체를 받습니다.

여기서 기존에 LINQ를 사용 해 보신 분들께서는 InsertOnSubmit매서드가 생소할 수 있습니다. 기존에는 분명 Add매서드를 이용하여 데이터를 추가하고 OnSubmitChange매서드를 호출하여 데이터를 전송했었지만 정식버전의 출시와 함께 약간의 매서드가 변경 되었습니다.

Beta1 and Beta2 VS 2008 RTM
Add() InsertOnSubmit()
AddAll() InsertAllOnSubmit()
Remove() DeleteOnSubmit()
RemoveAll() DeleteAllOnSubmit()

위의 표는 Beta에서 RTM로 버전 업 되면서 변경된 매서드 들입니다. 참고 하세요..

4. 데이터의 수정 및 삭제

데이터를 수정하거나 삭제하기 위해서는 가장 먼저 수정이나 삭제를 하기 위한 데이터를 가져와 entity 개체를 생성해야 합니다.

Post post;
int postId = Convert.ToInt32(Request.QueryString["postid"]);
if (postId > 0)
{
    post = db.Posts.Single(p => p.PostID == postId);
}

데이터의 삽입과 마찬가지로 entity개체의 각 속성에 값을 할당합니다.

post.Title = txtTitle.Text;
post.Content = txtContent.Text;
post.PubDate = DateTime.Now;
post.CategoryID = Convert.ToInt32(ddlCategory.SelectedItem.Value);

값을 모두 할당 한 다음 변경된 값을 처리하기 위해 BlogDataContext의 SubmitChanges 매서드를 호출하여 수정을 완료 합니다.

db.SubmitChanges();

데이터 삭제의 경우는 entity개체를 생성한 후 db개체의 DeleteOnSubmit매서드를 호출 하기만 하면 삭제 처리가 이루어 집니다.

5. 쿼리구문의 용법 및 컨트롤 데이터 바인딩

지금부터 조금 더 다양한 LINQ 쿼리 구문을 이용하여 데이터를 가져오고 또한 가져온 데이터를 ListView컨트롤에 바인딩 하는 방법을 블로그의 매인 페이지를 만들면서 보다 자세히 설명하도록 하겠습니다.

블로그의 메인페이지는 왼쪽 카테고리 영역과 오른쪽 포스트목록 영역으로 나누도록 하겠습니다.
이 두 부분은 .Net Frameowrk3.5에서 새롭게 등장한 ListView컨트롤을 사용하고 있습니다.

<asp:ListView ID="categoryList" ItemPlaceholderID="categoryItems" runat="server">
    <LayoutTemplate>
        <ul style="margin:0 0 0 0;padding:0 0 0 0">
            <asp:PlaceHolder ID="categoryItems" runat="server"></asp:PlaceHolder>
        </ul>
    </LayoutTemplate>
    <ItemTemplate>
        <li><a href="default.aspx?categoryid=<%#Eval("categoryID") %>"><%#Eval("CategoryName") %></a></li>
    </ItemTemplate>
</asp:ListView>

ListView컨트롤의 사용법은 본 강좌의 주제에서 벗어나기 때문에 간단하게 설명 하도록 하겠습니다.
ListView컨트롤은 간단히 LayoutTemplete과 ItemTemplete영역으로 나눌 수 있는데 LayoutTemplete은 목록의 헤더 또는 목록을 감싸는 Tag를 지정하여 기본 Layout을 지정하는 영역입니다. 반복되는 데이터를 표현하는 부분을 PlaceHolder를 통해 지정해주고 이 컨트롤의 아이디를 ListView컨트롤의ItemPlaceholderID속성에 지정합니다.
ItemTemplete영역은 실제 반복되는 부분을 만들어 줍니다. 데이터는 Eval매서드를 통해 지정해 줍니다. 이곳에서 반복적으로 생성된 코드가 LayoutTemplete의 PlaceHolder컨트롤에 표현됩니다.

다음으로 데이터를 ListView컨트롤에 바인딩 하는 코드는 아래와 같습니다.

List<Category> categories = db.Categories.ToList();
categoryList.DataSource = categories;
categoryList.DataBind();

코드는 DropDownList에 데이터를 바인딩 했을 때와 상당히 유사합니다. 다른 점은 ToList() 확장 매서드를 사용하여 반환 타입을 List<T>타입으로 한다는 것입니다.

쿼리구문은 기본적으로 IQueryable<T>형태로 만들어 지게 됩니다. 이 타입은 루프를 돌면서 값을 사용하기 시작할 때 실제 DB와 통신을 하여 값을 가져오게 됩니다.
위의 이미지에서도 확인 되듯이 디버그를 해 보면 var categories 부분 에서는 아무런 데이터를 받아 오지 못하고 DataSource에 값을 할당 하였을 때 비로소 데이터를 받아 온 것을 볼 수 있습니다.

위의 코드는 ToList() 확장 매서드를 사용한 경우인데 이때는 categories변수에 마우스를 올리자 바로 값을 받아 온 것을 확인 할 수 있습니다.
ToList(), ToArray()와 같은 확장 매서드를 사용하면 데이터의 타입을 변경하는 동시에 즉시 데이터베이스와 통신을 하여 값을 받아 올 수 있습니다.

마지막으로 Where구문을 사용하여 조건 별로 데이터를 가져오고 Skip과 Take확장 매서드를 사용하여 페이징을 처리 하는 부분에 대해 알아보도록 하겠습니다.

1) 조건 절을 이용한 데이터 조회 및 정렬(단일 포스트 가져오기)

List posts = (from p in db.Posts
                where p.PostID == postid
                orderby p.PubDate descending //정렬
                select p).ToList();

위의 코드에서는 where절을 사용하여 PostID값을 비교하여 데이터를 가져오며 orderby절을 이용하여 PubDate를 기준으로 내림차순으로 정렬합니다.

2) 페이징(한 화면에 보여 줄 포스트들을 가져오기)

List<Post> posts = (from p in db.Posts
                            orderby p.PubDate descending
                            select p).Skip(currentPageIdx * itemCount).Take(itemCount).ToList();

PubDate를 기준으로 정렬 하며 Skip확장 매서드를 통해 [현재페이지번호*한 페이지의 항목 수]만큼을 skip하고 Take확장 매서드를 사용하여 한 페이지의 항목 수 만큼 가져옵니다.

3) 조건 절과 페이지을 모두 이용한 데이터 조회(카테고리 별 목록을 페이징을 통하여 가져오기)

List<Post> posts = (from p in db.Posts
                            where p.CategoryID == categoryid
                            orderby p.PubDate descending
                            select p).Skip(currentPageIdx * itemCount).Take(itemCount).ToList();

위에서 가져온 데이터를 ListView컨트롤에 바인딩 하기 위해 ListView컨트롤을 구성하면

// Category영역
<asp:ListView ID="ListView1" runat="server" ItemPlaceholderID="categoryItems">
    <LayoutTemplate>
        <ul style="margin:0 0 0 0;padding:0 0 0 0">
        <asp:PlaceHolder ID="categoryItems" runat="server"></asp:PlaceHolder>
        </ul>
    </LayoutTemplate>
    <ItemTemplate>
        <li>
        <a href='default.aspx?categoryid=<%#Eval("categoryID") %>'>
        <%#Eval("CategoryName") %></a>
        </li>
    </ItemTemplate>
</asp:ListView>

// 포스트 영역
<asp:ListView ID="postList" ItemPlaceholderID="Items" runat="server">
    <LayoutTemplate>
        <asp:PlaceHolder ID="Items" runat="server"></asp:PlaceHolder>
    </LayoutTemplate>
    <ItemTemplate>
        <div>
        <div id="titArea">
            <span id="title" style="color:White;">
            <a href="/Default.aspx? postid=<%#Eval("postid") %>"><%#Eval("title") %></a>
            </span>
        </div>
        <div id="descArea">
            <span id="pubDate"><%#Eval("pubDate") %></span>
            <span id="category"><%#Eval("Category.CategoryName") %></span>
            <span id="edit"><a href="post.aspx?postid=<%#Eval("postid") %>">[수정]</a> 
            </span>
        </div>
        <div id="contentArea">
            <%#Eval("content") %>
        </div>
        </div>
    </ItemTemplate>
</asp:ListView>

블로그 포스트 영역에서는 LayoutTemplate에 PlaceHolder컨트롤을 추가하고 ItemTemplete에 제목영역과 설명영역(발행일, 카테고리, 수정버튼), 컨텐츠 영역을 각 div태그로 생성합니다.
데이터는 Eval매서드를 통하여 지정을 하는데 여기서 특이한 점은 <%#Eval("Category.CategoryName") %>부분에서 CategoryName을 가져오는 것입니다. Post테이블에는 CategoryID만 존재할 뿐 CategoryName은 존재하지 않는데 위의 코드에서는 CategoryName을 지정해 줬다는 것입니다. 이는 Post테이블이 CategoryID속성을 통해 Category테이블과 연관관계를 맺고 있으므로 Post의 Category개체를 통해 CategoryName에 접근 할 수 있게 되는 것 입니다.

카테고리 영역에서는 Unorder List(UL)태그를 ListView컨트롤에 사용하여 카테고리 역영을 생성합니다. LayoutTemplete영역을 <ul>태그로 구성하고 반복데이터가 표현될 영역을 PlaceHolder컨트롤로 지정합니다. ItemTelplete영역은 <li>태그를 사용하여 반복되는 데이터를 표현합니다.
ListView컨트롤은 반드시 테이블 형태가 아닌 어떠한 반복 구조도 표현 할 수 있는 장점이 있습니다.

이번 글에서는 LINQ to SQL의 간단한 사용법에 대해서 알아 보았습니다. '어떻게 LINQ to SQL을 사용할 수 있는 가'에 초점을 맞추었기 때문에 LINQ의 일반적인 사용법에 대해서만 알아 보았습니다. 다음 강좌는 다양한 쿼리구문과 Lambda Expression 예제를 통해 LINQ에 대해 보다 자세히 알아보도록 하겠습니다.

그럼 다음강좌에서 다시 뵙도록 하겠습니다.^^ 감사합니다.


authored by

  khw2231
  2009-06-02(16:27)
강좌 잘 보았습니다^^
그런데 테스트를 하니 한가지 오류가 나서요..
post = db.Posts.Single(p => p.PostID == postId);
여기서 p는 무엇인가요..p에서 오류가 나는 것 같은데요..ㅠ

  kikky
  2009-10-01(01:59)
람다표현식... p는 Posts겠죠? 보니깐 insert에도 SubmitChanges(); 해줘야 하는
듯 싶더군요.


 
 
.NET과 Java 동영상 기반의 교육사이트

로딩 중입니다...

서버 프레임워크 지원 : NeoDEEX
based on ASP.NET 3.5
Creative Commons License
{5}
{2} 읽음   :{3} ({4})