이 단계별 연습에서는 "기본 세부 정보" 형식의 WPF 컨트롤에 POCO 형식을 바인딩하는 방법을 보여 줍니다. 애플리케이션은 Entity Framework API를 사용하여 데이터베이스의 데이터로 개체를 채우고, 변경 내용을 추적하고, 데이터를 데이터베이스에 유지합니다.
이 모델은 일대다 관계에 참여하는 두 가지 형식인 범주 (principal\main) 및 Product (dependent\detail)를 정의합니다. WPF 데이터 바인딩 프레임워크를 사용하면 관련 개체 간을 탐색할 수 있습니다. 마스터 보기에서 행을 선택하면 세부 정보 보기가 해당 자식 데이터로 업데이트됩니다.
이 연습의 스크린샷 및 코드 목록은 Visual Studio 2019 16.6.5에서 캡처한 것입니다.
팁 (조언)
GitHub 에서 이 문서의샘플을 볼 수 있습니다.
필수 구성 요소
이 연습을 완료하려면 .NET 데스크톱 워크로드 가 선택된 상태에서 Visual Studio 2019 16.3 이상을 설치해야 합니다. 최신 버전의 Visual Studio 설치에 대한 자세한 내용은 Visual Studio 설치를 참조하세요.
애플리케이션 만들기
- Visual Studio를 엽니다.
- 시작 창에서 새 프로젝트 만들기를 선택합니다.
- "WPF"를 검색하고 WPF 앱(.NET Core) 을 선택한 다음 , 다음을 선택합니다.
- 다음 화면에서 프로젝트 이름(예: GetStartedWPF)을 지정하고 만들기를 선택합니다 .
Entity Framework NuGet 패키지 설치
솔루션을 마우스 오른쪽 단추로 클릭하고 솔루션용 NuGet 패키지 관리를 선택합니다.
검색 상자에
entityframeworkcore.sqlite
를 입력합니다.Microsoft.EntityFrameworkCore.Sqlite 패키지를 선택합니다.
오른쪽 창에서 프로젝트를 확인하고 설치를 클릭합니다.
entityframeworkcore.proxies
를 검색하고 설치하는 단계를 반복합니다.
비고
Sqlite 패키지를 설치하면 관련 Microsoft.EntityFrameworkCore 기본 패키지가 자동으로 내려옵니다. Microsoft.EntityFrameworkCore.Proxies 패키지는 "지연 로드" 데이터를 지원합니다. 즉, 자식 엔터티를 가진 경우 초기 로드 시 부모 엔터티만 가져옵니다. 프록시는 자식 엔터티에 액세스하려는 시도가 이루어지는 시기를 감지하고 요청 시 자동으로 로드합니다.
모델 정의
이 연습에서는 "코드 우선"을 사용하여 모델을 구현합니다. 즉, 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>();
}
}
Product 클래스의 Category 클래스 및 Category 속성에 있는 Products 속성 은 탐색 속성입니다. 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
는 EF Core에 어떤 C# 엔터티가 데이터베이스에 매핑되어야 하는지를 알립니다. - EF Core
DbContext
를 구성하는 방법에는 여러 가지가 있습니다. DbContext 구성에서 해당 항목에 대해 읽을 수 있습니다. - 이 예제에서는 재정의를
OnConfiguring
사용하여 Sqlite 데이터 파일을 지정합니다. - 이 호출은
UseLazyLoadingProxies
EF Core에 지연 로드를 구현하도록 지시하므로 부모에서 액세스할 때 자식 엔터티가 자동으로 로드됩니다.
Ctrl+Shift+B를 누르거나 빌드 솔루션 빌드 > 로 이동하여 프로젝트를 컴파일합니다.
팁 (조언)
데이터베이스와 EF Core 모델을 동기화된 상태로 유지하는 방법인 데이터베이스 스키마 관리에 대해 알아봅니다.
지연 로딩
Product 클래스의 Category 클래스 및 Category 속성에 있는 Products 속성 은 탐색 속성입니다. Entity Framework Core에서 탐색 속성은 두 엔터티 형식 간의 관계를 탐색하는 방법을 제공합니다.
EF Core는 탐색 속성에 처음 액세스할 때 데이터베이스에서 관련 엔터티를 자동으로 로드하는 옵션을 제공합니다. 이러한 유형의 로드(지연 로드라고 함)를 사용하면 각 탐색 속성에 처음 액세스할 때 콘텐츠가 아직 컨텍스트에 없는 경우 데이터베이스에 대해 별도의 쿼리가 실행됩니다.
"POCO(Plain Old C# Object)" 엔터티 형식을 사용하는 경우 EF Core는 런타임 중에 파생된 프록시 형식의 인스턴스를 만든 다음 클래스의 가상 속성을 재정의하여 로드 후크를 추가하여 지연 로드를 수행합니다. 관련 객체의 지연 로드를 활용하려면 탐색 속성 getter를 공용 및 가상 (Visual Basic에서 재정의 가능)으로 선언해야 하며, 클래스는 봉인되지 않도록 해야 합니다 (Visual Basic에서는 NotOverridable). Database First를 사용하는 경우 지연 로드를 사용하도록 탐색 속성이 자동으로 가상으로 만들어집니다.
컨트롤에 개체 바인딩
모델에 정의된 클래스를 이 WPF 애플리케이션의 데이터 원본으로 추가합니다.
솔루션 탐색기에서 MainWindow.xaml 을 두 번 클릭하여 기본 양식을 엽니다.
XAML 탭을 선택하여 XAML을 편집합니다.
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>
이렇게 하면 "부모" 범주의 소스와 "세부" 제품의 두 번째 소스가 완성됩니다.
XAML에
Grid
시작 태그 다음에 아래의 마크업을 추가합니다.<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>
참고로,
CategoryId
은(는) 데이터베이스에 의해 할당되어 변경할 수 없기 때문에ReadOnly
으로 설정됩니다.
세부 정보 표 추가
범주를 표시하기 위해 그리드가 존재했으므로 세부 정보 표를 추가하여 제품을 표시할 수 있습니다. categories 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"/>
디자인 화면은 다음과 같이 보일 것입니다.
데이터 상호 작용을 처리하는 코드 추가
주 창에 일부 이벤트 처리기를 추가해야 합니다.
XAML 창에서 Window< 요소를 클릭하여> 주 창을 선택합니다.
속성 창에서 오른쪽 위에 있는 이벤트를 선택한 다음 로드된 레이블의 오른쪽에 있는 텍스트 상자를 두 번 클릭합니다.
양식의 백엔드 코드로 이동하면 이제 데이터 액세스를 수행하는 데 사용할 ProductContext
코드를 편집합니다. 아래와 같이 코드를 업데이트합니다.
이 코드는 장기 실행 인스턴스를 선언합니다 ProductContext
. 개체는 ProductContext
데이터를 쿼리하고 데이터베이스에 저장하는 데 사용됩니다.
Dispose()
메서드는 재정의된 ProductContext
메서드에서 인스턴스의 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 데이터베이스를 사용하기 때문에 동기적으로 실행됩니다. 일반적으로 원격 서버를 포함하는 프로덕션 시나리오의 경우 Load
및 SaveChanges
메서드의 비동기 버전을 사용하는 것이 좋습니다.
WPF 애플리케이션 테스트
F5 키를 누르거나 디버그 > 시작 디버깅을 선택하여 애플리케이션을 컴파일하고 실행합니다. 라는 products.db
파일을 사용하여 데이터베이스를 자동으로 만들어야 합니다. 범주 이름을 입력하고 Enter 키를 누른 다음 아래쪽 눈금에 제품을 추가합니다. 저장을 클릭하고 데이터베이스 제공 ID를 사용하여 그리드 새로 고침을 확인합니다. 행을 강조 표시하고 Delete 키를 눌러 행을 제거합니다.
저장을 클릭하면 엔터티가 삭제됩니다.
속성 변경 알림
이 예제에서는 4단계를 사용하여 엔터티를 UI와 동기화합니다.
- 초기 호출
_context.Categories.Load()
은 범주 데이터를 로드합니다. - 지연 로드 프록시는 종속 제품 데이터를 로드합니다.
- EF Core의 내부 변경 사항 추적 기능은
_context.SaveChanges()
를 호출할 때 엔터티 삽입 및 삭제를 포함하여 필요한 수정 작업을 수행합니다. - 새로 생성된 ID로 강제 재로드를 수행하기 위한
DataGridView.Items.Refresh()
에 대한 호출입니다.
이는 시작 샘플에 대해 작동하지만 다른 시나리오에 대한 추가 코드가 필요할 수 있습니다. WPF 컨트롤은 엔터티의 필드와 속성을 읽어 UI를 렌더링합니다. UI(사용자 인터페이스)에서 값을 편집하면 해당 값이 엔터티에 전달됩니다. 데이터베이스에서 로드하는 것과 같이 엔터티에서 직접 속성 값을 변경하는 경우 WPF는 UI의 변경 내용을 즉시 반영하지 않습니다. 렌더링 엔진에 변경 내용을 알려야 합니다. 프로젝트는 수동으로 호출 Refresh()
하여 이 작업을 수행했습니다. 이 알림을 자동화하는 쉬운 방법은 INotifyPropertyChanged 인터페이스를 구현하는 것입니다. WPF 구성 요소는 인터페이스를 자동으로 검색하고 변경 이벤트를 등록합니다. 엔터티는 이러한 이벤트를 발생시키는 역할을 담당합니다.
팁 (조언)
변경 내용을 처리하는 방법에 대한 자세한 내용은 다음을 참조하세요. 속성 변경 알림을 구현하는 방법
다음 단계
DbContext 구성에 대해 자세히 알아봅니다.
.NET