방법: Windows Phone의 로컬 데이터베이스 응용프로그램(MVVM 포함) 만들기

2012-02-09

Windows Phone OS 7.1 에서는 응용프로그램의 격리된 저장소 컨테이너에 있는 로컬 데이터베이스에 관계형 데이터를 저장할 수 있습니다. 이 항목에서는 로컬 데이터베이스를 사용하는 다중 페이지 할일 목록 응용프로그램을 만드는 방법에 대해 설명합니다. 이 응용프로그램은 방법: Windows Phone용 기본 로컬 데이터베이스 응용프로그램 만들기에 설명된 응용프로그램과 유사하지만 MVVM(Model-View-ViewModel) 패턴과 Pivot 컨트롤, Silverlight for Windows Phone Toolkit 및 데이터베이스 연결을 사용하는 방법을 추가로 보여 줍니다. Windows Phone 응용프로그램에서 로컬 데이터베이스를 사용하는 방법에 대한 자세한 내용은 Windows Phone의 로컬 데이터베이스 개요를 참조하십시오.

팁팁:

이 항목은 로컬 데이터베이스 샘플에 해당합니다. 전체 프로젝트를 다운로드하려면 Windows Phone용 코드 샘플을 참조하십시오.

이 항목에서는 다음 주요 단계에 대해 설명합니다.

  1. 응용프로그램 준비

  2. 응용프로그램 UI 만들기

  3. 데이터 모델 만들기

  4. ViewModel 만들기

  5. 응용프로그램 완성

다음 이미지는 응용프로그램 피벗 페이지와 새 작업 페이지를 보여 줍니다.

AP_Con_Local_Database_MVVM

이 프로세스에서는 다음 코드 파일을 수정하거나 만듭니다.

  • MainPage.xaml: 응용프로그램의 기본 페이지를 수정하여 all, home, workhobbies 카테고리별로 그룹화된 할일 작업을 표시하는 Pivot 컨트롤을 추가합니다.

  • NewTaskPage.xaml: 이 페이지를 추가하여 데이터베이스에 새 작업을 추가하기 위한 UI를 제공합니다. 이 페이지는 작업과 연결된 카테고리를 지정하기 위해 Silverlight 도구 키트 ListPicker 컨트롤을 사용합니다.

  • Model\ToDoDataContext.cs: 이 클래스 파일을 만들어 로컬 데이터베이스를 나타내는 개체 모델과 LINQ to SQL 데이터 컨텍스트를 지정합니다. MVVM 측면에서 이 클래스는 데이터 모델입니다.

  • ViewModel\ToDoViewModel.cs: 이 클래스 파일을 만들어 응용프로그램의 ViewModel을 나타냅니다. 이 클래스에는 카테고리별로 할일 작업을 그룹화하기 위한 여러 관찰 가능 컬렉션이 포함되어 있습니다. ViewModel은 데이터베이스에서 추가 및 삭제 작업을 수행하는 메서드도 제공합니다.

  • App.xaml.cs: 이 파일을 수정하여 응용프로그램 전체에서 액세스할 수 있는 정적 ViewModel과 로컬 데이터베이스를 만듭니다.

  • MainPage.xaml.cs: 이 페이지를 수정하여 페이지 DataContext 속성을 응용프로그램 ViewModel로 설정하고 추가삭제 버튼 클릭을 처리합니다.

  • NewTaskPage.xaml.cs: 이 페이지를 수정하여 페이지 DataContext 속성을 응용프로그램 ViewModel로 설정하고 확인취소 버튼 클릭을 처리합니다.

참고참고:

다음 절차의 단계는 Windows Phone용 Visual Studio 2010 Express에 적용됩니다. Visual Studio 2010 Professional 또는 Visual Studio 2010 Ultimate용 추가 기능을 사용하는 경우에는 메뉴 명령이나 창 레이아웃에서 일부 소규모 변형이 나타날 수 있습니다.  

이 단원에서는 응용프로그램을 만들고, Silverlight for Windows Phone Toolkit를 설치하고, 어셈블리 참조를 설정하고, 응용프로그램에서 사용되는 아이콘을 추가합니다.

응용프로그램을 만들려면

  1. Windows Phone용 Visual Studio 2010 Express 에서 파일 | 새 프로젝트 메뉴 명령을 선택하여 새 프로젝트를 만듭니다.

  2. 새 프로젝트 창이 표시됩니다. Visual C# 템플릿을 확장하고 Windows Phone용 Silverlight 템플릿을 선택합니다.

  3. Windows Phone 응용프로그램 템플릿을 선택합니다. 이름 상자에 선택한 이름을 입력합니다.

  4. 확인을 클릭합니다. 새 Windows Phone 응용프로그램 창이 표시됩니다.

  5. 대상 Windows Phone 버전 메뉴에서 Windows Phone 7.1 이 선택되었는지 확인합니다.

  6. 확인을 클릭합니다. 새 프로젝트가 생성되고 MainPage.xaml이 Visual Studio 디자이너 창에서 열립니다.

Silverlight for Windows Phone Toolkit를 설치하려면

  1. Silverlight for Windows Phone Toolkit는 Windows Phone SDK 7.1 에 포함되어 있지 않습니다. Codeplex의 Silverlight 도구 키트 웹 사이트에서 별도로 도구 키트를 다운로드해야 합니다. 해당 웹 사이트로 이동하여 Silverlight for Windows Phone Toolkit 링크를 클릭합니다.

  2. Silverlight for Windows Phone Toolkit 웹 페이지에서 .MSI 파일 확장명이 포함된 링크를 클릭하고 도구 키트를 설치합니다.

  3. 도구 키트가 설치된 후 도구 키트 이진 파일의 경로를 확인합니다. Windows 7에서는 시작, 모든 프로그램, Microsoft Silverlight for Windows Phone Toolkit이진 파일을 클릭하여 Windows 탐색기에서 폴더를 찾을 수 있습니다.

어셈블리 참조를 설정하려면

  1. Visual Studio에서 Silverlight for Windows Phone Toolkit 어셈블리를 프로젝트에 추가합니다. 프로젝트 메뉴에서 참조 추가를 클릭합니다.

  2. 참조 추가 창에서 찾아보기 탭을 클릭한 다음 Silverlight for Windows Phone Toolkit 이진 파일이 있는 폴더로 이동합니다.

  3. Microsoft.Phone.Controls.Toolkit.dll이라는 어셈블리를 선택하고 확인을 클릭합니다. 솔루션 탐색기에서 Microsoft.Phone.Controls.Toolkit 어셈블리가 참조 폴더에 표시됩니다.

  4. 이 응용프로그램은 Windows Phone 의 LINQ to SQL 어셈블리에 대한 참조가 필요합니다. 프로젝트 메뉴에서 참조 추가를 클릭하고 .NET 탭에서 System.Data.Linq를 선택한 다음 확인을 클릭합니다.

  5. Pivot 컨트롤을 사용하려면 이 응용프로그램에 Windows Phone 컨트롤 어셈블리에 대한 참조가 필요합니다. 프로젝트 메뉴에서 참조 추가를 클릭하고 .NET 탭에서 Microsoft.Phone.Controls를 선택한 다음 확인을 클릭합니다.

응용프로그램에서 사용되는 아이콘을 추가하려면

  1. 솔루션 탐색기에서 프로젝트를 마우스 오른쪽 버튼으로 클릭하고 추가, 새 폴더를 차례로 선택합니다.

  2. 새 폴더의 이름을 이미지로 지정합니다.

  3. 솔루션 탐색기에서 이미지 폴더를 마우스 오른쪽 버튼으로 클릭하고 추가, 기존 항목을 차례로 선택합니다. 응용프로그램에서 사용되는 아이콘을 선택할 수 있는 기존 항목 추가 메뉴가 열립니다.

  4. 기존 항목 추가 창에서 다음 경로 중 하나로 이동하여 아이콘을 선택합니다. 이 단계에서는 Visual Studio가 기본 위치에 설치되어 있다고 가정합니다. 다른 위치에 설치한 경우 해당 위치에서 아이콘을 찾습니다.

    • 64비트 운영 체제: C:\Program Files (x86)\Microsoft SDKs\Windows Phone\v7.1\Icons\dark

    • 32비트 운영 체제: C:\Program Files\Microsoft SDKs\Windows Phone\v7.1\Icons\dark

    폴더에서 다음 아이콘을 선택합니다.

    • appbar.add.rest.png

    • appbar.cancel.rest.png

    • appbar.check.rest.png

    • appbar.delete.rest.png

    이러한 아이콘은 어두운 배경에 맞게 디자인되었으며 흰색으로 되어 있습니다. 기존 항목 추가 창의 흰색 배경에서는 없는 것처럼 보일 수 있습니다.

  5. 솔루션 탐색기에서 각 아이콘을 마우스 오른쪽 버튼으로 클릭한 다음 아이콘이 콘텐츠로 빌드되고 항상 출력 디렉터리에 복사되도록(항상 복사) 파일 속성을 설정합니다.

이 단원에서는 이 응용프로그램의 기본 페이지 및 새 작업 페이지에 대한 UI를 만듭니다. MVVM 측면에서 응용프로그램 페이지는 뷰입니다. 기본 페이지는 Pivot 컨트롤을 사용하여 데이터를 표시하는 방법을 보여 줍니다. 새 작업 페이지는 Silverlight for Windows Phone Toolkit의 ListPicker 컨트롤을 사용하여 데이터를 선택하는 방법을 보여 줍니다.

기본 페이지 UI를 만들려면

  1. 응용프로그램의 기본 페이지 MainPage.xaml에서 다음 특성을 페이지 맨 위의 phone:PhoneApplicationPage 요소에 추가합니다.

    xmlns:controls="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls"
    

    Pivot 컨트롤을 사용하려면 이 네임스페이스가 필요합니다.

  2. MainPage.xaml에서 LayoutRoot라는 표 위에 다음 리소스 요소를 추가합니다.

        <phone:PhoneApplicationPage.Resources>
            <DataTemplate x:Key="ToDoListBoxItemTemplate">
    
                <Grid HorizontalAlignment="Stretch" Width="420">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="100" />
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="100" />
                    </Grid.ColumnDefinitions>
    
                    <CheckBox 
                        IsChecked="{Binding IsComplete, Mode=TwoWay}" 
                        Grid.Column="0" VerticalAlignment="Top"/>
    
                    <TextBlock 
                        Text="{Binding ItemName}" 
                        FontSize="{StaticResource PhoneFontSizeLarge}" 
                        Grid.Column="1" Grid.ColumnSpan="2" 
                        VerticalAlignment="Top" Margin="-36, 12, 0, 0"/>
    
                    <Button                                
                        Grid.Column="3"
                        x:Name="deleteTaskButton"
                        BorderThickness="0"                                                                  
                        Margin="0, -18, 0, 0"
                        Click="deleteTaskButton_Click">
    
                        <Image 
                        Source="/Images/appbar.delete.rest.png"
                        Height="75"
                        Width="75"/>
    
                    </Button>
                </Grid>
            </DataTemplate>
        </phone:PhoneApplicationPage.Resources>
    
    

    이 항목 템플릿은 각 피벗 페이지에서 다시 사용되어 로컬 데이터베이스의 데이터 한 행, 즉 할일 작업 하나를 표시합니다. 각 행에는 작업을 완료된 것으로 표시하는 CheckBox, 할일 작업 텍스트를 표시하는 TextBlock 및 작업을 삭제할 수 있는 Button이 있습니다. CheckBox는 작업의 IsCompleted 속성에 대해 양방향 바인딩으로 구성되어 있습니다. UI에서 작업을 완료된 것으로 확인하면 작업이 바인딩될 ViewModel의 해당 관찰 가능 컬렉션에서 새 값을 자동으로 캡처합니다. Textblock은 작업의 ItemName 속성에 바인딩됩니다.

  3. MainPage.xaml에서 LayoutRoot라는 표를 다음 코드로 바꿉니다. 이 코드는 이전 단계에서 추가한 리소스 요소 아래에 추가해야 합니다.

        <!--LayoutRoot is the root grid where all page content is placed.-->
        <Grid x:Name="LayoutRoot" Background="Transparent">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
    
            <!--TitlePanel contains the name of the application and page title.-->
            <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
                <TextBlock 
                    x:Name="ApplicationTitle" 
                    Text="LOCAL DATABASE EXAMPLE: TO-DO LIST" 
                    Style="{StaticResource PhoneTextNormalStyle}"/>
            </StackPanel>
    
            <!--ContentPanel - place additional content here.-->
            <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
                <controls:Pivot Margin="0, -36, 0, 0">
                    
                    <controls:PivotItem Header="all">
                        <ListBox 
                            x:Name="allToDoItemsListBox" 
                            ItemsSource="{Binding AllToDoItems}" 
                            Margin="12, 0, 12, 0" Width="440" 
                            ItemTemplate="{StaticResource ToDoListBoxItemTemplate}" />
                    </controls:PivotItem>
                    
                    <controls:PivotItem Header="home">
                        <ListBox 
                            x:Name="homeToDoItemsListBox" 
                            ItemsSource="{Binding HomeToDoItems}" 
                            Margin="12, 0, 12, 0" Width="440" 
                            ItemTemplate="{StaticResource ToDoListBoxItemTemplate}" />
                    </controls:PivotItem>
                    
                    <controls:PivotItem Header="work">
                        <ListBox 
                            x:Name="workToDoItemsListBox" 
                            ItemsSource="{Binding WorkToDoItems}" 
                            Margin="12, 0, 12, 0" Width="440" 
                            ItemTemplate="{StaticResource ToDoListBoxItemTemplate}" />
                    </controls:PivotItem>
                    
                    <controls:PivotItem Header="hobbies">
                        <ListBox
                            x:Name="hobbiesToDoItemsListBox" 
                            ItemsSource="{Binding HobbiesToDoItems}" 
                            Margin="12, 0, 12, 0" Width="440" 
                            ItemTemplate="{StaticResource ToDoListBoxItemTemplate}" />
                    </controls:PivotItem>
                    
                </controls:Pivot>
            </Grid>
        </Grid>
    
    

    이 표에서는 응용프로그램 제목과 Pivot 컨트롤을 지정합니다. Pivot 컨트롤에는 각각 할일 작업에 할당할 수 있는 네 가지 카테고리에 해당하는 all, home, workhobbies의 네 페이지가 포함되어 있습니다. 각 페이지는 ViewModel의 AllToDoItems, HomeToDoItems, WorkToDoItemsHobbiesToDoItems 관찰 가능 컬렉션에 각각 바인딩됩니다.

  4. MainPage.xaml에서 LayoutRoot라는 표 아래에 다음 코드를 추가합니다.

        <phone:PhoneApplicationPage.ApplicationBar>
            <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
                
                <shell:ApplicationBarIconButton 
                    IconUri="/Images/appbar.add.rest.png" 
                    Text="add" 
                    x:Name="newTaskAppBarButton" 
                    Click="newTaskAppBarButton_Click"/>
                
            </shell:ApplicationBar>
        </phone:PhoneApplicationPage.ApplicationBar>
    
    

    이 페이지는 응용프로그램 모음을 사용하여 데이터베이스에 할일 작업을 추가하기 위한 add 버튼을 표시합니다. 이 버튼에 해당하는 메서드는 이 항목의 뒷부분에서 생성됩니다.

새 작업 페이지 UI를 만들려면

  1. 솔루션 탐색기에서 프로젝트를 마우스 오른쪽 버튼으로 클릭하고 추가, 새 항목을 차례로 선택합니다.

  2. 새 항목 추가 창에서 Windows Phone 세로 페이지를 선택하고 파일 이름을 NewTaskPage.xaml로 지정합니다. 추가를 클릭합니다.

  3. NewTaskPage.xaml에서 다음 특성을 페이지 맨 위의 phone:PhoneApplicationPage 요소에 추가합니다.

    xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
    

    Silverlight for Windows Phone Toolkit의 ListPicker 컨트롤을 사용하려면 이 네임스페이스가 필요합니다.

  4. NewTaskPage.xaml에서 LayoutRoot라는 표를 다음 코드로 바꿉니다.

        <!--LayoutRoot is the root grid where all page content is placed.-->
        <Grid x:Name="LayoutRoot" Background="Transparent">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
    
            <!--TitlePanel contains the name of the application and page title.-->
            <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
                <TextBlock 
                    x:Name="ApplicationTitle" 
                    Text="LOCAL DATABASE EXAMPLE: TO-DO LIST" 
                    Style="{StaticResource PhoneTextNormalStyle}"/>
                <TextBlock 
                    x:Name="PageTitle" 
                    Text="new task" 
                    Margin="9,-7,0,0" 
                    Style="{StaticResource PhoneTextTitle1Style}"/>
            </StackPanel>
    
            <!--ContentPanel - place additional content here.-->
            <StackPanel x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
                <TextBlock Text="Name"/>
                <TextBox x:Name="newTaskNameTextBox"/>
                <TextBlock Text="Category"/>
    
                <toolkit:ListPicker
                    x:Name="categoriesListPicker"
                    ItemsSource="{Binding CategoriesList}"
                    DisplayMemberPath="Name">
                </toolkit:ListPicker>
            </StackPanel>
        </Grid>
    
    

    이 표에는 StackPanel 컨트롤 두 개가 있습니다. 첫 번째 컨트롤은 응용프로그램 및 페이지 제목을 지정합니다. 두 번째 StackPanel에는 데이터 입력 컨트롤이 포함되어 있습니다. TextBox은 새 할일 작업의 텍스트를 입력하는 데 사용되고 ListPicker 컨트롤은 작업의 카테고리를 지정하는 데 사용됩니다. ListPickerList 형식의 ViewModel 속성인 CategoriesList에 바인딩됩니다.

  5. NewTaskPage.xaml에서 LayoutRoot라는 표 아래에 다음 코드를 추가합니다.

        <phone:PhoneApplicationPage.ApplicationBar>
            <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
                
                <shell:ApplicationBarIconButton 
                    x:Name="appBarOkButton" 
                    IconUri="/Images/appbar.check.rest.png" 
                    Text="ok" 
                    Click="appBarOkButton_Click"/>
                
                <shell:ApplicationBarIconButton 
                    x:Name="appBarCancelButton" 
                    IconUri="/Images/appbar.cancel.rest.png" 
                    Text="cancel" 
                    Click="appBarCancelButton_Click"/>
                
            </shell:ApplicationBar>
        </phone:PhoneApplicationPage.ApplicationBar>
    
    

    이 페이지는 응용프로그램 모음을 사용하여 okcancel 버튼을 표시합니다. 이 버튼에 해당하는 메서드는 이 항목의 뒷부분에서 생성됩니다.

이 단원에서는 데이터베이스 테이블 및 연결을 나타내는 개체와 LINQ to SQL 데이터 컨텍스트를 만듭니다. 먼저 파일을 만들고 각 테이블에 대한 템플릿을 추가합니다. 각 테이블을 빌드아웃하고 데이터 컨텍스트를 만듭니다.

데이터 모델 파일을 준비하려면

  1. 솔루션 탐색기에서 프로젝트를 마우스 오른쪽 버튼으로 클릭하고 추가, 새 폴더를 차례로 선택합니다.

  2. 새 폴더의 이름을 모델로 지정합니다.

  3. 솔루션 탐색기에서 모델 폴더를 마우스 오른쪽 버튼으로 클릭하고 추가, 새 항목을 차례로 선택합니다.

  4. 새 항목 추가 창에서 코드 파일을 선택하고 파일 이름을 ToDoDataContext.cs로 지정합니다. 추가를 클릭합니다.

  5. ToDoDataContext.cs에서 다음 지시문과 데이터 모델 클래스가 포함될 네임스페이스를 추가합니다.

    using System;
    using System.ComponentModel;
    using System.Data.Linq;
    using System.Data.Linq.Mapping;
    
    namespace LocalDatabaseSample.Model
    {
    
    }
    
    
  6. ToDoDataContext.cs에서 다음 코드를 LocalDatabaseSample.Model 네임스페이스에 두 번 추가합니다. 각 테이블에 대한 다음 단계에서는 이 클래스의 이름이 바뀝니다.

    참고참고:

    다음 단계에서 클래스의 이름을 바꿀 때까지 Visual Studio에서 모호성 오류를 표시합니다.

        [Table]
        public class AddTableNameHere : INotifyPropertyChanged, INotifyPropertyChanging
        {
    
            //
            // TODO: Add columns and associations, as applicable, here.
            //
    
            // Version column aids update performance.
            [Column(IsVersion = true)]
            private Binary _version;
    
            #region INotifyPropertyChanged Members
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            // Used to notify that a property changed
            private void NotifyPropertyChanged(string propertyName)
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }
    
            #endregion
    
            #region INotifyPropertyChanging Members
    
            public event PropertyChangingEventHandler PropertyChanging;
    
            // Used to notify that a property is about to change
            private void NotifyPropertyChanging(string propertyName)
            {
                if (PropertyChanging != null)
                {
                    PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
                }
            }
    
            #endregion
        }
    
    
    

    이것은 로컬 데이터베이스 테이블을 나타내는 클래스인 엔터티의 기본 템플릿입니다. 대부분의 엔터티에 공통적으로 적용하는 것이 좋은 다음 코드 기능을 보여 주기 위해 지금은 열과 연결이 빠져 있습니다.

    • [table] 특성은 클래스가 데이터베이스 테이블을 나타내도록 지정합니다.

    • INotifyPropertyChanged 인터페이스는 변경 추적에 사용됩니다.

    • INotifyPropertyChanging 인터페이스는 변경 추적과 관련된 메모리 사용을 제한하는 데 유용합니다.

    • [Column(IsVersion = true)] 특성이 있는 Binary 버전 열은 테이블 업데이트 성능을 크게 향상시킵니다.

    참고참고:

    엔터티는 다른 엔터티에서 이벤트, 메서드, 속성 등의 CLR 개체 멤버를 상속받을 수 있습니다. 다른 엔터티의 LINQ to SQL 특성은 상속받을 수 없습니다. 예를 들어 INotifyPropertyChangedINotifyPropertyChanging을 구현하고 이름이 L2SEntity인 기본 클래스를 만들 수 있습니다. 다른 엔터티가 L2SEntity에서 이벤트와 메서드를 상속받을 수 있지만 LINQ to SQL은 [Table] 특성을 사용하여 명시적으로 표시된 엔터티만 인식합니다. L2SEntity 클래스 자체에 작성된 LINQ to SQL 특성은 다른 엔터티에 상속되지 않습니다.

  7. AddTableNameHere 클래스 중 하나의 이름을 ToDoItem으로 바꿉니다. 이 클래스는 할일 작업 정보를 저장합니다.

  8. 다른 AddTableNameHere 클래스의 이름을 ToDoCategory로 바꿉니다. 이 클래스는 카테고리 목록을 저장합니다.

ToDoItem 클래스를 빌드아웃하려면

  1. ToDoDataContext.cs에서 다음 코드를 ToDoItem 클래스에 추가합니다.

            // Define ID: private field, public property, and database column.
            private int _toDoItemId;
    
            [Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL Identity", CanBeNull = false, AutoSync = AutoSync.OnInsert)]
            public int ToDoItemId
            {
                get {return _toDoItemId;}
                set
                {
                    if (_toDoItemId != value)
                    {
                        NotifyPropertyChanging("ToDoItemId");
                        _toDoItemId = value;
                        NotifyPropertyChanged("ToDoItemId");
                    }
                }
            }
    
            // Define item name: private field, public property, and database column.
            private string _itemName;
    
            [Column]
            public string ItemName
            {
                get {return _itemName;}
                set
                {
                    if (_itemName != value)
                    {
                        NotifyPropertyChanging("ItemName");
                        _itemName = value;
                        NotifyPropertyChanged("ItemName");
                    }
                }
            }
    
            // Define completion value: private field, public property, and database column.
            private bool _isComplete;
    
            [Column]
            public bool IsComplete
            {
                get {return _isComplete;}
                set
                {
                    if (_isComplete != value)
                    {
                        NotifyPropertyChanging("IsComplete");
                        _isComplete = value;
                        NotifyPropertyChanged("IsComplete");
                    }
                }
            }
    
    

    이러한 필드와 속성은 세 개의 열(ToDoItemId, ItemNameIsCompleted)을 데이터베이스에 추가합니다. [Column] 특성은 LINQ to SQL 런타임에 속성이 데이터베이스 열을 나타냄을 식별합니다. 유효한 열 특성 설정의 전체 목록을 보려면 System.Data.Linq.Mapping.ColumnAttribute를 참조하십시오.

  2. ToDoDataContext.cs에서 다음 코드를 ToDoItem 클래스에 추가합니다.

            // Internal column for the associated ToDoCategory ID value
            [Column]
            internal int _categoryId;
    
            // Entity reference, to identify the ToDoCategory "storage" table
            private EntityRef<ToDoCategory> _category;
    
            // Association, to describe the relationship between this key and that "storage" table
            [Association(Storage = "_category", ThisKey = "_categoryId", OtherKey = "Id", IsForeignKey = true)]
            public ToDoCategory Category
            {
                get {return _category.Entity;}
                set
                {
                    NotifyPropertyChanging("Category");
                    _category.Entity = value;
    
                    if (value != null)
                    {
                        _categoryId = value.Id;
                    }
    
                    NotifyPropertyChanging("Category");
                }
            }
    
    

    이 코드는 ToDoItemToDoCategory 테이블 간의 연결을 정의합니다. private _categoryId 필드는 할일 항목에 해당하는 카테고리의 식별자를 저장합니다. _category 엔터티 참조는 이 테이블과 연결할 다른 테이블을 식별합니다. Category 연결은 get 도중 적절한 ToDoCategory 개체를 검색하고 set 도중 적절한 ToDoCategory 식별자 값을 _categoryId에 할당하는 작업을 처리합니다.

    private 엔터티 참조와 public 접근자를 통해 점 표기법을 사용하는 연결을 탐색할 수 있습니다. 또한 개체 수준의 변경 사항이 있을 때 데이터베이스 관계를 최신 상태로 유지할 수 있습니다.

ToDoCategory 클래스를 빌드아웃하려면

  1. ToDoDataContext.cs에서 다음 코드를 ToDoCategory 클래스에 추가합니다.

            // Define ID: private field, public property, and database column.
            private int _id;
    
            [Column(DbType = "INT NOT NULL IDENTITY", IsDbGenerated = true, IsPrimaryKey = true)]
            public int Id
            {
                get {return _id;}
                set
                {
                    NotifyPropertyChanging("Id");
                    _id = value;
                    NotifyPropertyChanged("Id");
                }
            }
    
            // Define category name: private field, public property, and database column.
            private string _name;
    
            [Column]
            public string Name
            {
                get {return _name;}
                set
                {
                    NotifyPropertyChanging("Name");
                    _name = value;
                    NotifyPropertyChanged("Name");
                }
            }
    
    

    이 코드는 카테고리의 IdName 속성을 정의합니다.

  2. ToDoDataContext.cs에서 다음 코드를 ToDoCategory 클래스에 추가합니다.

            // Define the entity set for the collection side of the relationship.
            private EntitySet<ToDoItem> _todos;
    
            [Association(Storage = "_todos", OtherKey = "_categoryId", ThisKey = "Id")]
            public EntitySet<ToDoItem> ToDos
            {
                get { return this._todos; }
                set { this._todos.Assign(value); }
            }
    
    
            // Assign handlers for the add and remove operations, respectively.
            public ToDoCategory()
            {
                _todos = new EntitySet<ToDoItem>(
                    new Action<ToDoItem>(this.attach_ToDo), 
                    new Action<ToDoItem>(this.detach_ToDo)
                    );
            }
    
            // Called during an add operation
            private void attach_ToDo(ToDoItem toDo)
            {
                NotifyPropertyChanging("ToDoItem");
                toDo.Category = this;
            }
    
            // Called during a remove operation
            private void detach_ToDo(ToDoItem toDo)
            {
                NotifyPropertyChanging("ToDoItem");
                toDo.Category = null;
            }
    
    

    이 코드는 ToDoItems 테이블에 대한 연결의 컬렉션 쪽을 정의합니다. EntitySet 생성자는 집합의 add 이벤트와 remove 이벤트에 대해 대리자를 사용합니다. 그러면 새 ToDoItem 개체를 만들 때 개체에 해당 카테고리를 명시적으로 설정하지 않은 경우에도 컬렉션에 추가된 새 ToDoItem 개체에 카테고리 정보가 제대로 설정됩니다.

LINQ to SQL 데이터 컨텍스트를 만들려면

  • ToDoDataContext.cs에서 다음 코드를 LocalDatabaseSample.Model 네임스페이스에 추가합니다.

        public class ToDoDataContext : DataContext
        {
            // Pass the connection string to the base class.
            public ToDoDataContext(string connectionString) : base(connectionString)
            { }
    
            // Specify a table for the to-do items.
            public Table<ToDoItem> Items;
    
            // Specify a table for the categories.
            public Table<ToDoCategory> Categories;
        }
    
    

    이 데이터 컨텍스트는 ItemsCategories라는 두 테이블을 지정합니다. Items 테이블은 각각 ToDoItem 클래스를 기반으로 하는 할일 작업 항목을 저장합니다. Categories 테이블은 각각 ToDoCategory 클래스를 기반으로 하는 할일 작업 카테고리를 저장합니다.

이 단원에서는 응용프로그램의 ViewModel을 만듭니다. ViewModel은 데이터베이스에서 작업을 수행하고 여러 관찰 가능 컬렉션과 목록을 통해 응용프로그램 페이지에 데이터를 제공합니다.

ViewModel 파일을 준비하려면

  1. 솔루션 탐색기에서 프로젝트를 마우스 오른쪽 버튼으로 클릭하고 추가, 새 폴더를 차례로 선택합니다.

  2. 새 폴더의 이름을 ViewModel로 지정합니다.

  3. 솔루션 탐색기에서 ViewModel 폴더를 마우스 오른쪽 버튼으로 클릭하고 추가, 새 항목을 차례로 선택합니다.

  4. 새 항목 추가 창에서 코드 파일을 선택하고 파일 이름을 ToDoViewModel.cs로 지정합니다. 추가를 클릭합니다.

  5. ToDoViewModel.cs에서 다음 코드를 추가합니다.

    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Linq;
    
    // Directive for the data model.
    using LocalDatabaseSample.Model;
    
    
    namespace LocalDatabaseSample.ViewModel
    {
        public class ToDoViewModel : INotifyPropertyChanged
        {
            // LINQ to SQL data context for the local database.
            private ToDoDataContext toDoDB;
    
            // Class constructor, create the data context object.
            public ToDoViewModel(string toDoDBConnectionString)
            {
                toDoDB = new ToDoDataContext(toDoDBConnectionString);
            }
    
            //
            // TODO: Add collections, list, and methods here.
            //
    
            // Write changes in the data context to the database.
            public void SaveChangesToDB()
            {
                toDoDB.SubmitChanges();
            }
    
            #region INotifyPropertyChanged Members
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            // Used to notify Silverlight that a property has changed.
            private void NotifyPropertyChanged(string propertyName)
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }
            #endregion
        }
    }
    
    

    이것은 ViewModel의 템플릿입니다. LocalDatabaseSample.Model 네임스페이스에 대한 지시문을 사용하여 이전 단원에서 만든 LINQ to SQL 데이터 모델을 참조합니다. ViewModel은 변경 추적을 위한 INotifyPropertyChanged 인터페이스를 구현합니다.

    ViewModel의 핵심 기능은 로컬 데이터베이스에서 작업을 수행하는 것입니다. LINQ to SQL 데이터 컨텍스트인 toDoDB는 ViewModel 전체에서 참조되며 ViewModel의 생성자에서 생성됩니다. SaveChangesToDB 메서드는 일반 용도의 저장 메커니즘을 뷰에 제공합니다.

관찰 가능 컬렉션과 목록을 만들려면

  • ToDoViewModel.cs에서 다음 코드를 ToDoViewModel 클래스에 추가합니다.

            // All to-do items.
            private ObservableCollection<ToDoItem> _allToDoItems;
            public ObservableCollection<ToDoItem> AllToDoItems
            {
                get { return _allToDoItems; }
                set
                {
                    _allToDoItems = value;
                    NotifyPropertyChanged("AllToDoItems");
                }
            }
    
            // To-do items associated with the home category.
            private ObservableCollection<ToDoItem> _homeToDoItems;
            public ObservableCollection<ToDoItem> HomeToDoItems
            {
                get { return _homeToDoItems; }
                set
                {
                    _homeToDoItems = value;
                    NotifyPropertyChanged("HomeToDoItems");
                }
            }
    
            // To-do items associated with the work category.
            private ObservableCollection<ToDoItem> _workToDoItems;
            public ObservableCollection<ToDoItem> WorkToDoItems
            {
                get { return _workToDoItems; }
                set
                {
                    _workToDoItems = value;
                    NotifyPropertyChanged("WorkToDoItems");
                }
            }
    
            // To-do items associated with the hobbies category.
            private ObservableCollection<ToDoItem> _hobbiesToDoItems;
            public ObservableCollection<ToDoItem> HobbiesToDoItems
            {
                get { return _hobbiesToDoItems; }
                set
                {
                    _hobbiesToDoItems = value;
                    NotifyPropertyChanged("HobbiesToDoItems");
                }
            }
    
            // A list of all categories, used by the add task page.
            private List<ToDoCategory> _categoriesList;
            public List<ToDoCategory> CategoriesList
            {
                get { return _categoriesList; }
                set
                {
                    _categoriesList = value;
                    NotifyPropertyChanged("CategoriesList");
                }
            }
    
    

    이 코드는 기본 페이지에서 Pivot 컨트롤이 사용하는 관찰 가능 컬렉션(AllToDoItems, HomeToDoItems, WorkToDoItemsHobbiesToDoItems)을 지정합니다. 또한 새 작업 페이지에서 ListPicker 컨트롤이 사용하는 CategoriesList 목록을 지정합니다.

컬렉션과 목록을 로드하려면

  • ToDoViewModel.cs에서 다음 코드를 ToDoViewModel 클래스에 추가합니다.

        // Query database and load the collections and list used by the pivot pages.
        public void LoadCollectionsFromDatabase()
        {
    
            // Specify the query for all to-do items in the database.
            var toDoItemsInDB = from ToDoItem todo in toDoDB.Items
                                select todo;
    
            // Query the database and load all to-do items.
            AllToDoItems = new ObservableCollection<ToDoItem>(toDoItemsInDB);
    
            // Specify the query for all categories in the database.
            var toDoCategoriesInDB = from ToDoCategory category in toDoDB.Categories
                                    select category;
    
                
            // Query the database and load all associated items to their respective collections.
            foreach (ToDoCategory category in toDoCategoriesInDB)
            {
                switch (category.Name)
                {
                    case "Home":
                        HomeToDoItems = new ObservableCollection<ToDoItem>(category.ToDos);
                        break;
                    case "Work":
                        WorkToDoItems = new ObservableCollection<ToDoItem>(category.ToDos);
                        break;
                    case "Hobbies":
                        HobbiesToDoItems = new ObservableCollection<ToDoItem>(category.ToDos);
                        break;
                    default:
                        break;
                }
            }
    
            // Load a list of all categories.
            CategoriesList = toDoDB.Categories.ToList(); 
    
        }
    
    

    이 코드에서 ViewModel은 컬렉션과 목록에 로컬 데이터베이스의 데이터를 로드합니다. 지연 로드를 사용하므로 관찰 가능 컬렉션을 인스턴스화할 때까지 실제로 데이터베이스가 쿼리되지 않습니다.

    참고참고:

    foreachswitch 문은 EntitySet 개체를 이용하여 특정 카테고리와 연결된 ToDoItem 개체를 모두 검색하는 방법을 보여 주기 위한 것입니다. 이 특정 메서드에서는 세 가지 표준 LINQ 쿼리를 통해서도 동일한 결과를 얻을 수 있습니다.

데이터베이스에서 추가 및 제거 작업을 수행하려면

  1. ToDoViewModel.cs에서 다음 코드를 ToDoViewModel 클래스에 추가합니다.

            // Add a to-do item to the database and collections.
            public void AddToDoItem(ToDoItem newToDoItem)
            {
                // Add a to-do item to the data context.
                toDoDB.Items.InsertOnSubmit(newToDoItem);
    
                // Save changes to the database.
                toDoDB.SubmitChanges();
    
                // Add a to-do item to the "all" observable collection.
                AllToDoItems.Add(newToDoItem);
    
                // Add a to-do item to the appropriate filtered collection.
                switch (newToDoItem.Category.Name)
                {
                    case "Home":
                        HomeToDoItems.Add(newToDoItem);
                        break;
                    case "Work":
                        WorkToDoItems.Add(newToDoItem);
                        break;
                    case "Hobbies":
                        HobbiesToDoItems.Add(newToDoItem);
                        break;
                    default:
                        break;
                }
            }
    
    

    이 메서드는 새 할일 항목인 작업을 응용프로그램에 추가하기 위해 호출됩니다. 할일 항목이 추가되면 여러 항목을 업데이트해야 합니다. 먼저 LINQ to SQL 데이터 컨텍스트인 toDoDB를 업데이트합니다. SubmitChanges 메서드를 호출하여 변경 사항을 데이터베이스에 저장합니다. 최종적으로, Add 메서드를 호출하여 해당하는 관찰 가능 컬렉션을 업데이트합니다.

  2. ToDoViewModel.cs에서 다음 코드를 ToDoViewModel 클래스에 추가합니다.

            // Remove a to-do task item from the database and collections.
            public void DeleteToDoItem(ToDoItem toDoForDelete)
            {
    
                // Remove the to-do item from the "all" observable collection.
                AllToDoItems.Remove(toDoForDelete);
    
                // Remove the to-do item from the data context.
                toDoDB.Items.DeleteOnSubmit(toDoForDelete);
    
                // Remove the to-do item from the appropriate category.   
                switch (toDoForDelete.Category.Name)
                {
                    case "Home":
                        HomeToDoItems.Remove(toDoForDelete);
                        break;
                    case "Work":
                        WorkToDoItems.Remove(toDoForDelete);
                        break;
                    case "Hobbies":
                        HobbiesToDoItems.Remove(toDoForDelete);
                        break;
                    default:
                        break;
                }
    
                // Save changes to the database.
                toDoDB.SubmitChanges();                
            }
    
    

    이 메서드는 할일 항목인 작업을 응용프로그램에서 제거하기 위해 호출됩니다. 항목 제거 순서는 추가할 때와 반대입니다. 먼저 Remove 메서드가 AllToDoItems 관찰 가능 컬렉션에서 항목을 제거합니다. DeleteOnSubmit 메서드가 LINQ to SQL 데이터 컨텍스트에서 항목을 제거합니다. 그 후에 해당하는 필터링된 컬렉션에서 항목이 제거됩니다. 최종적으로, SubmitChanges 메서드를 사용하여 로컬 데이터베이스에서 항목을 제거하고 다른 변경 사항을 저장합니다.

이 단원에서는 이전 단원에서 만든 모든 부분을 연결합니다. 먼저 정적 ViewModel을 지정하고 로컬 데이터베이스를 만들도록 응용프로그램 클래스를 수정합니다. 그런 다음 ViewModel을 사용하고 버튼 클릭을 처리하도록 페이지의 코드 숨김 파일을 수정합니다.

App 클래스를 완성하려면

  1. App.xaml.cs 파일에서 다음 지시문을 페이지 맨 위에 추가합니다.

    // Directives
    using LocalDatabaseSample.Model;
    using LocalDatabaseSample.ViewModel;
    
    
  2. App.xaml.cs 파일에서 App 클래스 내의 클래스 생성자 위에 다음 코드를 추가합니다.

            // The static ViewModel, to be used across the application.
            private static ToDoViewModel viewModel;
            public static ToDoViewModel ViewModel
            {
                get { return viewModel; }
            }
    
    

    이 코드는 응용프로그램 전체에서 각 페이지에 사용할 수 있는 정적 ViewModel을 지정합니다.

  3. App.xaml.cs 파일에서 App 클래스의 클래스 생성자 끝에 다음 코드를 추가합니다.

        // Specify the local database connection string.
        string DBConnectionString = "Data Source=isostore:/ToDo.sdf";
    
        // Create the database if it does not exist.
        using (ToDoDataContext db = new ToDoDataContext(DBConnectionString))
        {
            if (db.DatabaseExists() == false)
            {
                // Create the local database.
                db.CreateDatabase();
    
                // Prepopulate the categories.
                db.Categories.InsertOnSubmit(new ToDoCategory { Name = "Home" });
                db.Categories.InsertOnSubmit(new ToDoCategory { Name = "Work" });
                db.Categories.InsertOnSubmit(new ToDoCategory { Name = "Hobbies" });
    
                // Save categories to the database.
                db.SubmitChanges();
            }
        }
    
        // Create the ViewModel object.
        viewModel = new ToDoViewModel(DBConnectionString);
    
        // Query the local database and load observable collections.
        viewModel.LoadCollectionsFromDatabase();
    
    

    응용프로그램 개체를 인스턴스화할 때 로컬 데이터베이스가 없으면 새로 생성됩니다. 데이터베이스를 만들 때 ToDoCategory 테이블에 세 개의 카테고리가 미리 채워집니다. 데이터베이스가 있으면 ViewModel 개체가 생성되고 데이터가 로드됩니다.

MainPage 클래스를 완성하려면

  1. 기본 페이지의 코드 숨김 파일인 MainPage.xaml.cs에서 다음 지시문을 추가합니다.

    // Directive for the ViewModel.
    using LocalDatabaseSample.Model;
    
    
  2. MainPage.xaml.cs에서 MainPage 생성자의 InitializeComponent 메서드 호출 뒤에 다음 코드를 추가합니다.

                // Set the page DataContext property to the ViewModel.
                this.DataContext = App.ViewModel;
    
    

    이 코드는 페이지의 DataContext 속성을 ViewModel로 설정합니다. 페이지 DataContext는 LINQ to SQL 데이터 컨텍스트와 다릅니다. MVVM 측면에서 페이지 DataContext는 뷰의 속성이고, LINQ to SQL 데이터 컨텍스트는 모델을 정의합니다.

  3. MainPage.xaml.cs에서 다음 코드를 MainPage 클래스에 추가합니다.

            private void newTaskAppBarButton_Click(object sender, EventArgs e)
            {
                NavigationService.Navigate(new Uri("/NewTaskPage.xaml", UriKind.Relative));
            }
    
    
            private void deleteTaskButton_Click(object sender, RoutedEventArgs e)
            {
                // Cast the parameter as a button.
                var button = sender as Button;
    
                if (button != null)
                {
                    // Get a handle for the to-do item bound to the button.
                    ToDoItem toDoForDelete = button.DataContext as ToDoItem;
    
                    App.ViewModel.DeleteToDoItem(toDoForDelete);
                }
    
                // Put the focus back to the main page.
                this.Focus();
            }
    
            protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
            {
                // Save changes to the database.
                App.ViewModel.SaveChangesToDB();    
            }
    
    

    이 코드는 페이지의 이벤트 처리 코드를 보여 줍니다. 새 작업 버튼을 클릭하면 NavigationService 개체를 사용하여 새 작업 페이지로 이동합니다. 삭제 버튼을 호출하면 해당하는 ToDoItem 개체가 검색되어 ViewModel의 DeleteToDoItem 메서드에 전송됩니다. 사용자가 페이지를 벗어난 부분을 탐색할 때마다 데이터 컨텍스트의 변경 사항이 SaveChangesToDB 메서드를 사용하여 자동으로 로컬 데이터베이스에 저장됩니다.

NewTaskPage 클래스를 완성하려면

  1. 새 작업 페이지의 코드 숨김 파일인 NewTaskPage.xaml.cs에서 다음 지시문을 추가합니다.

    // Directive for the data model.
    using LocalDatabaseSample.Model;
    
    
  2. NewTaskPage.xaml.cs에서 NewTaskPage 생성자의 InitializeComponent 메서드 호출 뒤에 다음 코드를 추가합니다.

                // Set the page DataContext property to the ViewModel.
                this.DataContext = App.ViewModel;
    
    

    이 코드는 페이지의 DataContext 속성을 ViewModel로 설정합니다. 페이지 DataContext는 LINQ to SQL 데이터 컨텍스트와 다릅니다. MVVM 측면에서 페이지 DataContext는 뷰의 속성이고, LINQ to SQL 데이터 컨텍스트는 모델을 정의합니다.

  3. NewTaskPage.xaml.cs에서 다음 코드를 NewTaskPage 클래스에 추가합니다.

            private void appBarOkButton_Click(object sender, EventArgs e)
            {
                // Confirm there is some text in the text box.
                if (newTaskNameTextBox.Text.Length > 0)
                {
                    // Create a new to-do item.
                    ToDoItem newToDoItem = new ToDoItem
                    {
                        ItemName = newTaskNameTextBox.Text,
                        Category = (ToDoCategory)categoriesListPicker.SelectedItem
                    };
    
                    // Add the item to the ViewModel.
                    App.ViewModel.AddToDoItem(newToDoItem);
    
                    // Return to the main page.
                    if (NavigationService.CanGoBack)
                    {
                        NavigationService.GoBack();
                    }
                }
            }
    
            private void appBarCancelButton_Click(object sender, EventArgs e)
            {
                // Return to the main page.
                if (NavigationService.CanGoBack)
                {
                    NavigationService.GoBack();
                }
            }
    
    

    appBarOkButton_Click 메서드는 작업에 대해 사소한 유효성 검사를 수행하고 작업을 ViewModel에 추가합니다.

  4. 이제 응용프로그램이 완성되었습니다. F5 키를 눌러 디버깅을 시작하고 응용프로그램을 테스트합니다.

표시: