방법: Windows Phone용 기본 로컬 데이터베이스 응용프로그램 만들기

2012-02-09

Windows Phone OS 7.1 에서는 응용프로그램의 격리된 저장소 컨테이너에 있는 로컬 데이터베이스에 관계형 데이터를 저장할 수 있습니다. 이 항목에서는 로컬 데이터베이스를 사용하여 데이터를 저장하는 기본 단일 페이지 할일 목록 응용프로그램을 만드는 과정을 안내합니다. Windows Phone 응용프로그램에서 로컬 데이터베이스를 사용하는 방법에 대한 자세한 내용은 Windows Phone의 로컬 데이터베이스 개요를 참조하십시오.

팁팁:

MVVM(Model-View-ViewModel) 패턴을 사용하는 동시에 추가 데이터베이스 테이블과 응용프로그램 페이지, Pivot 컨트롤 및 Silverlight for Windows Phone Toolkit를 사용하여 이 응용프로그램을 구현하려면 방법: Windows Phone의 로컬 데이터베이스 응용프로그램(MVVM 포함) 만들기를 참조하십시오.

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

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

  2. 데이터 컨텍스트 빌드

  3. 데이터베이스 만들기

  4. 응용프로그램 완성

다음 이미지는 개발자 지망생을 위해 응용프로그램이 어떻게 표시될 수 있는지의 예를 보여 줍니다.

AP_Con_IStorage_DB2

텍스트 상자에 텍스트를 입력하고 추가를 터치하면 응용프로그램의 목록에 "할일" 항목이 추가됩니다. 삭제 아이콘을 터치하면 목록에서 항목이 제거됩니다. 할일 항목은 로컬 데이터베이스에 저장되기 때문에 응용프로그램 시작 간에 목록의 값이 유지됩니다.

참고참고:

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

이 단원에서는 응용프로그램 프로젝트를 만들고 사용자 인터페이스를 준비합니다.

응용프로그램 UI를 만들려면

  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 디자이너 창에서 열립니다.

  7. 프로젝트 메뉴에서 기존 항목 추가를 선택합니다. 할일 항목 삭제 아이콘을 선택할 수 있는 기존 항목 추가 메뉴가 열립니다.

  8. 기존 항목 추가 창에서 다음 경로 중 하나로 이동하여 appbar.delete.rest.png 아이콘을 선택합니다. 이러한 아이콘은 어두운 배경에 맞게 디자인되었으며 흰색으로 되어 있습니다. 기존 항목 추가 창의 흰색 배경에서는 없는 것처럼 보일 수 있습니다.

    • 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

    참고참고:

    이 단계에서는 Visual Studio가 기본 위치에 설치되어 있다고 가정합니다. 다른 위치에 설치한 경우 해당 위치에서 아이콘을 찾습니다.

  9. 추가를 클릭합니다. Solution Explorer의 목록에 아이콘이 추가됩니다.

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

  11. MainPage.xaml에서 Grid의 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" Style="{StaticResource PhoneTextNormalStyle}"/>
                <TextBlock x:Name="PageTitle" Text="to-do list" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
            </StackPanel>
    
            <!--ContentPanel - place additional content here.-->
            <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>
                
                <!-- Bind the list box to the observable collection. -->
                <ListBox x:Name="toDoItemsListBox" ItemsSource="{Binding ToDoItems}" 
                         Grid.Row="0" Margin="12, 0, 12, 0" Width="440">
                    <ListBox.ItemTemplate>
                        <DataTemplate>
                            <Grid HorizontalAlignment="Stretch" Width="440">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="50" />
                                    <ColumnDefinition Width="*" />                               
                                    <ColumnDefinition Width="100" />                                
                                </Grid.ColumnDefinitions>
                                <CheckBox
                                    IsChecked="{Binding IsComplete, Mode=TwoWay}"
                                    Grid.Column="0"
                                    VerticalAlignment="Center"/>
                                <TextBlock
                                    Text="{Binding ItemName}"
                                    FontSize="{StaticResource PhoneFontSizeLarge}"
                                    Grid.Column="1"
                                    VerticalAlignment="Center"/>
                                <Button
                                    Grid.Column="2"
                                    x:Name="deleteTaskButton"
                                    BorderThickness="0"                                
                                    Margin="0"
                                    Click="deleteTaskButton_Click">
                                    <Image Source="appbar.delete.rest.png"/>                                
                                </Button>
                            </Grid>
                        </DataTemplate>
                    </ListBox.ItemTemplate>
                </ListBox>      
                
                <Grid Grid.Row="1">                
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="Auto" />
                    </Grid.ColumnDefinitions>
                    <TextBox
                        x:Name="newToDoTextBox"                    
                        Grid.Column="0"
                        Text="add new task"
                        FontFamily="{StaticResource PhoneFontFamilyLight}"                    
                        GotFocus="newToDoTextBox_GotFocus"/>
                    <Button 
                        Content="add"
                        Grid.Column="1"
                        x:Name="newToDoAddButton"
                        Click="newToDoAddButton_Click"/>                
                </Grid>
            </Grid>
        </Grid>
    
    

    XAML 코드가 Grid 요소 두 개를 응용프로그램에 추가합니다. 한 개의 표에는 할일 항목을 표시하는 toDoItemsListBox가 포함됩니다. 할일 항목이 추가로 목록에 바인딩되면 ListBox의 크기가 증가하여 두 번째 표가 화면 아래로 내려갑니다. 다른 Grid 요소에는 새 할일 항목을 입력하기 위한 newToDoTextBoxButton이 포함됩니다.

이 단원에서는 데이터베이스 스키마를 결정하는 개체 모델을 지정하고 데이터 컨텍스트를 만듭니다.

데이터 컨텍스트를 빌드하려면

  1. 기본 페이지의 코드 숨김 파일인 MainPage.xaml.cs를 엽니다. 이 페이지에는 응용프로그램 논리가 대부분 포함됩니다. 단순한 구현을 위해 이 응용프로그램은 의도적으로 단일 페이지로 유지되었습니다. 실제 응용프로그램은 대체로 MVVM(Model-View–ViewModel) 프로그래밍 패턴을 사용합니다. 자세한 내용은 Windows Phone 응용프로그램에서 MVVM(Model-View-ViewModel) 패턴 구현을 참조하십시오.

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

  3. 페이지의 맨 위에서 다음 지시문을 추가합니다.

    using System.Data.Linq;
    using System.Data.Linq.Mapping;
    using System.ComponentModel;
    using System.Collections.ObjectModel;
    
    
  4. MainPage 클래스 아래에 다음 코드를 추가합니다. 이것은 로컬 데이터베이스에 있는 응용프로그램의 데이터베이스 테이블을 나타내는 ToDoItem이라는 엔터티 클래스입니다. 이 클래스는 변경 추적을 위한 INotifyPropertyChanged를 구현합니다. INotifyPropertyChanging을 구현하면 변경 추적과 관련된 메모리 사용을 제한할 수 있습니다. 테이블 특성 [Table]은 LINQ to SQL 런타임에 클래스를 로컬 데이터베이스 테이블에 매핑하도록 지정합니다.

    [Table]
    public class ToDoItem : INotifyPropertyChanged, INotifyPropertyChanging
    {
    // 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");
            }
        }
    }
    // Version column aids update performance.
    [Column(IsVersion = true)]
    private Binary _version;
    
    #region INotifyPropertyChanged Members
    
    public event PropertyChangedEventHandler PropertyChanged;
    
    // Used to notify the page that a data context 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 the data context that a data context property is about to change
    private void NotifyPropertyChanging(string propertyName)
    {
        if (PropertyChanging != null)
        {
            PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
        }
    }
    
    #endregion
    }
    
    

    ToDoItem 클래스에는 세 개의 데이터베이스 열에 해당하는 public 속성 세 개가 있습니다.

    • ToDoItemId: 데이터베이스에서 자동으로 채우는 식별자 열입니다. 이 열은 데이터베이스 인덱스가 자동으로 생성되는 기본 키이기도 합니다. 이러한 설정과 기타 설정은 속성 구문 위에 작성된 LINQ to SQL Column 매핑 특성을 사용하여 지정됩니다.

    • ItemName: 할일 항목의 텍스트를 저장할 열입니다.

    • IsComplete: 할일 항목의 완료 상태를 저장할 열입니다.

    중요중요:

    변경 추적에 필요한 메모리 사용을 제한하려면 데이터 컨텍스트에서 항상 개체에 대한 INotifyPropertyChanging 인터페이스를 구현합니다.

  5. MainPage 클래스 아래에 다음 코드를 추가합니다. 이 클래스는 이름이 ToDoDataContext이며, DataContext에서 상속받아 데이터 컨텍스트라고도 합니다. 무엇보다도, 이 코드는 기본 생성자를 호출하고 ToDoItems라는 데이터베이스 테이블을 선언합니다.

    public class ToDoDataContext : DataContext
    {
        // Specify the connection string as a static, used in main page and app.xaml.
        public static string DBConnectionString = "Data Source=isostore:/ToDo.sdf";
    
        // Pass the connection string to the base class.
        public ToDoDataContext(string connectionString)
            : base(connectionString)
        { }
    
        // Specify a single table for the to-do items.
        public Table<ToDoItem> ToDoItems;
    }
    
    
    참고참고:

    데이터베이스 연결 문자열은 정적 필드가 아니어도 되며, 이 특정 예제에서 편의상 사용된 것입니다. 연결 문자열에 대한 자세한 내용은 Windows Phone의 로컬 데이터베이스 연결 문자열을 참조하십시오.

이 단원에서는 데이터베이스가 없는 경우 새로 만드는 코드를 추가합니다.

데이터베이스를 만들려면

  1. 응용프로그램의 코드 숨김 파일(App.xaml.cs)을 엽니다. 이 파일에는 응용프로그램 개체에 대한 코드가 들어 있습니다.

  2. 응용프로그램 개체의 생성자(App())에서 메서드 끝에 다음 코드를 추가합니다. 이 데이터베이스 생성 코드는 기본 페이지의 코드가 실행되기 전에 데이터베이스가 존재하도록 하기 위해 여기에 추가됩니다.

    // Create the database if it does not exist.
    using (ToDoDataContext db = new ToDoDataContext(ToDoDataContext.DBConnectionString))
    {
        if (db.DatabaseExists() == false)
        {
            //Create the database
            db.CreateDatabase();
        }
    }
    
    

    ToDoDataContext 클래스의 생성자에서 연결 문자열의 정적 필드는 편의상 사용된 것입니다. 이것은 값 "Data Source=isostore:/ToDo.sdf"를 생성자에 전달하는 것과 같습니다.

이 단원에서는 응용프로그램 코드의 나머지 부분을 완성합니다.

응용프로그램을 완성하려면

  1. 기본 페이지의 코드 숨김 파일인 MainPage.xaml.cs를 엽니다. 이 파일에는 응용프로그램 코드의 나머지 부분이 포함됩니다.

  2. MainPage 클래스를 다음 코드로 바꿉니다. 데이터 바인딩을 지원하기 위해 이 코드는 MainPage 클래스 및 해당 INotifyPropertyChanged 멤버의 서명에 INotifyPropertyChanged 인터페이스를 추가합니다.

    public partial class MainPage : PhoneApplicationPage, INotifyPropertyChanged
    {
    
        // Constructor
        public MainPage()
        {
            InitializeComponent();
        }
    
    
        #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
    }
    
    
  3. MainPage 클래스에서 다음 코드를 클래스 생성자 위에 추가합니다. 이 코드는 toDoDB라는 private 데이터 컨텍스트와 public 속성 ObservableCollection(ToDoItems)을 선언합니다. ToDoItems는 데이터를 UI에 바인딩하는 데 사용됩니다.

    // Data context for the local database
    private ToDoDataContext toDoDB;
    
    // Define an observable collection property that controls can bind to.
    private ObservableCollection<ToDoItem> _toDoItems;
    public ObservableCollection<ToDoItem> ToDoItems
    {
        get
        {
            return _toDoItems;
        }
        set
        {
            if (_toDoItems != value)
            {
                _toDoItems = value;
                NotifyPropertyChanged("ToDoItems");
            }
        }
    }
    
    
  4. MainPage 클래스 생성자에서 다음 코드를 InitializeComponent 호출 아래에 추가합니다. 이 코드는 정적 연결 문자열 DBConnectionString을 다시 사용하여 (로컬 데이터베이스) 데이터 컨텍스트를 인스턴스화하고 기본 페이지를 (페이지) 데이터 컨텍스트의 루트로 설정합니다.

    // Connect to the database and instantiate data context.
    toDoDB = new ToDoDataContext(ToDoDataContext.DBConnectionString);
                
    // Data context and observable collection are children of the main page.
    this.DataContext = this;
    
    
  5. MainPage 클래스에서 다음 코드를 클래스 생성자 아래에 추가합니다. 페이지를 탐색할 때 이 코드는 로컬 데이터베이스를 쿼리하고 ToDoItems 컬렉션에 결과를 채웁니다. XAML에 구성된 데이터 바인딩 때문에 toDoItemsListBox 컨트롤에 해당 할일 항목이 자동으로 채워집니다.

    protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
    {
    // Define the query to gather all of the to-do items.
    var toDoItemsInDB = from ToDoItem todo in toDoDB.ToDoItems
                        select todo;
    
    // Execute the query and place the results into a collection.
    ToDoItems = new ObservableCollection<ToDoItem>(toDoItemsInDB);
                
        // Call the base method.
        base.OnNavigatedTo(e);
    }
    
    
  6. MainPage 클래스에서 다음 코드를 클래스 생성자 아래에 추가합니다. 이 코드는 할일 항목을 데이터베이스에 추가하는 데 사용됩니다. newToDoTextBox_GotFocus는 사용자가 새 할일 항목에 대한 텍스트를 입력하기 전에 텍스트 상자를 지웁니다. newToDoAddButton_Click은 새 ToDoItem 개체를 만들고 ToDoItems 컬렉션과 toDoDB 데이터 컨텍스트에 추가합니다. 페이지를 벗어난 부분을 탐색하고 SubmitChanges 메서드를 호출할 때까지는 새 할일 항목이 실제로 로컬 데이터베이스에 추가되지 않습니다.

    private void newToDoTextBox_GotFocus(object sender, RoutedEventArgs e)
    {
        // Clear the text box when it gets focus.
        newToDoTextBox.Text = String.Empty;
    }
    
    private void newToDoAddButton_Click(object sender, RoutedEventArgs e)
    {
        // Create a new to-do item based on the text box.
        ToDoItem newToDo = new ToDoItem { ItemName = newToDoTextBox.Text };
    
        // Add a to-do item to the observable collection.
        ToDoItems.Add(newToDo);
                
        // Add a to-do item to the local database.
        toDoDB.ToDoItems.InsertOnSubmit(newToDo);          
    }
    
    protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
    {
        // Call the base method.
        base.OnNavigatedFrom(e);
    
        // Save changes to the database.
        toDoDB.SubmitChanges();
    }
    
    
    중요중요:

    SubmitChanges 메서드를 호출할 때까지는 데이터 컨텍스트에 추가된 새 항목이 데이터베이스에 저장되지 않습니다.

  7. MainPage 클래스에서 다음 코드를 클래스 생성자 아래에 추가합니다. 이 코드는 사용자가 삭제 아이콘을 누를 때 호출됩니다. 코드를 실행하면 ToDoItems 컬렉션과 toDoDB 데이터 컨텍스트에서 해당 할일 항목이 제거됩니다. SubmitChanges 메서드를 호출할 때까지는 삭제된 할일 항목이 실제로 로컬 데이터베이스에서 제거되지 않습니다.

    private void deleteTaskButton_Click(object sender, RoutedEventArgs e)
    {
        // Cast 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;
    
            // Remove the to-do item from the observable collection.
            ToDoItems.Remove(toDoForDelete);
    
            // Remove the to-do item from the local database.
            toDoDB.ToDoItems.DeleteOnSubmit(toDoForDelete);
    
            // Save changes to the database.
            toDoDB.SubmitChanges();
    
            // Put the focus back to the main page.
            this.Focus();
        }
    }
    
    
    중요중요:

    SubmitChanges 메서드를 호출할 때까지는 데이터 컨텍스트에서 제거된 항목이 데이터베이스에서 제거되지 않습니다.

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

표시: