다음을 통해 공유


WPF 시작

이 단계별 연습은 POCO 유형을 "주-세부 정보" 양식의 WPF 컨트롤에 바인딩하는 방법을 보여 줍니다. 이 애플리케이션은 Entity Framework API를 사용하여 데이터베이스의 데이터로 개체를 채우고, 변경 내용을 추적하고, 데이터를 데이터베이스에 유지합니다.

이 모델은 일대다 관계에 참여하는 범주(principal\main)와 제품(dependent\detail)의 두 가지 형식을 정의합니다. WPF 데이터 바인딩 프레임워크를 사용하면 관련 개체 간 탐색이 가능합니다. 즉, 마스터 보기에서 행을 선택하면 세부 정보 보기가 해당 자식 데이터로 업데이트됩니다.

이 연습의 스크린샷 및 코드 목록은 Visual Studio 2019 16.6.5에서 가져온 것입니다.

GitHub에서 이 문서의 샘플을 볼 수 있습니다.

필수 구성 요소

이 연습을 완료하려면 Visual Studio 2019 16.3 이상이 설치되어 있고 .NET 데스크톱 워크로드가 선택되어야 합니다. 최신 버전의 Visual Studio 설치에 대한 자세한 내용은 Visual Studio 설치를 참조하세요.

애플리케이션 만들기

  1. Visual Studio를 엽니다.
  2. 시작 창에서 새 프로젝트 만들기를 선택합니다.
  3. "WPF"를 검색하고 WPF 앱(.NET Core) 를 선택한 후 다음을 선택합니다.
  4. 다음 화면에서 프로젝트의 이름(예: GetStartedWPF)을 지정하고 만들기를 선택합니다.

Entity Framework NuGet 패키지 설치

  1. 솔루션을 마우스 오른쪽 단추로 클릭하고 솔루션용 NuGet 패키지 관리를 선택합니다.

    Manage NuGet Packages

  2. 검색 상자에 entityframeworkcore.sqlite를 입력합니다.

  3. Microsoft.EntityFrameworkCore.Sqlite 패키지를 선택합니다.

  4. 오른쪽 창에서 프로젝트를 선택하고 설치를 클릭합니다.

    Sqlite Package

  5. entityframeworkcore.proxies를 검색하고 Microsoft.EntityFrameworkCore.Proxies를 설치하는 단계를 반복합니다.

참고

Sqlite 패키지를 설치하면 관련 Microsoft.EntityFrameworkCore 기본 패키지를 자동으로 끌어옵니다. Microsoft.EntityFrameworkCore.Proxies 패키지는 데이터 "지연 로드"를 지원합니다. 즉, 자식 엔터티가 포함된 엔터티가 있는 경우 초기 로드에서 부모만 가져옵니다. 프록시는 자식 엔터티에 대한 액세스가 시도될 때 감지하여 요청 시 자동으로 로드합니다.

모델 정의

이 연습에서는 “code first”를 사용하여 모델을 구현합니다. 즉, EF Core는 사용자가 정의하는 C# 클래스를 기반으로 데이터베이스 테이블과 스키마를 만듭니다.

새 클래스를 추가합니다. 이름을 Product.cs로 지정하고 다음과 같이 채웁니다.

Product.cs

namespace GetStartedWPF
{
    public class Product
    {
        public int ProductId { get; set; }
        public string Name { get; set; }

        public int CategoryId { get; set; }
        public virtual Category Category { get; set; }
    }
}

다음으로 Category.cs 라는 클래스를 추가하고 다음 코드로 채웁니다.

Category.cs

using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace GetStartedWPF
{
    public class Category
    {
        public int CategoryId { get; set; }
        public string Name { get; set; }

        public virtual ICollection<Product>
            Products
        { get; private set; } =
            new ObservableCollection<Product>();
    }
}

카테고리 클래스의 제품 속성과 제품 클래스의 카테고리 속성은 탐색 속성입니다. Entity Framework에서 탐색 속성은 두 엔터티 형식 간의 관계를 탐색하는 방법을 제공합니다.

엔터티를 정의하는 것 외에도 DbContext에서 파생되고 DbSet<TEntity> 속성을 노출하는 클래스를 정의해야 합니다. DbSet<TEntity> 속성은 모델에 포함하려는 형식을 컨텍스트에 알립니다.

DbContext 파생 형식의 인스턴스는 런타임 중에 엔터티 개체를 관리합니다. 여기에는 데이터베이스의 데이터로 개체 채우기, 변경 내용 추적 및 데이터베이스에 데이터 유지가 포함됩니다.

다음 정의를 사용하여 새 ProductContext.cs 클래스를 프로젝트에 추가합니다.

ProductContext.cs

using Microsoft.EntityFrameworkCore;

namespace GetStartedWPF
{
    public class ProductContext : DbContext
    {
        public DbSet<Product> Products { get; set; }
        public DbSet<Category> Categories { get; set; }

        protected override void OnConfiguring(
            DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlite(
                "Data Source=products.db");
            optionsBuilder.UseLazyLoadingProxies();
        }
    }
}
  • DbSet는 데이터베이스에 매핑해야 할 C# 엔터티를 EF Core에게 알립니다.
  • EF Core DbContext를 구성하는 방법에는 여러 가지가 있습니다. 다음에서 확인할 수 있습니다. DbContext 구성.
  • 이 예제에서는 OnConfiguring 재정의를 사용하여 Sqlite 데이터 파일을 지정합니다.
  • UseLazyLoadingProxies 호출은 지연 로드를 구현하도록 EF Core에 지시하므로 부모에서 액세스할 때 자식 엔터티가 자동으로 로드됩니다.

CTRL+SHIFT+B를 누르거나 빌드 > 솔루션 빌드로 이동하여 프로젝트를 컴파일합니다.

데이터베이스 모델과 EF Core 모델을 동기화 상태로 유지하는 다양한 방법에 대해 알아봅니다. 데이터베이스 스키마 관리.

지연 로드

카테고리 클래스의 제품 속성과 제품 클래스의 카테고리 속성은 탐색 속성입니다. Entity Framework Core에서 탐색 속성은 두 엔터티 형식 간의 관계를 탐색하는 방법을 제공합니다.

EF Core는 탐색 속성에 처음 액세스할 때 데이터베이스에서 관련 엔터티를 자동으로 로드하는 옵션을 제공합니다. 이 유형의 로드(지연 로드라고 함)에서는 각 탐색 속성에 처음 액세스할 때 내용이 컨텍스트에 아직 없는 경우 데이터베이스에 대해 별도의 쿼리가 실행됩니다.

POCO("Plain Old C# Object") 엔터티 형식을 사용할 때 EF Core는 런타임 중에 파생된 프록시 형식의 인스턴스를 만든 다음 클래스의 가상 속성을 재정의해 로딩 후크를 추가하여 지연 로드를 달성합니다. 관련 개체의 지연 로드를 얻으려면 탐색 속성 getter를 공개가상(Visual Basic에서 Overridable)으로 선언해야 하며, 클래스가 봉인(Visual Basic에서 NotOverridable)되지 않아야 합니다. Database First를 사용하는 경우 지연 로드를 사용할 수 있도록 탐색 속성이 자동으로 가상이 됩니다.

개체를 컨트롤에 바인딩

모델에서 정의된 클래스를 이 WPF 애플리케이션의 데이터 원본으로 추가합니다.

  1. 솔루션 탐색기에서 MainWindow.xaml을 두 번 클릭하여 기본 양식을 엽니다.

  2. XAML을 편집할 XAML 탭을 선택합니다.

  3. 여는 Window 태그 바로 뒤에 다음 원본을 추가하여 EF Core 엔터티에 연결합니다.

    <Window x:Class="GetStartedWPF.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:GetStartedWPF"
            mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800" Loaded="Window_Loaded">
        <Window.Resources>
            <CollectionViewSource x:Key="categoryViewSource"/>
            <CollectionViewSource x:Key="categoryProductsViewSource" 
                                  Source="{Binding Products, Source={StaticResource categoryViewSource}}"/>
        </Window.Resources>
    
  4. 이렇게 하면 "부모" 카테고리의 원본과 "세부 정보" 제품의 두 번째 원본이 설정됩니다.

  5. 다음으로, 여는 Grid 태그 뒤에 다음 태그를 XAML에 추가합니다.

    <DataGrid x:Name="categoryDataGrid" AutoGenerateColumns="False" 
              EnableRowVirtualization="True" 
              ItemsSource="{Binding Source={StaticResource categoryViewSource}}" 
              Margin="13,13,43,229" RowDetailsVisibilityMode="VisibleWhenSelected">
        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding CategoryId}"
                                Header="Category Id" Width="SizeToHeader"
                                IsReadOnly="True"/>
            <DataGridTextColumn Binding="{Binding Name}" Header="Name" 
                                Width="*"/>
        </DataGrid.Columns>
    </DataGrid>
    
  6. 데이터베이스에 의해 할당되고 변경할 수 없기 때문에 CategoryIdReadOnly로 설정되어 있습니다.

세부 정보 그리드 추가

이제 카테고리를 표시할 그리드가 있으므로 세부 정보 그리드를 추가하여 제품을 표시할 수 있습니다. Grid 요소 내에서 범주 DataGrid 요소 뒤에 이를 추가합니다.

MainWindow.xaml

<DataGrid x:Name="productsDataGrid" AutoGenerateColumns="False" 
          EnableRowVirtualization="True" 
          ItemsSource="{Binding Source={StaticResource categoryProductsViewSource}}" 
          Margin="13,205,43,108" RowDetailsVisibilityMode="VisibleWhenSelected" 
          RenderTransformOrigin="0.488,0.251">
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding CategoryId}" 
                            Header="Category Id" Width="SizeToHeader"
                            IsReadOnly="True"/>
        <DataGridTextColumn Binding="{Binding ProductId}" Header="Product Id" 
                            Width="SizeToHeader" IsReadOnly="True"/>
        <DataGridTextColumn Binding="{Binding Name}" Header="Name" Width="*"/>
    </DataGrid.Columns>
</DataGrid>

마지막으로 Save 버튼을 추가하고 클릭 이벤트를 Button_Click에 연결합니다.

<Button Content="Save" HorizontalAlignment="Center" Margin="0,240,0,0" 
        Click="Button_Click" Height="20" Width="123"/>

디자인 뷰는 다음과 같이 표시되어야 합니다.

Screenshot of WPF Designer

데이터 상호 작용을 처리하는 코드 추가

이제 주 창에 일부 이벤트 처리기를 추가해야 합니다.

  1. XAML 창에서 <창> 요소를 클릭하여 주 창을 선택합니다.

  2. 속성 창에서 오른쪽 위에 있는 이벤트를 선택한 다음 로드됨 레이블 오른쪽에 있는 텍스트 상자를 두 번 클릭합니다.

    Main Window Properties

그러면 양식 뒤에 있는 코드로 이동하게 됩니다. 이제 ProductContext를 사용하여 데이터 액세스를 수행하도록 코드를 편집합니다. 다음과 같이 코드를 업데이트합니다.

이 코드는 ProductContext의 장기 실행 인스턴스를 선언합니다. ProductContext 개체는 데이터를 쿼리하고 데이터베이스에 저장하는 데 사용됩니다. 그러면 ProductContext 인스턴스의 Dispose() 메서드가 재정의된 OnClosing 메서드에서 호출됩니다. 각 단계에서 수행하는 작업은 코드 주석에 설명되어 있습니다.

MainWindow.xaml.cs

using Microsoft.EntityFrameworkCore;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;

namespace GetStartedWPF
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private readonly ProductContext _context =
            new ProductContext();

        private CollectionViewSource categoryViewSource;

        public MainWindow()
        {
            InitializeComponent();
            categoryViewSource =
                (CollectionViewSource)FindResource(nameof(categoryViewSource));
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // this is for demo purposes only, to make it easier
            // to get up and running
            _context.Database.EnsureCreated();

            // load the entities into EF Core
            _context.Categories.Load();

            // bind to the source
            categoryViewSource.Source =
                _context.Categories.Local.ToObservableCollection();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            // all changes are automatically tracked, including
            // deletes!
            _context.SaveChanges();

            // this forces the grid to refresh to latest values
            categoryDataGrid.Items.Refresh();
            productsDataGrid.Items.Refresh();
        }

        protected override void OnClosing(CancelEventArgs e)
        {
            // clean up database connections
            _context.Dispose();
            base.OnClosing(e);
        }
    }
}

참고

이 코드는 EnsureCreated()에 대한 호출을 사용하여 첫 번째 실행에서 데이터베이스를 빌드합니다. 이것은 데모에서는 허용되지만 프로덕션 앱에서는 스키마를 관리하기 위해 마이그레이션을 확인해야 합니다. 또한 코드는 로컬 SQLite 데이터베이스를 사용하기 때문에 동기적으로 실행됩니다. 일반적으로 원격 서버와 관련된 프로덕션 시나리오에서는 비동기 버전의 LoadSaveChanges 메서드를 사용하는 것이 좋습니다.

WPF 애플리케이션 테스트

F5를 누르거나 디버그 > 디버깅 시작을 선택하여 애플리케이션을 컴파일하고 실행합니다. products.db라는 파일을 사용하여 데이터베이스가 자동으로 만들어져야 합니다. 카테고리 이름을 입력하고 enter 키를 누른 후 아래쪽 그리드에 제품을 추가합니다. 저장을 클릭하고 데이터베이스가 제공하는 ID로 그리드 새로 고침을 관찰합니다. 행을 강조 표시하고 삭제를 눌러 행을 제거합니다. 저장을 클릭하면 엔터티가 삭제됩니다.

Running application

속성 변경 알림

이 예제에서는 네 가지 단계를 사용하여 엔터티를 UI와 동기화합니다.

  1. _context.Categories.Load() 초기 호출은 카테고리 데이터를 로드합니다.
  2. 지연 로드 프록시는 종속 제품 데이터를 로드합니다.
  3. EF Core의 기본 제공 변경 내용 추적은 _context.SaveChanges()가 호출될 때 삽입 및 삭제 등 필요한 엔터티 수정을 수행합니다.
  4. DataGridView.Items.Refresh()에 대한 호출은 새로 생성된 ID를 사용한 다시 로드를 강제 적용합니다.

시작 샘플에서는 이 방법을 사용할 수 있지만 다른 시나리오의 경우 추가 코드가 필요할 수 있습니다. WPF 컨트롤은 엔터티에서 필드와 속성을 읽어 UI를 렌더링합니다. UI(사용자 인터페이스)에서 값을 편집하면 해당 값이 엔터티에 전달됩니다. 데이터베이스에서 데이터 로드와 같이 엔터티에서 속성의 값을 직접 변경하면 WPF는 UI에 변경 내용을 즉시 반영하지 않습니다. 렌더링 엔진에 변경 내용을 알려야 합니다. 프로젝트는 Refresh()를 호출하여 수동으로 이를 수행했습니다. 이 알림을 쉽게 자동화하는 방법은 INotifyPropertyChanged 인터페이스를 구현하는 것입니다. WPF 구성 요소는 자동으로 인터페이스를 검색하고 변경 이벤트에 등록합니다. 엔터티는 이러한 이벤트를 발생시킵니다.

변경을 처리하는 방법에 대해 자세히 알아보려면 다음을 참조하세요. 속성 변경 알림을 구현하는 방법.

다음 단계

DbContext 구성에 대해 자세히 알아봅니다.