Liaison de données avec WinForms

Cette procédure pas à pas montre comment lier des types POCO aux contrôles Windows Forms (WinForms) dans un formulaire « master-detail ». L’application utilise Entity Framework pour remplir des objets avec des données de la base de données, suivre les modifications et conserver les données dans la base de données.

Le modèle définit deux types qui participent à une relation un-à-plusieurs : Catégorie (principal\master) et Product (dépendant\detail). Ensuite, les outils Visual Studio sont utilisés pour lier les types définis dans le modèle aux contrôles WinForms. L’infrastructure de liaison de données WinForms permet la navigation entre les objets associés : la sélection de lignes dans la vue maître entraîne la mise à jour de la vue détaillée avec les données enfants correspondantes.

Les captures d’écran et les listes de code de cette procédure pas à pas sont extraites de Visual Studio 2013, mais vous pouvez effectuer cette procédure pas à pas avec Visual Studio 2012 ou Visual Studio 2010.

Prérequis

Vous devez avoir installé Visual Studio 2013, Visual Studio 2012 ou Visual Studio 2010 pour effectuer cette procédure pas à pas.

Si vous utilisez Visual Studio 2010, vous devez également installer NuGet. Pour plus d’informations, consultez Installation de NuGet.

Création de l’application

  • Ouvrez Visual Studio.
  • Fichier - Nouveau ->> Project....
  • Sélectionner Windows dans le volet gauche et Windows FormsApplication dans le volet droit
  • Entrez WinFormswithEFSample comme nom
  • Sélectionnez OK.

Installer le package NuGet Entity Framework

  • Dans Explorateur de solutions, cliquez avec le bouton droit sur le projet WinFormswithEFSample
  • Sélectionnez Gérer les packages NuGet...
  • Dans la boîte de dialogue Gérer les packages NuGet, sélectionnez l’onglet En ligne et choisissez le package EntityFramework
  • Cliquez sur Install.

    Notes

    Outre l’assembly EntityFramework, une référence à System.ComponentModel.DataAnnotations est également ajoutée. Si le projet a une référence à System.Data.Entity, il est supprimé lorsque le package EntityFramework est installé. L’assembly System.Data.Entity n’est plus utilisé pour les applications Entity Framework 6.

Implémentation d’IListSource pour collections

Les propriétés de collection doivent implémenter l’interface IListSource pour activer la liaison de données bidirectionnelle avec le tri lors de l’utilisation de Windows Forms. Pour ce faire, nous allons étendre ObservableCollection pour ajouter des fonctionnalités IListSource.

  • Ajoutez une classe ObservableListSource au projet :
    • Cliquez avec le bouton droit sur le nom du projet.
    • Sélectionner Ajouter -> Nouvel élément
    • Sélectionnez Classe et entrez ObservableListSource pour le nom de la classe
  • Remplacez le code généré par défaut par le code suivant :

Cette classe active la liaison de données bidirectionnelle ainsi que le tri. La classe dérive de ObservableCollection<T> et ajoute une implémentation explicite d’IListSource. La méthode GetList() d’IListSource est implémentée pour retourner une implémentation IBindingList qui reste synchronisée avec ObservableCollection. L’implémentation IBindingList générée par ToBindingList prend en charge le tri. La méthode d’extension ToBindingList est définie dans l’assembly 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());
            }
        }
    }

Définir un modèle

Dans cette procédure pas à pas, vous pouvez choisir d’implémenter un modèle à l’aide de Code First ou d’EF Designer. Effectuez l’une des deux sections suivantes.

Option 1 : Définir un modèle à l’aide du code First

Cette section montre comment créer un modèle et sa base de données associée à l’aide de Code First. Passez à la section suivante (Option 2 : Définir un modèle à l’aide de database First) si vous préférez utiliser Database First pour inverser l’ingénierie de votre modèle à partir d’une base de données à l’aide du concepteur EF

Lorsque vous utilisez le développement Code First, vous commencez généralement par écrire des classes .NET Framework qui définissent votre modèle conceptuel (domaine).

  • Ajouter une nouvelle classe Product au projet
  • Remplacez le code généré par défaut par le code suivant :
    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; }
        }
    }
  • Ajoutez une classe Category au projet.
  • Remplacez le code généré par défaut par le code suivant :
    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; } }
        }
    }

En plus de définir des entités, vous devez définir une classe qui dérive de DbContext et expose les propriétés TEntity> DbSet<. Les propriétés DbSet indiquent au contexte quels types vous souhaitez inclure dans le modèle. Les types DbContext et DbSet sont définis dans l’assembly EntityFramework.

Une instance du type dérivé DbContext gère les objets d’entité pendant l’exécution, notamment le remplissage d’objets avec des données d’une base de données, le suivi des modifications et la persistance des données dans la base de données.

  • Ajoutez une nouvelle classe ProductContext au projet.
  • Remplacez le code généré par défaut par le code suivant :
    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; }
        }
    }

Compilez le projet.

Option 2 : Définir un modèle à l’aide de Database First

Cette section montre comment utiliser Database First pour inverser l’ingénierie de votre modèle à partir d’une base de données à l’aide du concepteur EF. Si vous avez terminé la section précédente (Option 1 : Définir un modèle à l’aide de Code First), ignorez cette section et accédez directement à la section Chargement différé .

Créer une base de données existante

En règle générale, lorsque vous ciblez une base de données existante, elle sera déjà créée, mais pour cette procédure pas à pas, nous devons créer une base de données à accéder.

Le serveur de base de données installé avec Visual Studio est différent selon la version de Visual Studio que vous avez installée :

  • Si vous utilisez Visual Studio 2010, vous allez créer une base de données SQL Express.
  • Si vous utilisez Visual Studio 2012, vous allez créer une base de données LocalDB .

Allons-y et générons la base de données.

  • Affichage -> Explorateur de serveurs

  • Cliquez avec le bouton droit sur Connexions de données -> Ajouter une connexion...

  • Si vous n’avez pas connecté à une base de données à partir de l’Explorateur de serveurs avant de devoir sélectionner Microsoft SQL Server comme source de données

    Modifier la source de données

  • Connectez-vous à LocalDB ou SQL Express, selon celui que vous avez installé, puis entrez Products comme nom de base de données.

    Ajouter une base de données locale de connexion

    Ajouter Connection Express

  • Sélectionnez OK et vous serez invité à créer une base de données, sélectionnez Oui.

    Créer une base de données

  • La nouvelle base de données s’affiche désormais dans l’Explorateur de serveurs, cliquez dessus avec le bouton droit, puis sélectionnez Nouvelle requête.

  • Copiez le code SQL suivant dans la nouvelle requête, puis cliquez avec le bouton droit sur la requête, puis sélectionnez Exécuter

    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

Modèle d’ingénieur inverse

Nous allons utiliser Entity Framework Designer, qui est inclus dans Visual Studio, pour créer notre modèle.

  • Projet -> Ajouter un nouvel élément...

  • Sélectionnez Données dans le menu de gauche, puis ADO.NET Modèle de données d’entité

  • Entrez ProductModel comme nom, puis cliquez sur OK

  • Cette opération lance l’Assistant Modèle de données d’entité

  • Sélectionnez Générer à partir de la base de données , puis cliquez sur Suivant

    Choisir le contenu du modèle

  • Sélectionnez la connexion à la base de données que vous avez créée dans la première section, entrez ProductContext comme nom de la chaîne de connexion, puis cliquez sur Suivant.

    Choisir votre connexion

  • Cochez la case en regard de « Tables » pour importer toutes les tables, puis cliquez sur « Terminer ».

    Choisir vos objets

Une fois que le processus de l’ingénieur inverse a terminé le nouveau modèle est ajouté à votre projet et ouvert pour vous permettre d’afficher dans Entity Framework Designer. Un fichier App.config a également été ajouté à votre projet avec les détails de connexion de la base de données.

Étapes supplémentaires dans Visual Studio 2010

Si vous travaillez dans Visual Studio 2010, vous devez mettre à jour le concepteur EF pour utiliser la génération de code EF6.

  • Cliquez avec le bouton droit sur un emplacement vide de votre modèle dans le concepteur EF, puis sélectionnez Ajouter un élément de génération de code...
  • Sélectionnez Modèles en ligne dans le menu de gauche et recherchez DbContext
  • Sélectionnez le générateur EF 6.x DbContext pour C#, entrez ProductsModel comme nom, puis cliquez sur Ajouter

Mise à jour de la génération de code pour la liaison de données

EF génère du code à partir de votre modèle à l’aide de modèles T4. Les modèles fournis avec Visual Studio ou téléchargés à partir de la galerie Visual Studio sont destinés à une utilisation générale. Cela signifie que les entités générées à partir de ces modèles ont des propriétés ICollection<T> simples. Toutefois, lorsque vous effectuez une liaison de données, il est souhaitable d’avoir des propriétés de collection qui implémentent IListSource. C’est pourquoi nous avons créé la classe ObservableListSource ci-dessus et nous allons maintenant modifier les modèles pour utiliser cette classe.

  • Ouvrez le Explorateur de solutions et recherchez le fichier ProductModel.edmx

  • Recherchez le fichier ProductModel.tt qui sera imbriqué sous le fichier ProductModel.edmx

    Modèle de modèle de produit

  • Double-cliquez sur le fichier ProductModel.tt pour l’ouvrir dans l’éditeur Visual Studio

  • Recherchez et remplacez les deux occurrences de « ICollection » par « ObservableListSource ». Celles-ci se trouvent à environ 296 et 484.

  • Recherchez et remplacez la première occurrence de « HashSet » par « ObservableListSource ». Cette occurrence se trouve à environ la ligne 50. Ne remplacez pas la deuxième occurrence de HashSet trouvée plus loin dans le code.

  • Enregistrez le fichier ProductModel.tt. Cela doit entraîner la régénération du code des entités. Si le code ne se régénère pas automatiquement, cliquez avec le bouton droit sur ProductModel.tt et choisissez « Exécuter l’outil personnalisé ».

Si vous ouvrez maintenant le fichier Category.cs (qui est imbriqué sous ProductModel.tt), vous devez voir que la collection Products a le type ObservableListSource<Product>.

Compilez le projet.

Chargement différé

La propriété Products de la classe Category et de la propriété Category de la classe Product sont des propriétés de navigation. Dans Entity Framework, les propriétés de navigation permettent de parcourir une relation entre deux types d’entités.

EF vous offre une option de chargement d’entités associées à partir de la base de données automatiquement la première fois que vous accédez à la propriété de navigation. Avec ce type de chargement (appelé chargement paresseux), sachez que la première fois que vous accédez à chaque propriété de navigation, une requête distincte sera exécutée sur la base de données si le contenu n’est pas déjà dans le contexte.

Lorsque vous utilisez des types d’entités POCO, EF obtient un chargement différé en créant des instances de types proxy dérivés pendant l’exécution, puis en remplaçant les propriétés virtuelles de vos classes pour ajouter le hook de chargement. Pour obtenir le chargement différé d’objets associés, vous devez déclarer des getters de propriété de navigation en tant que getters publics et virtuels (substituables en Visual Basic), et vous ne devez pas être scellé (NotOverridable en Visual Basic). Lors de l’utilisation des propriétés de navigation Database First, les propriétés de navigation sont automatiquement rendues virtuelles pour activer le chargement paresseux. Dans la section Code First, nous avons choisi de rendre les propriétés de navigation virtuelles pour la même raison

Lier un objet à des contrôles

Ajoutez les classes définies dans le modèle en tant que sources de données pour cette application WinForms.

  • Dans le menu principal, sélectionnez Project -> Ajouter une nouvelle source de données ... (dans Visual Studio 2010, vous devez sélectionner Données -> Ajouter une nouvelle source de données...)

  • Dans la fenêtre Choisir un type de source de données, sélectionnez Objet , puis cliquez sur Suivant

  • Dans la boîte de dialogue Sélectionner les objets de données, déployez winFormswithEFSample deux fois et sélectionnez Catégorie Il n’est pas nécessaire de sélectionner la source de données Produit, car nous y accéderons via la propriété du produit sur la source de données Catégorie.

    Source de données

  • Cliquez sur Terminer. Si la fenêtre Sources de données n’apparaît pas, sélectionnez Affichage -> Autres sources de données Windows->

  • Appuyez sur l’icône épingle, de sorte que la fenêtre Sources de données ne se masque pas automatiquement. Vous devrez peut-être appuyer sur le bouton Actualiser si la fenêtre était déjà visible.

    Source de données 2

  • Dans Explorateur de solutions, double-cliquez sur le fichier Form1.cs pour ouvrir le formulaire principal dans le concepteur.

  • Sélectionnez la source de données Catégorie et faites-la glisser sur le formulaire. Par défaut, les contrôles de barre d’outils DataGridView (categoryDataGridView) et Navigation sont ajoutés au concepteur. Ces contrôles sont liés aux composants BindingSource (categoryBindingSource) et Binding Navigator (categoryBindingNavigator) qui sont également créés.

  • Modifiez les colonnes de la catégorieDataGridView. Nous voulons définir la colonne CategoryId en lecture seule. La valeur de la propriété CategoryId est générée par la base de données après avoir enregistré les données.

    • Cliquez avec le bouton droit sur le contrôle DataGridView et sélectionnez Modifier les colonnes...
    • Sélectionnez la colonne CategoryId et définissez ReadOnly sur True
    • Appuyez sur OK
  • Sélectionnez Produits dans la source de données Catégorie et faites-le glisser sur le formulaire. ProductDataGridView et productBindingSource sont ajoutés au formulaire.

  • Modifiez les colonnes sur productDataGridView. Nous voulons masquer les colonnes CategoryId et Category et définir ProductId en lecture seule. La valeur de la propriété ProductId est générée par la base de données après avoir enregistré les données.

    • Cliquez avec le bouton droit sur le contrôle DataGridView et sélectionnez Modifier les colonnes....
    • Sélectionnez la colonne ProductId et définissez ReadOnly sur True.
    • Sélectionnez la colonne CategoryId , puis appuyez sur le bouton Supprimer . Procédez de la même façon avec la colonne Catégorie .
    • Appuyez sur OK.

    Jusqu’à présent, nous avons associé nos contrôles DataGridView aux composants BindingSource dans le concepteur. Dans la section suivante, nous allons ajouter du code au code behind pour définir categoryBindingSource.DataSource sur la collection d’entités actuellement suivies par DbContext. Lorsque nous avons déplacé et supprimé des produits sous la catégorie, winForms a pris soin de configurer la propriété productsBindingSource.DataSource sur categoryBindingSource et productsBindingSource.DataMember sur Products. En raison de cette liaison, seuls les produits appartenant à la catégorie actuellement sélectionnée s’affichent dans productDataGridView.

  • Activez le bouton Enregistrer dans la barre d’outils de navigation en cliquant sur le bouton droit de la souris et en sélectionnant Activé.

    Concepteur Form 1

  • Ajoutez le gestionnaire d’événements pour le bouton Enregistrer en double-cliquant sur le bouton. Cela ajoute le gestionnaire d’événements et vous amène au code derrière le formulaire. Le code du gestionnaire d’événements categoryBindingNavigatorSaveItem_Click sera ajouté dans la section suivante.

Ajouter le code qui gère l’interaction des données

Nous allons maintenant ajouter le code pour utiliser ProductContext pour effectuer l’accès aux données. Mettez à jour le code de la fenêtre de formulaire principale, comme indiqué ci-dessous.

Le code déclare une instance longue de ProductContext. L’objet ProductContext est utilisé pour interroger et enregistrer des données dans la base de données. La méthode Dispose() sur l’instance ProductContext est ensuite appelée à partir de la méthode OnClosing remplacée. Les commentaires de code fournissent des détails sur ce que fait le code.

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

Tester l’application Windows Forms

  • Compilez et exécutez l’application et vous pouvez tester les fonctionnalités.

    Formulaire 1 avant d’enregistrer

  • Une fois que vous avez enregistré les clés générées dans le magasin, vous s’affichez sur l’écran.

    Formulaire 1 Après l’enregistrement

  • Si vous avez utilisé Code First, vous verrez également qu’une base de données WinFormswithEFSample.ProductContext est créée pour vous.

    Serveur Explorateur d'objets