Łączenie danych z WPF
Ważne
Ten dokument jest prawidłowy tylko dla platformy WPF na platformie .NET Framework
W tym dokumencie opisano powiązanie danych dla platformy WPF na platformie .NET Framework. W przypadku nowych projektów platformy .NET Core zalecamy użycie programu EF Core zamiast programu Entity Framework 6. Dokumentacja dotycząca powiązania danych w programie EF Core znajduje się tutaj: Wprowadzenie do platformy WPF.
W tym przewodniku krok po kroku pokazano, jak powiązać typy POCO z kontrolkami WPF w formularzu "master-detail". Aplikacja używa interfejsów API programu Entity Framework do wypełniania obiektów danymi z bazy danych, śledzenia zmian i utrwalania danych w bazie danych.
Model definiuje dwa typy, które uczestniczą w relacji jeden do wielu: Kategoria (principal\master) i Product (zależne\szczegóły). Następnie narzędzia programu Visual Studio służą do powiązania typów zdefiniowanych w modelu z kontrolkami WPF. Struktura powiązania danych WPF umożliwia nawigację między powiązanymi obiektami: wybieranie wierszy w widoku głównym powoduje, że widok szczegółów zostanie zaktualizowany przy użyciu odpowiednich danych podrzędnych.
Zrzuty ekranu i listy kodu w tym przewodniku pochodzą z programu Visual Studio 2013, ale możesz ukończyć ten przewodnik za pomocą programu Visual Studio 2012 lub Visual Studio 2010.
Użyj opcji "Object" do tworzenia źródeł danych WPF
W poprzedniej wersji programu Entity Framework zalecamy użycie opcji Baza danych podczas tworzenia nowego źródła danych na podstawie modelu utworzonego przy użyciu Projektant EF. Wynika to z faktu, że projektant wygeneruje kontekst pochodzący z klasy ObjectContext i jednostek, które pochodzą z obiektu EntityObject. Użycie opcji Baza danych ułatwi napisanie najlepszego kodu do interakcji z tą powierzchnią interfejsu API.
Projektant EF dla programów Visual Studio 2012 i Visual Studio 2013 generują kontekst pochodzący z klasy jednostek DbContext wraz z prostymi klasami jednostek POCO. W programie Visual Studio 2010 zalecamy zamianę na szablon generowania kodu, który używa elementu DbContext zgodnie z opisem w dalszej części tego przewodnika.
W przypadku korzystania z powierzchni interfejsu API DbContext należy użyć opcji Obiekt podczas tworzenia nowego źródła danych, jak pokazano w tym przewodniku.
W razie potrzeby możesz przywrócić generowanie kodu opartego na obiekcie ObjectContext dla modeli utworzonych za pomocą Projektant EF.
Wymagania wstępne
Aby ukończyć ten przewodnik, musisz mieć zainstalowany program Visual Studio 2013, Visual Studio 2012 lub Visual Studio 2010.
Jeśli używasz programu Visual Studio 2010, musisz również zainstalować pakiet NuGet. Aby uzyskać więcej informacji, zobacz Instalowanie pakietu NuGet.
Tworzenie aplikacji
- Otwórz program Visual Studio.
- Plik — Nowy —>> Project....
- Wybierz pozycję Windows w okienku po lewej stronie i WPFApplication w okienku po prawej stronie
- Wprowadź ciąg WPFwithEFSample jako nazwę
- Wybierz OK
Instalowanie pakietu NuGet programu Entity Framework
- W Eksplorator rozwiązań kliknij prawym przyciskiem myszy projekt WinFormswithEFSample
- Wybierz pozycję Zarządzaj pakietami NuGet...
- W oknie dialogowym Zarządzanie pakietami NuGet wybierz kartę Online i wybierz pakiet EntityFramework
- Kliknij pozycję Zainstaluj
Uwaga
Oprócz zestawu EntityFramework dodano również odwołanie do elementu System.ComponentModel.DataAnnotations. Jeśli projekt ma odwołanie do elementu System.Data.Entity, zostanie on usunięty po zainstalowaniu pakietu EntityFramework. Zestaw System.Data.Entity nie jest już używany dla aplikacji platformy Entity Framework 6.
Definiowanie modelu
W tym przewodniku można wybrać wdrożenie modelu przy użyciu funkcji Code First lub EF Projektant. Wykonaj jedną z dwóch poniższych sekcji.
Opcja 1. Definiowanie modelu przy użyciu funkcji Code First
W tej sekcji przedstawiono sposób tworzenia modelu i skojarzonej z nią bazy danych przy użyciu funkcji Code First. Przejdź do następnej sekcji (Opcja 2: Definiowanie modelu przy użyciu funkcji Database First), jeśli wolisz użyć usługi Database First do odtworzenia modelu z bazy danych przy użyciu projektanta EF
W przypadku korzystania z programowania Code First zwykle zaczynasz od pisania klas programu .NET Framework, które definiują model koncepcyjny (domena).
- Dodaj nową klasę do wpFwithEFSample:
- Kliknij prawym przyciskiem myszy nazwę projektu
- Wybierz pozycję Dodaj, a następnie pozycję Nowy element
- Wybierz pozycję Klasa i wprowadź wartość Product jako nazwę klasy.
- Zastąp definicję klasy Product następującym kodem:
namespace WPFwithEFSample
{
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
}
}
- Dodaj klasę Category z następującą definicją:
using System.Collections.ObjectModel;
namespace WPFwithEFSample
{
public class Category
{
public Category()
{
this.Products = new ObservableCollection<Product>();
}
public int CategoryId { get; set; }
public string Name { get; set; }
public virtual ObservableCollection<Product> Products { get; private set; }
}
}
Właściwość Products w klasie Category i właściwości Category w klasie Product to właściwości nawigacji. W programie Entity Framework właściwości nawigacji zapewniają sposób nawigowania po relacji między dwoma typami jednostek.
Oprócz definiowania jednostek należy zdefiniować klasę pochodzącą z elementu DbContext i uwidaczniać właściwości TEntity> zestawu dbSet<. Właściwości DbSet<TEntity> poinformują kontekst o typach, które mają zostać uwzględnione w modelu.
Wystąpienie typu pochodnego DbContext zarządza obiektami jednostki w czasie wykonywania, co obejmuje wypełnianie obiektów danymi z bazy danych, śledzenie zmian i utrwalanie danych do bazy danych.
- Dodaj nową klasę ProductContext do projektu z następującą definicją:
using System.Data.Entity;
namespace WPFwithEFSample
{
public class ProductContext : DbContext
{
public DbSet<Category> Categories { get; set; }
public DbSet<Product> Products { get; set; }
}
}
Skompiluj projekt.
Opcja 2. Definiowanie modelu przy użyciu funkcji Database First
W tej sekcji pokazano, jak używać usługi Database First do odtwarzania modelu z bazy danych przy użyciu projektanta ef. Jeśli poprzednia sekcja została ukończona (Opcja 1: Definiowanie modelu przy użyciu funkcji Code First), pomiń tę sekcję i przejdź prosto do sekcji Ładowanie z opóźnieniem.
Tworzenie istniejącej bazy danych
Zazwyczaj w przypadku określania wartości docelowej istniejącej bazy danych zostanie ona już utworzona, ale w tym przewodniku musimy utworzyć bazę danych, aby uzyskać dostęp.
Serwer bazy danych zainstalowany w programie Visual Studio różni się w zależności od zainstalowanej wersji programu Visual Studio:
- Jeśli używasz programu Visual Studio 2010, utworzysz bazę danych SQL Express.
- Jeśli używasz programu Visual Studio 2012, utworzysz bazę danych LocalDB .
Wygenerujmy bazę danych.
Widok —> Eksplorator serwera
Kliknij prawym przyciskiem myszy pozycję Połączenie ions danych —> Dodaj Połączenie ion...
Jeśli nie nawiązaliśmy połączenia z bazą danych z Eksploratora serwera przed wybraniem programu Microsoft SQL Server jako źródła danych
Połączenie do lokalnej bazy danych lub programu SQL Express, w zależności od zainstalowanej bazy danych i wprowadź Produkty jako nazwa bazy danych
Wybierz przycisk OK i zostanie wyświetlony monit o utworzenie nowej bazy danych, wybierz pozycję Tak
Nowa baza danych pojawi się teraz w Eksploratorze serwera, kliknij ją prawym przyciskiem myszy i wybierz pozycję Nowe zapytanie
Skopiuj następujący kod SQL do nowego zapytania, a następnie kliknij prawym przyciskiem myszy zapytanie i wybierz polecenie Wykonaj
CREATE TABLE [dbo].[Categories] (
[CategoryId] [int] NOT NULL IDENTITY,
[Name] [nvarchar](max),
CONSTRAINT [PK_dbo.Categories] PRIMARY KEY ([CategoryId])
)
CREATE TABLE [dbo].[Products] (
[ProductId] [int] NOT NULL IDENTITY,
[Name] [nvarchar](max),
[CategoryId] [int] NOT NULL,
CONSTRAINT [PK_dbo.Products] PRIMARY KEY ([ProductId])
)
CREATE INDEX [IX_CategoryId] ON [dbo].[Products]([CategoryId])
ALTER TABLE [dbo].[Products] ADD CONSTRAINT [FK_dbo.Products_dbo.Categories_CategoryId] FOREIGN KEY ([CategoryId]) REFERENCES [dbo].[Categories] ([CategoryId]) ON DELETE CASCADE
Model odwrotnego inżyniera
Użyjemy platformy Entity Framework Projektant, która jest uwzględniona w programie Visual Studio, aby utworzyć nasz model.
Projekt —> dodaj nowy element...
Wybierz pozycję Dane z menu po lewej stronie, a następnie ADO.NET model danych jednostki
Wprowadź productModel jako nazwę i kliknij przycisk OK
Spowoduje to uruchomienie Kreatora modelu danych jednostki
Wybierz pozycję Generuj z bazy danych i kliknij przycisk Dalej
Wybierz połączenie z bazą danych utworzoną w pierwszej sekcji, wprowadź productContext jako nazwę parametry połączenia, a następnie kliknij przycisk Dalej.
Kliknij pole wyboru obok pozycji "Tabele", aby zaimportować wszystkie tabele, a następnie kliknij przycisk Zakończ.
Po zakończeniu procesu inżyniera odwrotnego nowy model zostanie dodany do projektu i otwarty, aby wyświetlić go w Projektant Entity Framework. Plik App.config został również dodany do projektu ze szczegółami połączenia dla bazy danych.
Dodatkowe kroki w programie Visual Studio 2010
Jeśli pracujesz w programie Visual Studio 2010, musisz zaktualizować projektanta ef do używania generowania kodu EF6.
- Kliknij prawym przyciskiem myszy puste miejsce modelu w Projektant EF i wybierz pozycję Dodaj element generowania kodu...
- Wybierz pozycję Szablony online z menu po lewej stronie i wyszukaj pozycję DbContext
- Wybierz generator EF 6.x DbContext dla języka C#, wprowadź ciąg ProductsModel jako nazwę, a następnie kliknij przycisk Dodaj
Aktualizowanie generowania kodu na potrzeby powiązania danych
Program EF generuje kod z modelu przy użyciu szablonów T4. Szablony dostarczane z programem Visual Studio lub pobrane z galerii programu Visual Studio są przeznaczone do użytku ogólnego przeznaczenia. Oznacza to, że jednostki wygenerowane na podstawie tych szablonów mają proste właściwości ICollection<T> . Jednak w przypadku tworzenia powiązania danych przy użyciu WPF pożądane jest użycie funkcji ObservableCollection dla właściwości kolekcji, dzięki czemu WPF może śledzić zmiany wprowadzone w kolekcjach. W tym celu zmodyfikujemy szablony tak, aby używały funkcji ObservableCollection.
Otwórz Eksplorator rozwiązań i znajdź plik ProductModel.edmx
Znajdź plik ProductModel.tt, który zostanie zagnieżdżony w pliku ProductModel.edmx
Kliknij dwukrotnie plik ProductModel.tt, aby otworzyć go w edytorze programu Visual Studio
Znajdź i zastąp dwa wystąpienia elementu "ICollection" ciągiem "ObservableCollection". Znajdują się one w przybliżeniu w liniach 296 i 484.
Znajdź i zastąp pierwsze wystąpienie elementu "HashSet" ciągiem "ObservableCollection". To zdarzenie znajduje się w przybliżeniu w wierszu 50. Nie zamieniaj drugiego wystąpienia elementu HashSet znalezionego w dalszej części kodu.
Znajdź i zastąp jedyne wystąpienie elementu "System.Collections.Generic" ciągiem "System.Collections.ObjectModel". Znajduje się on w przybliżeniu w wierszu 424.
Zapisz plik ProductModel.tt. Powinno to spowodować ponowne wygenerowanie kodu jednostek. Jeśli kod nie zostanie wygenerowany automatycznie, kliknij prawym przyciskiem myszy ProductModel.tt i wybierz polecenie "Uruchom narzędzie niestandardowe".
Jeśli teraz otworzysz plik Category.cs (który jest zagnieżdżony w ProductModel.tt), zobaczysz, że kolekcja Products ma typ ObservableCollection<Product>.
Skompiluj projekt.
Ładowanie opóźnione
Właściwość Products w klasie Category i właściwości Category w klasie Product to właściwości nawigacji. W programie Entity Framework właściwości nawigacji zapewniają sposób nawigowania po relacji między dwoma typami jednostek.
Program EF umożliwia automatyczne ładowanie powiązanych jednostek z bazy danych przy pierwszym uzyskiwaniu dostępu do właściwości nawigacji. W przypadku tego typu ładowania (nazywanego ładowaniem leniwym) należy pamiętać, że przy pierwszym uzyskiwaniu dostępu do każdej właściwości nawigacji zostanie wykonane oddzielne zapytanie względem bazy danych, jeśli zawartość nie znajduje się jeszcze w kontekście.
W przypadku korzystania z typów jednostek POCO program EF osiąga opóźnienie ładowania, tworząc wystąpienia pochodnych typów serwerów proxy w czasie wykonywania, a następnie przesłaniając właściwości wirtualne w klasach, aby dodać punkt zaczepienia ładowania. Aby uzyskać leniwe ładowanie powiązanych obiektów, należy zadeklarować metody pobierania właściwości nawigacji jako publiczne i wirtualne (zastępowalne w Visual Basic), a klasa nie może być zapieczętowana (NotOverridable w Visual Basic). W przypadku korzystania z właściwości nawigacji Database First są automatycznie tworzone wirtualne, aby umożliwić ładowanie leniwe. W sekcji Code First wybraliśmy, aby właściwości nawigacji wirtualne z tego samego powodu.
Wiązanie obiektu z kontrolkami
Dodaj klasy zdefiniowane w modelu jako źródła danych dla tej aplikacji WPF.
Kliknij dwukrotnie plik MainWindow.xaml w Eksplorator rozwiązań, aby otworzyć formularz główny
Z menu głównego wybierz pozycję Projekt —> Dodaj nowe źródło danych... (w programie Visual Studio 2010 musisz wybrać pozycję Dane —> Dodaj nowe źródło danych...)
W obszarze Wybierz typ źródła danychwindow wybierz pozycję Obiekt , a następnie kliknij przycisk Dalej
W oknie dialogowym Wybieranie obiektów danych rozwiń plik WPFwithEFSample dwa razy i wybierz pozycję Kategoria
Nie ma potrzeby wybierania źródła danych Produkt, ponieważ przejdziemy do niego za pośrednictwem właściwości Product w źródle danych CategoryKliknij przycisk Finish (Zakończ).
Okno Źródła danych jest otwierane obok okna MainWindow.xaml Jeśli okno Źródła danych nie jest wyświetlane, wybierz pozycję Widok —> Inne źródła danych systemu Windows —> Źródła danych
Naciśnij ikonę pinezki, aby okno Źródła danych nie ukrywało się automatycznie. Jeśli okno było już widoczne, może być konieczne naciśnięcie przycisku odświeżania.
Wybierz źródło danych Kategorii i przeciągnij je na formularz.
Podczas przeciągnięcia tego źródła wystąpiły następujące zdarzenia:
- Zasób categoryViewSource i kontrolka categoryDataGrid zostały dodane do języka XAML
- Właściwość DataContext elementu nadrzędnej siatki została ustawiona na "{StaticResource categoryViewSource }". Zasób categoryViewSource służy jako źródło powiązania elementu zewnętrznej\nadrzędnej siatki. Elementy wewnętrznej siatki dziedziczą następnie wartość DataContext z nadrzędnej siatki (właściwość ItemsSource kategoriiDataGrid jest ustawiona na "{Binding}")
<Window.Resources>
<CollectionViewSource x:Key="categoryViewSource"
d:DesignSource="{d:DesignInstance {x:Type local:Category}, CreateList=True}"/>
</Window.Resources>
<Grid DataContext="{StaticResource categoryViewSource}">
<DataGrid x:Name="categoryDataGrid" AutoGenerateColumns="False" EnableRowVirtualization="True"
ItemsSource="{Binding}" Margin="13,13,43,191"
RowDetailsVisibilityMode="VisibleWhenSelected">
<DataGrid.Columns>
<DataGridTextColumn x:Name="categoryIdColumn" Binding="{Binding CategoryId}"
Header="Category Id" Width="SizeToHeader"/>
<DataGridTextColumn x:Name="nameColumn" Binding="{Binding Name}"
Header="Name" Width="SizeToHeader"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
Dodawanie siatki szczegółów
Teraz, gdy mamy siatkę do wyświetlenia kategorii, dodajmy siatkę szczegółów w celu wyświetlenia skojarzonych produktów.
- Wybierz właściwość Products z obszaru Źródło danych Kategoria i przeciągnij ją na formularz.
- Zasób categoryProductsViewSource i siatka productDataGrid są dodawane do języka XAML
- Ścieżka powiązania dla tego zasobu jest ustawiona na Products
- Struktura powiązania danych WPF zapewnia, że tylko produkty powiązane z wybraną kategorią są wyświetlane w elemecie productDataGrid
- Z przybornika przeciągnij przycisk na formularz. Ustaw właściwość Name na przyciskZapisz i właściwość Content na Zapisz.
Formularz powinien wyglądać podobnie do następującego:
Dodawanie kodu obsługującego interakcję z danymi
Nadszedł czas, aby dodać niektóre programy obsługi zdarzeń do okna głównego.
W oknie XAML kliknij <element Okno , co powoduje wybranie głównego okna
W oknie Właściwości wybierz pozycję Zdarzenia w prawym górnym rogu, a następnie kliknij dwukrotnie pole tekstowe po prawej stronie załadowanej etykiety
Dodaj również zdarzenie Kliknij dla przycisku Zapisz , klikając dwukrotnie przycisk Zapisz w projektancie.
Spowoduje to wyświetlenie kodu formularza. Teraz zmodyfikujemy kod, aby używać obiektu ProductContext do wykonywania dostępu do danych. Zaktualizuj kod dla systemu MainWindow, jak pokazano poniżej.
Kod deklaruje długotrwałe wystąpienie elementu ProductContext. Obiekt ProductContext służy do wykonywania zapytań i zapisywania danych w bazie danych. Metoda Dispose() w wystąpieniu ProductContext jest następnie wywoływana z zastąpionej metody OnClosing . Komentarze kodu zawierają szczegółowe informacje o kodzie.
using System.Data.Entity;
using System.Linq;
using System.Windows;
namespace WPFwithEFSample
{
public partial class MainWindow : Window
{
private ProductContext _context = new ProductContext();
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
System.Windows.Data.CollectionViewSource categoryViewSource =
((System.Windows.Data.CollectionViewSource)(this.FindResource("categoryViewSource")));
// Load is an extension method on IQueryable,
// defined in the System.Data.Entity namespace.
// This method enumerates the results of the query,
// similar to ToList but without creating a list.
// When used with Linq to Entities this method
// creates entity objects and adds them to the context.
_context.Categories.Load();
// After the data is loaded call the DbSet<T>.Local property
// to use the DbSet<T> as a binding source.
categoryViewSource.Source = _context.Categories.Local;
}
private void buttonSave_Click(object sender, RoutedEventArgs e)
{
// When you delete an object from the related entities collection
// (in this case Products), the Entity Framework doesn’t mark
// these child entities as deleted.
// Instead, it removes the relationship between the parent and the child
// by setting the parent reference to null.
// So we manually have to delete the products
// that have a Category reference set to null.
// The following code uses LINQ to Objects
// against the Local collection of Products.
// The ToList call is required because otherwise the collection will be modified
// by the Remove call while it is being enumerated.
// In most other situations you can use LINQ to Objects directly
// against the Local property without using ToList first.
foreach (var product in _context.Products.Local.ToList())
{
if (product.Category == null)
{
_context.Products.Remove(product);
}
}
_context.SaveChanges();
// Refresh the grids so the database generated values show up.
this.categoryDataGrid.Items.Refresh();
this.productsDataGrid.Items.Refresh();
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
base.OnClosing(e);
this._context.Dispose();
}
}
}
Testowanie aplikacji WPF
Skompiluj i uruchom aplikację. Jeśli użyto funkcji Code First, zobaczysz, że zostanie utworzona baza danych WPFwithEFSample.ProductContext .
Wprowadź nazwę kategorii w górnej siatce i nazwy produktów w dolnej siatce Nie wprowadzaj niczego w kolumnach identyfikatorów, ponieważ klucz podstawowy jest generowany przez bazę danych
Naciśnij przycisk Zapisz, aby zapisać dane w bazie danych
Po wywołaniu metody SaveChanges() elementu DbContext identyfikatory są wypełniane wartościami wygenerowanymi przez bazę danych. Ponieważ po funkcji SaveChanges() kontrolki DataGrid zostały zaktualizowane przy użyciu nowych wartości.
Dodatkowe zasoby
Aby dowiedzieć się więcej na temat powiązania danych z kolekcjami przy użyciu platformy WPF, zobacz ten temat w dokumentacji platformy WPF.