Łączenie danych za pomocą formularzy WinForm
W tym przewodniku krok po kroku pokazano, jak powiązać typy POCO z kontrolkami Formularze okien (WinForms) w formularzu "master-detail". Aplikacja używa 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 WinForms. Struktura powiązania danych WinForms umożliwia nawigację między powiązanymi obiektami: wybieranie wierszy w widoku głównym powoduje zaktualizowanie widoku szczegółów 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.
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 pozycję Windows FormsApplication w okienku po prawej stronie
- Wprowadź ciąg WinFormswithEFSample 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.
Implementowanie elementu IListSource dla kolekcji
Właściwości kolekcji muszą implementować interfejs IListSource, aby umożliwić dwukierunkowe powiązanie danych z sortowaniem podczas korzystania z formularzy systemu Windows. W tym celu rozszerzymy funkcję ObservableCollection, aby dodać funkcję IListSource.
- Dodaj klasę ObservableListSource do projektu:
- Kliknij prawym przyciskiem myszy nazwę projektu
- Wybierz pozycję Dodaj —> nowy element
- Wybierz pozycję Klasa i wprowadź wartość ObservableListSource jako nazwę klasy.
- Zastąp kod wygenerowany domyślnie następującym kodem:
Ta klasa umożliwia dwukierunkowe powiązanie danych, a także sortowanie. Klasa pochodzi z obiektu ObservableCollection<T> i dodaje jawną implementację elementu IListSource. Metoda GetList() elementu IListSource jest implementowana w celu zwrócenia implementacji IBindingList, która pozostaje zsynchronizowana z aplikacją ObservableCollection. Implementacja IBindingList wygenerowana przez bibliotekę ToBindingList obsługuje sortowanie. Metoda rozszerzenia ToBindingList jest zdefiniowana w zestawie EntityFramework.
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Data.Entity;
namespace WinFormswithEFSample
{
public class ObservableListSource<T> : ObservableCollection<T>, IListSource
where T : class
{
private IBindingList _bindingList;
bool IListSource.ContainsListCollection { get { return false; } }
IList IListSource.GetList()
{
return _bindingList ?? (_bindingList = this.ToBindingList());
}
}
}
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).
- Dodawanie nowej klasy Product do projektu
- Zastąp kod wygenerowany domyślnie następującym kodem:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WinFormswithEFSample
{
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 do projektu.
- Zastąp kod wygenerowany domyślnie następującym kodem:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WinFormswithEFSample
{
public class Category
{
private readonly ObservableListSource<Product> _products =
new ObservableListSource<Product>();
public int CategoryId { get; set; }
public string Name { get; set; }
public virtual ObservableListSource<Product> Products { get { return _products; } }
}
}
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 poinformują kontekst o typach, które mają zostać uwzględnione w modelu. Typy DbContext i DbSet są definiowane w zestawie EntityFramework.
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.
- Zastąp kod wygenerowany domyślnie następującym kodem:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
namespace WinFormswithEFSample
{
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 podczas tworzenia powiązania danych pożądane jest posiadanie właściwości kolekcji, które implementują usługę IListSource. Dlatego utworzyliśmy powyżej klasę ObservableListSource i teraz zmodyfikujemy szablony, aby korzystały z tej klasy.
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" elementem "ObservableListSource". Znajdują się one w około liniach 296 i 484.
Znajdź i zastąp pierwsze wystąpienie elementu "HashSet" ciągiem "ObservableListSource". To zdarzenie znajduje się w około wierszu 50. Nie zamieniaj drugiego wystąpienia elementu HashSet znalezionego w dalszej części kodu.
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 obszarze ProductModel.tt), zobaczysz, że kolekcja Products ma typ ObservableListSource<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 WinForms.
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 oknie Wybieranie typu źródła danych wybierz pozycję Obiekt , a następnie kliknij przycisk Dalej.
W oknie dialogowym Wybieranie obiektów danych rozwiń dwukrotnie obiekt WinFormswithEFSample 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 Kategoria.
Kliknij przycisk Zakończ. 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.
W Eksplorator rozwiązań kliknij dwukrotnie plik Form1.cs, aby otworzyć formularz główny w projektancie.
Wybierz źródło danych Kategorii i przeciągnij je na formularz. Domyślnie do projektanta są dodawane nowe kontrolki paska narzędzi DataGridView (categoryDataGridView) i nawigacji. Te kontrolki są powiązane ze składnikami BindingSource (categoryBindingSource) i Binding Navigator (categoryBindingNavigator), które również są tworzone.
Edytuj kolumny w kategoriiDataGridView. Chcemy ustawić kolumnę CategoryId na tylko do odczytu. Wartość właściwości CategoryId jest generowana przez bazę danych po zapisaniu danych.
- Kliknij prawym przyciskiem myszy kontrolkę DataGridView i wybierz polecenie Edytuj kolumny...
- Wybierz kolumnę CategoryId i ustaw wartość ReadOnly na True
- Naciśnij przycisk OK
Wybierz pozycję Products (Produkty) w obszarze Kategoria źródła danych i przeciągnij je na formularz. Element productDataGridView i productBindingSource są dodawane do formularza.
Edytuj kolumny w obiekcie productDataGridView. Chcemy ukryć kolumny CategoryId i Category i ustawić wartość ProductId na wartość tylko do odczytu. Wartość właściwości ProductId jest generowana przez bazę danych po zapisaniu danych.
- Kliknij prawym przyciskiem myszy kontrolkę DataGridView i wybierz polecenie Edytuj kolumny....
- Wybierz kolumnę ProductId i ustaw wartość ReadOnly na True.
- Wybierz kolumnę CategoryId i naciśnij przycisk Usuń . Zrób to samo w kolumnie Kategoria .
- Naciśnij przycisk OK.
Do tej pory skojarzyliśmy kontrolki DataGridView ze składnikami BindingSource w projektancie. W następnej sekcji dodamy kod do kodu, aby ustawić właściwość categoryBindingSource.DataSource do kolekcji jednostek, które są obecnie śledzone przez element DbContext. Po przeciągnięciu i upuszczeniu elementów Products z kategorii właściwość WinForms zajęła się skonfigurowaniem właściwości productsBindingSource.DataSource do właściwości categoryBindingSource i productsBindingSource.DataMember do właściwości Products. Ze względu na to powiązanie w widoku productDataGridView będą wyświetlane tylko produkty należące do aktualnie wybranej kategorii.
Włącz przycisk Zapisz na pasku narzędzi nawigacji, klikając prawym przyciskiem myszy i wybierając pozycję Włączone.
Dodaj procedurę obsługi zdarzeń dla przycisku zapisywania, klikając dwukrotnie przycisk . Spowoduje to dodanie programu obsługi zdarzeń i przełączenie do kodu dla formularza. Kod obsługi zdarzeń categoryBindingNavigatorSaveItem_Click zostanie dodany w następnej sekcji.
Dodawanie kodu obsługującego interakcję z danymi
Teraz dodamy kod, aby użyć obiektu ProductContext do wykonywania dostępu do danych. Zaktualizuj kod okna formularza głównego, 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;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Data.Entity;
namespace WinFormswithEFSample
{
public partial class Form1 : Form
{
ProductContext _context;
public Form1()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_context = new ProductContext();
// Call the Load method to get the data for the given DbSet
// from the database.
// The data is materialized as entities. The entities are managed by
// the DbContext instance.
_context.Categories.Load();
// Bind the categoryBindingSource.DataSource to
// all the Unchanged, Modified and Added Category objects that
// are currently tracked by the DbContext.
// Note that we need to call ToBindingList() on the
// ObservableCollection<TEntity> returned by
// the DbSet.Local property to get the BindingList<T>
// in order to facilitate two-way binding in WinForms.
this.categoryBindingSource.DataSource =
_context.Categories.Local.ToBindingList();
}
private void categoryBindingNavigatorSaveItem_Click(object sender, EventArgs e)
{
this.Validate();
// Currently, the Entity Framework doesn’t mark the entities
// that are removed from a navigation property (in our example the Products)
// as deleted in the context.
// The following code uses LINQ to Objects against the Local collection
// to find all products and marks any that do not have
// a Category reference as deleted.
// 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 do 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);
}
}
// Save the changes to the database.
this._context.SaveChanges();
// Refresh the controls to show the values
// that were generated by the database.
this.categoryDataGridView.Refresh();
this.productsDataGridView.Refresh();
}
protected override void OnClosing(CancelEventArgs e)
{
base.OnClosing(e);
this._context.Dispose();
}
}
}
Testowanie aplikacji Windows Forms
Skompiluj i uruchom aplikację i możesz przetestować funkcjonalność.
Po zapisaniu wygenerowanych kluczy magazynu są wyświetlane na ekranie.
Jeśli użyto funkcji Code First, zobaczysz również, że zostanie utworzona baza danych WinFormswithEFSample.ProductContext .