Sdílet prostřednictvím


Vazby dat pomocí WinForms

Tento podrobný návod ukazuje, jak svázat typy POCO s ovládacími prvky Windows Forms (WinForms) ve formuláři master-detail. Aplikace používá Entity Framework k naplnění objektů dat z databáze, sledování změn a zachování dat do databáze.

Model definuje dva typy, které se účastní relace 1:N: Category (principal\master) a Product (závislý\detail). Nástroje sady Visual Studio se pak používají k vytvoření vazby typů definovaných v modelu k ovládacím prvkům WinForms. Rozhraní datové vazby WinForms umožňuje navigaci mezi souvisejícími objekty: výběr řádků v zobrazení předlohy způsobí aktualizaci zobrazení podrobností s odpovídajícími podřízenými daty.

Snímky obrazovky a výpisy kódu v tomto návodu pocházejí ze sady Visual Studio 2013, ale tento návod můžete dokončit pomocí sady Visual Studio 2012 nebo Visual Studio 2010.

Předpoklady

K dokončení tohoto návodu musíte mít nainstalovanou sadu Visual Studio 2013, Visual Studio 2012 nebo Visual Studio 2010.

Pokud používáte Visual Studio 2010, musíte také nainstalovat NuGet. Další informace najdete v tématu Instalace NuGetu.

Vytvoření aplikace

  • Otevřete sadu Visual Studio.
  • Soubor - Nový ->> Project....
  • Výběr Windows v levém podokně a model Windows Forms Aplikace v pravém podokně
  • Jako název zadejte WinFormswithEFSample .
  • Vyberte OK.

Instalace balíčku NuGet entity Framework

  • V Průzkumník řešení klikněte pravým tlačítkem myši na projekt WinFormswithEFSample.
  • Vyberte Spravovat balíčky NuGet...
  • V dialogovém okně Spravovat balíčky NuGet vyberte kartu Online a zvolte balíček EntityFramework .
  • Klikněte na Nainstalovat.

    Poznámka

    Kromě sestavení EntityFramework se také přidá odkaz na System.ComponentModel.DataAnnotations. Pokud má projekt odkaz na System.Data.Entity, při instalaci balíčku EntityFramework se odebere. Sestavení System.Data.Entity se už nepoužívá pro aplikace Entity Framework 6.

Implementace IListSource pro kolekce

Vlastnosti kolekce musí implementovat IListSource rozhraní pro povolení obousměrné datové vazby s řazením při použití model Windows Forms. Abychom to udělali, rozšíříme ObservableCollection o přidání funkce IListSource.

  • Přidejte do projektu třídu ObservableListSource:
    • Klikněte pravým tlačítkem myši na název projektu.
    • Vybrat přidat –> nová položka
    • Vyberte třídu a jako název třídy zadejte ObservableListSource .
  • Ve výchozím nastavení nahraďte kód vygenerovaný následujícím kódem:

Tato třída umožňuje obousměrnou datovou vazbu i řazení. Třída je odvozena z ObservableCollection<T> a přidává explicitní implementaci IListSource. Metoda GetList() IListSource je implementována k vrácení IBindingList implementace, která zůstává synchronizovaná s ObservableCollection. Implementace IBindingList vygenerovaná ToBindingList podporuje řazení. ToBindingList rozšiřující metoda je definována v EntityFramework sestavení.

    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());
            }
        }
    }

Definování modelu

V tomto názorném postupu můžete zvolit implementaci modelu pomocí code First nebo EF Designeru. Dokončete jednu ze dvou následujících částí.

Možnost 1: Definování modelu pomocí kódu First

Tato část ukazuje, jak vytvořit model a jeho přidruženou databázi pomocí code First. Přejděte k další části (možnost 2: Definování modelu pomocí databáze First), pokud byste raději použili Database First k zpětnému analýzu modelu z databáze pomocí návrháře EF.

Při použití vývoje Code First obvykle začínáte psaním tříd rozhraní .NET Framework, které definují váš konceptuální (doménový) model.

  • Přidání nové třídy Product do projektu
  • Ve výchozím nastavení nahraďte kód vygenerovaný následujícím kódem:
    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; }
        }
    }
  • Přidejte do projektu třídu Category.
  • Ve výchozím nastavení nahraďte kód vygenerovaný následujícím kódem:
    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; } }
        }
    }

Kromě definování entit je nutné definovat třídu, která je odvozena z DbContext a zveřejňuje DbSet<TEntity> vlastnosti. Vlastnosti DbSet znají kontext, které typy chcete zahrnout do modelu. Typy DbContext a DbSet jsou definovány v sestavení EntityFramework.

Instance odvozeného typu DbContext spravuje objekty entity během doby běhu, což zahrnuje naplnění objektů dat z databáze, sledování změn a zachování dat do databáze.

  • Přidejte do projektu novou třídu ProductContext .
  • Ve výchozím nastavení nahraďte kód vygenerovaný následujícím kódem:
    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; }
        }
    }

Zkompilujte projekt.

Možnost 2: Definování modelu pomocí database First

Tato část ukazuje, jak pomocí Database First provést zpětnou analýzu modelu z databáze pomocí návrháře EF. Pokud jste dokončili předchozí část (možnost 1: Definování modelu pomocí kódu First), přeskočte tuto část a přejděte přímo do části Opožděné načítání .

Vytvoření existující databáze

Obvykle platí, že když cílíte na existující databázi, bude již vytvořena, ale pro účely tohoto názorného postupu potřebujeme vytvořit databázi pro přístup.

Databázový server nainstalovaný se sadou Visual Studio se liší v závislosti na nainstalované verzi sady Visual Studio:

  • Pokud používáte Visual Studio 2010, budete vytvářet databázi SQL Express.
  • Pokud používáte Visual Studio 2012, budete vytvářet databázi LocalDB .

Pojďme pokračovat a vygenerovat databázi.

  • Zobrazení –> Průzkumník serveru

  • Klikněte pravým tlačítkem na Připojení iony dat –> přidat Připojení ion...

  • Pokud jste se ještě nepřipojili k databázi z Průzkumníka serveru, musíte jako zdroj dat vybrat Microsoft SQL Server.

    Change Data Source

  • Připojení do LocalDB nebo SQL Express v závislosti na tom, který máte nainstalovaný, a zadejte Produkty jako název databáze

    Add Connection LocalDB

    Add Connection Express

  • Vyberte OK a zobrazí se dotaz, jestli chcete vytvořit novou databázi, vyberte Ano.

    Create Database

  • Nová databáze se teď zobrazí v Průzkumníku serveru, klikněte na ni pravým tlačítkem myši a vyberte Nový dotaz.

  • Zkopírujte následující SQL do nového dotazu, klikněte pravým tlačítkem myši na dotaz a vyberte Spustit.

    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 zpětné analýzy

K vytvoření modelu použijeme Návrhář entity Framework, který je součástí sady Visual Studio.

  • Projekt –> přidat novou položku...

  • V nabídce vlevo vyberte Data a pak ADO.NET Entity Data Model.

  • Jako název zadejte ProductModel a klikněte na OK.

  • Tím se spustí Průvodce datovým modelem entity.

  • Vyberte Vygenerovat z databáze a klikněte na Další.

    Choose Model Contents

  • Vyberte připojení k databázi, kterou jste vytvořili v první části, jako název připojovací řetězec zadejte ProductContext a klikněte na Další.

    Choose Your Connection

  • Kliknutím na zaškrtávací políčko vedle tabulky naimportujete všechny tabulky a kliknete na Dokončit.

    Choose Your Objects

Jakmile proces zpětné analýzy dokončí nový model, přidá se do projektu a otevře se vám zobrazení v Návrháři entity Framework. Do projektu byl přidán také soubor App.config s podrobnostmi o připojení pro databázi.

Další kroky v sadě Visual Studio 2010

Pokud pracujete v sadě Visual Studio 2010, budete muset aktualizovat návrháře EF tak, aby používal generování kódu EF6.

  • Klikněte pravým tlačítkem na prázdné místo modelu v nástroji EF Designer a vyberte Přidat položku generování kódu...
  • V nabídce vlevo vyberte online šablony a vyhledejte DbContext.
  • Vyberte generátor EF 6.x DbContext pro jazyk C#, jako název zadejte ProductsModel a klikněte na Přidat.

Aktualizace generování kódu pro datová vazba

EF vygeneruje kód z modelu pomocí šablon T4. Šablony dodávané se sadou Visual Studio nebo stažené z galerie sady Visual Studio jsou určené pro obecné účely. To znamená, že entity generované z těchto šablon mají jednoduché ICollection<T> vlastnosti. Při vytváření datové vazby je však žádoucí mít vlastnosti kolekce, které implementují IListSource. Proto jsme vytvořili výše uvedenou třídu ObservableListSource a nyní upravíme šablony tak, aby používaly tuto třídu.

  • Otevřete Průzkumník řešení a najděte soubor ProductModel.edmx.

  • Vyhledejte soubor ProductModel.tt, který bude vnořený pod souborem ProductModel.edmx.

    Product Model Template

  • Poklikáním otevřete soubor ProductModel.tt v editoru sady Visual Studio.

  • Vyhledejte a nahraďte dva výskyty "ICollection" "ObservableListSource". Nachází se přibližně v řádcích 296 a 484.

  • Vyhledejte a nahraďte první výskyt "HashSet" za "ObservableListSource". Tento výskyt se nachází přibližně na řádku 50. Nenahrazovat druhý výskyt HashSet nalezen později v kódu.

  • Uložte soubor ProductModel.tt. To by mělo způsobit opětovné vygenerování kódu entit. Pokud se kód automaticky nevygeneruje, klikněte pravým tlačítkem na ProductModel.tt a zvolte Spustit vlastní nástroj.

Pokud teď otevřete soubor Category.cs (který je vnořený pod ProductModel.tt), měli byste vidět, že kolekce Products má typ ObservableListSource<Product>.

Zkompilujte projekt.

Opožděné načítání

Vlastnost Products ve třídě Category a Category ve třídě Product jsou navigační vlastnosti. Ve službě Entity Framework poskytují navigační vlastnosti způsob, jak přecházet mezi dvěma typy entit.

EF nabízí možnost načíst související entity z databáze automaticky při prvním přístupu k navigační vlastnosti. U tohoto typu načítání (označovaného jako opožděné načítání) mějte na paměti, že při prvním přístupu ke každé navigační vlastnosti se vůči databázi spustí samostatný dotaz, pokud obsah ještě není v kontextu.

Při použití typů entit POCO ef dosahuje opožděného načítání vytvořením instancí odvozených typů proxy během běhu a následným přepsáním virtuálních vlastností ve vašich třídách, aby se přidala zátěžová háka. Chcete-li získat opožděné načítání souvisejících objektů, musíte deklarovat navigační vlastnost getters jako veřejné a virtuální (Overridable v jazyce Visual Basic) a třída nesmí být zapečetěná (NotOverridable v jazyce Visual Basic). Při použití vlastností navigace Database First se automaticky vytvoří virtuální, aby se povolilo opožděné načítání. V části Code First jsme se rozhodli nastavit, aby navigační vlastnosti byly virtuální z stejného důvodu.

Vytvoření vazby objektu k ovládacím prvkům

Přidejte třídy definované v modelu jako zdroje dat pro tuto aplikaci WinForms.

  • V hlavní nabídce vyberte Projekt –> Přidat nový zdroj dat ... (v sadě Visual Studio 2010 musíte vybrat Data –> Přidat nový zdroj dat...)

  • V okně Zvolit typ zdroje dat vyberte Objekt a klikněte na Další.

  • V dialogovém okně Vybrat datové objekty rozbalte WinFormswithEFSample dvakrát a vyberte Kategorie Není nutné vybrat zdroj dat Product, protože se k němu dostaneme prostřednictvím vlastnosti Product ve zdroji dat Category.

    Data Source

  • Klikněte na Dokončit. Pokud se okno Zdroje dat nezobrazuje, vyberte Zobrazit –> Jiné zdroje dat Windows.>

  • Stiskněte ikonu připnutí, takže okno Zdroje dat se automaticky neskryje. Možná budete muset stisknout tlačítko aktualizovat, pokud už bylo okno viditelné.

    Data Source 2

  • V Průzkumník řešení poklikáním otevřete hlavní formulář v návrháři poklikáním na soubor Form1.cs.

  • Vyberte zdroj dat Kategorie a přetáhněte ho ve formuláři. Ve výchozím nastavení jsou do návrháře přidány nové ovládací prvky DataGridView (categoryDataGridView) a navigační panel nástrojů. Tyto ovládací prvky jsou svázané s komponentami BindingSource (categoryBindingSource) a Binding Navigator (categoryBindingNavigator), které jsou také vytvořeny.

  • Upravte sloupce v categoryDataGridView. Sloupec CategoryId chceme nastavit jen pro čtení. Hodnota vlastnosti CategoryId je generována databází po uložení dat.

    • Klikněte pravým tlačítkem myši na ovládací prvek DataGridView a vyberte Upravit sloupce...
    • Vyberte sloupec CategoryId a nastavte ReadOnly na True.
    • Stiskněte OK.
  • V části Zdroj dat Kategorie vyberte Produkty a přetáhněte je do formuláře. ProductDataGridView a productBindingSource jsou přidány do formuláře.

  • Upravte sloupce v productDataGridView. Chceme skrýt sloupce CategoryId a Category a nastavit ProductId na jen pro čtení. Hodnota vlastnosti ProductId je generována databází po uložení dat.

    • Klikněte pravým tlačítkem myši na ovládací prvek DataGridView a vyberte Upravit sloupce....
    • Vyberte sloupec ProductId a nastavte ReadOnly na True.
    • Vyberte sloupec CategoryId a stiskněte tlačítko Odebrat. Totéž proveďte se sloupcem Kategorie .
    • Stiskněte OK.

    Zatím jsme přidružovali ovládací prvky DataGridView k komponentám BindingSource v návrháři. V další části přidáme kód do kódu za nastavením categoryBindingSource.DataSource na kolekci entit, které jsou aktuálně sledovány DbContext. Když jsme přetáhli produkty z kategorie, winForms se postaral o nastavení productsBindingSource.DataSource vlastnost categoryBindingSource a productsBindingSource.DataMember vlastnost Products. Z důvodu této vazby se v productDataGridView zobrazí pouze produkty, které patří do aktuálně vybrané kategorie.

  • Kliknutím na pravé tlačítko myši a výběrem možnosti Povoleno povolte tlačítko Uložit na navigačním panelu nástrojů.

    Form 1 Designer

  • Obslužnou rutinu události pro tlačítko Uložit přidáte poklikáním na tlačítko. Tím se přidá obslužná rutina události a přenese vás do kódu formuláře. Kód obslužné rutiny události categoryBindingNavigatorSaveItem_Click se přidá v další části.

Přidání kódu, který zpracovává interakci s daty

Teď přidáme kód, který použije ProductContext k provádění přístupu k datům. Aktualizujte kód hlavního okna formuláře, jak je znázorněno níže.

Kód deklaruje dlouho běžící instanci ProductContext. ProductContext objekt slouží k dotazování a ukládání dat do databáze. Metoda Dispose() u instance ProductContext je volána z přepsání OnClosing metoda. Komentáře ke kódu poskytují podrobnosti o tom, co kód dělá.

    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();
            }
        }
    }

Otestování aplikace model Windows Forms

  • Zkompilujte a spusťte aplikaci a můžete otestovat funkčnost.

    Form 1 Before Save

  • Po uložení vygenerovaných klíčů úložiště se zobrazí na obrazovce.

    Form 1 After Save

  • Pokud jste použili Code First, uvidíte také, že je pro vás vytvořena databáze WinFormswithEFSample.ProductContext .

    Server Object Explorer