Condividi tramite


Associazione dati con WinForms

Questa procedura dettagliata illustra come associare tipi POCO ai controlli Window Form (WinForms) in un modulo "master-detail". L'applicazione usa Entity Framework per popolare oggetti con dati dal database, tenere traccia delle modifiche e rendere persistenti i dati nel database.

Il modello definisce due tipi che partecipano a una relazione uno-a-molti: *Category* (principale/master) e *Product* (dipendente/dettaglio). Gli strumenti di Visual Studio vengono quindi usati per associare i tipi definiti nel modello ai controlli WinForms. Il framework di data binding WinForms consente la navigazione tra oggetti correlati: la selezione di righe nella vista principale fa sì che la vista dettagliata si aggiorni con i dati figlio corrispondenti.

Le schermate e gli elenchi di codice in questa procedura dettagliata sono tratti da Visual Studio 2013, ma è possibile completare questa procedura dettagliata con Visual Studio 2012 o Visual Studio 2010.

Prerequisiti

Per completare questa procedura dettagliata, è necessario che Visual Studio 2013, Visual Studio 2012 o Visual Studio 2010 sia installato.

Se si usa Visual Studio 2010, è anche necessario installare NuGet. Per altre informazioni, vedere Installazione di NuGet.

Creare l'applicazione

  • Apri Visual Studio.
  • File -> Nuovo -> Progetto....
  • Selezionare Windows nel riquadro sinistro e Windows FormsApplication nel riquadro destro
  • Immettere WinFormswithEFSample come nome
  • Seleziona OK.

Installare il pacchetto NuGet di Entity Framework

  • In Esplora soluzioni fare clic con il pulsante destro del mouse sul progetto WinFormswithEFSample
  • Selezionare Gestisci pacchetti NuGet...
  • Nella finestra di dialogo Gestisci pacchetti NuGet selezionare la scheda Online e scegliere il pacchetto EntityFramework
  • Fai clic su Install (Installa).

    Annotazioni

    Oltre all'assembly EntityFramework viene aggiunto anche un riferimento a System.ComponentModel.DataAnnotations. Se il progetto ha un riferimento a System.Data.Entity, verrà rimosso quando viene installato il pacchetto EntityFramework. L'assembly System.Data.Entity non viene più usato per le applicazioni Entity Framework 6.

Implementazione di IListSource per le raccolte

Le proprietà della raccolta devono implementare l'interfaccia IListSource per abilitare il data binding bidirezionale con l'ordinamento quando si usa Windows Form. A tale scopo, estendere ObservableCollection per aggiungere la funzionalità IListSource.

  • Aggiungere una classe ObservableListSource al progetto:
    • Fare clic con il pulsante destro del mouse sul nome del progetto
    • Selezionare Aggiungi -> Nuovo elemento
    • Selezionare Classe e immettere ObservableListSource come nome della classe
  • Sostituire il codice generato per impostazione predefinita con il codice seguente:

Questa classe abilita il data binding bidirezionale e l'ordinamento. La classe deriva da ObservableCollection<T> e aggiunge un'implementazione esplicita di IListSource. Il metodo GetList() di IListSource viene implementato per restituire un'implementazione IBindingList che rimane sincronizzata con ObservableCollection. L'implementazione IBindingList generata da ToBindingList supporta l'ordinamento. Il metodo di estensione ToBindingList viene definito nell'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());
            }
        }
    }

Definire un modello

In questa procedura dettagliata è possibile scegliere di implementare un modello usando Code First o Ef Designer. Completare una delle due sezioni seguenti.

Opzione 1: Definire un modello usando Code First

Questa sezione illustra come creare un modello e il relativo database associato usando Code First. Passare alla sezione successiva (opzione 2: Definire un modello con Database First) se si preferisce usare Database First per generare il modello da un database utilizzando lo strumento di progettazione di Entity Framework

Quando si usa lo sviluppo Code First, in genere si inizia scrivendo classi .NET Framework che definiscono il modello concettuale (dominio).

  • Aggiungere una nuova classe Product al progetto
  • Sostituire il codice generato per impostazione predefinita con il codice seguente:
    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; }
        }
    }
  • Aggiungere una classe Category al progetto.
  • Sostituire il codice generato per impostazione predefinita con il codice seguente:
    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; } }
        }
    }

Oltre a definire le entità, è necessario definire una classe che deriva da DbContext ed espone le proprietà DbSet<TEntity> . Le proprietà DbSet consentono al contesto di conoscere i tipi da includere nel modello. I tipi DbContext e DbSet sono definiti nell'assembly EntityFramework.

Un'istanza del tipo derivato DbContext gestisce gli oggetti entità durante l'esecuzione, che include il popolamento di oggetti con dati di un database, il rilevamento delle modifiche e la persistenza dei dati nel database.

  • Aggiungere una nuova classe ProductContext al progetto.
  • Sostituire il codice generato per impostazione predefinita con il codice seguente:
    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; }
        }
    }

Compilare il progetto.

Opzione 2: Definire un modello con Database First

Questa sezione illustra come usare Database First per generare nuovamente il tuo modello da un database utilizzando lo strumento di progettazione di EF. Se hai completato la sezione precedente (alla Opzione 1: Definire un modello usando Code First), ignora questa sezione e passa direttamente alla sezione Caricamento differito.

Creare un database esistente

In genere, quando si ha come destinazione un database esistente, verrà già creato, ma per questa procedura dettagliata è necessario creare un database per l'accesso.

Il server di database installato con Visual Studio è diverso a seconda della versione di Visual Studio installata:

  • Se stai usando Visual Studio 2010, stai creando un database SQL Express.
  • Se stai utilizzando Visual Studio 2012, allora creerai un database LocalDB.

Andiamo avanti e generiamo il database.

  • Visualizza -> Esplora server

  • Fare clic con il pulsante destro del mouse su Connessioni dati -> Aggiungi connessione...

  • Se non ti sei mai connesso a un database da Esplora server, dovrai selezionare Microsoft SQL Server come origine dati.

    Modificare l'origine dati

  • Connettersi a LocalDB o SQL Express, a seconda di quello installato e immettere Products come nome del database

    Aggiungere un database locale di connessione

    Aggiungere Connection Express

  • Selezionare OK e verrà chiesto se si vuole creare un nuovo database, selezionare

    Creare un database

  • Il nuovo database verrà visualizzato in Esplora server, fare clic con il pulsante destro del mouse su di esso e selezionare Nuova query

  • Copiare il codice SQL seguente nella nuova query, quindi fare clic con il pulsante destro del mouse sulla query e selezionare Esegui

    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

Modello di ingegneria inversa

Si userà Entity Framework Designer, incluso come parte di Visual Studio, per creare il modello.

  • Progetto -> Aggiungi nuovo elemento...

  • Selezionare Dati dal menu a sinistra e quindi ADO.NET Entity Data Model

  • Immettere ProductModel come nome e fare clic su OK

  • Verrà avviata la Procedura guidata Modello di dati di entità

  • Selezionare Genera dal database e fare clic su Avanti

    Scegliere il contenuto del modello

  • Selezionare la connessione al database creato nella prima sezione, immettere ProductContext come nome della stringa di connessione e fare clic su Avanti

    Scegliere la connessione

  • Fare clic sulla casella di controllo accanto a "Tabelle" per importare tutte le tabelle e fare clic su "Fine"

    Scegliere gli oggetti

Dopo aver completato il processo di reverse engineer, il nuovo modello viene aggiunto al progetto e aperto per la visualizzazione in Entity Framework Designer. Al progetto è stato aggiunto anche un file App.config con i dettagli di connessione per il database.

Passaggi aggiuntivi in Visual Studio 2010

Se si lavora in Visual Studio 2010, sarà necessario aggiornare la finestra di progettazione ef per usare la generazione di codice EF6.

  • Fare clic con il pulsante destro del mouse su un punto vuoto del modello in Ef Designer e selezionare Aggiungi elemento generazione codice...
  • Selezionare Modelli online dal menu a sinistra e cercare DbContext
  • Selezionare EF 6.x DbContext Generator per C#, immettere ProductsModel come nome e fare clic su Aggiungi

Aggiornamento della generazione di codice per il data binding

Ef genera codice dal modello usando i modelli T4. I modelli forniti con Visual Studio o scaricati dalla raccolta di Visual Studio sono destinati all'uso generico. Ciò significa che le entità generate da questi modelli hanno semplici proprietà ICollection<T> . Tuttavia, quando si esegue il data binding, è consigliabile avere proprietà di raccolta che implementano IListSource. Questo è il motivo per cui è stata creata la classe ObservableListSource precedente e ora verranno modificati i modelli per usare questa classe.

  • Aprire Esplora soluzioni e trovare il file ProductModel.edmx

  • Trovare il file ProductModel.tt che verrà annidato nel file ProductModel.edmx

    Modello di prodotto

  • Fare doppio clic sul file ProductModel.tt per aprirlo nell'editor di Visual Studio

  • Trovare e sostituire le due occorrenze di "ICollection" con "ObservableListSource". Si trovano a circa le linee 296 e 484.

  • Trovare e sostituire la prima occorrenza di "HashSet" con "ObservableListSource". Questa occorrenza si trova circa alla riga 50. Non sostituire la seconda occorrenza di HashSet trovata più avanti nel codice.

  • Salvare il file ProductModel.tt. Questo dovrebbe provocare la rigenerazione del codice per le entità. Se il codice non viene rigenerato automaticamente, fare clic con il pulsante destro del mouse su ProductModel.tt e scegliere "Esegui strumento personalizzato".

Se si apre ora il file Category.cs (annidato in ProductModel.tt), si noterà che la raccolta Products ha il tipo ObservableListSource<Product>.

Compilare il progetto.

Caricamento pigro

La proprietà Products nella classe Category e nella proprietà Category della classe Product sono proprietà di navigazione. In Entity Framework, le proprietà di navigazione forniscono un modo per navigare una relazione tra due tipi di entità.

Entity Framework consente di caricare automaticamente le entità correlate dal database alla prima volta che si accede alla proprietà di navigazione. Con questo tipo di caricamento (denominato caricamento differito), è importante tenere presente che, la prima volta che si accede a ciascuna proprietà di navigazione, sarà eseguita una query separata sul database se il contenuto non è già presente nel contesto.

Quando si usano tipi di entità POCO, EF ottiene il caricamento differito creando istanze di tipi proxy derivati durante il runtime e quindi per eseguire l'override delle proprietà virtuali nelle classi per aggiungere l'hook di caricamento. Per ottenere il caricamento differito degli oggetti correlati, è necessario dichiarare i metodi getter delle proprietà di navigazione come public e virtual (Overridable in Visual Basic) e la classe non deve essere sealed (NotOverridable in Visual Basic). Quando si usano le proprietà di navigazione Database First vengono automaticamente rese virtuali per abilitare il caricamento differita. Nella sezione Code First abbiamo scelto di rendere virtuali le proprietà di spostamento per lo stesso motivo

Associare l'oggetto ai controlli

Aggiungere le classi definite nel modello come origini dati per questa applicazione WinForms.

  • Dal menu principale selezionare Progetto -> Aggiungi nuova origine dati ... (in Visual Studio 2010 è necessario selezionare Dati -> Aggiungi nuova origine dati...)

  • Nella finestra Scegliere un tipo di origine dati selezionare Oggetto e fare clic su Avanti

  • Nella finestra di dialogo Seleziona Oggetti Dati, espandi WinFormswithEFSample due volte e seleziona Categoria. Non è necessario selezionare l'Origine dati Product, perché accederemo a essa tramite la proprietà Product nell'Origine dati Category.

    Origine dati

  • Fare clic su Fine. Se la finestra Origini dati non viene visualizzata, selezionare Visualizza -> Altre finestre-> Origini dati

  • Premere l'icona a forma di puntina, in modo che la finestra Origini dati non venga nascosta automaticamente. Potrebbe essere necessario premere il pulsante aggiorna se la finestra era già visibile.

    Origine dati 2

  • In Esplora soluzioni, fare doppio clic sul file Form1.cs per aprire il modulo principale in progettazione.

  • Selezionare l'origine dati Categoria e trascinarla sul modulo. Per impostazione predefinita, alla finestra di progettazione vengono aggiunti un nuovo DataGridView (categoryDataGridView) e controlli della barra degli strumenti di navigazione. Questi controlli sono associati ai componenti BindingSource (categoryBindingSource) e Binding Navigator (categoryBindingNavigator) creati anche.

  • Modificare le colonne nella categoriaDataGridView. Si vuole impostare la colonna CategoryId su sola lettura. Il valore per la proprietà CategoryId viene generato dal database dopo il salvataggio dei dati.

    • Fare clic con il pulsante destro del mouse sul controllo DataGridView e scegliere Modifica colonne...
    • Selezionare la colonna CategoryId e impostare ReadOnly su True
    • Premere OK
  • Selezionare Prodotti dall'origine dati Categoria e trascinarlo nel modulo. Il prodottoDataGridView e productBindingSource vengono aggiunti al modulo.

  • Modificare le colonne nel productDataGridView. Vogliamo nascondere le colonne CategoryId e Category e impostare ProductId su sola lettura. Il valore della proprietà ProductId viene generato dal database dopo il salvataggio dei dati.

    • Fare clic con il pulsante destro del mouse sul controllo DataGridView e scegliere Modifica colonne.
    • Selezionare la colonna ProductId e impostare ReadOnly su True.
    • Selezionare la colonna CategoryId e premere il pulsante Rimuovi . Eseguire la stessa operazione con la colonna Categoria .
    • Scegliere OK.

    Finora, i controlli DataGridView sono stati associati ai componenti BindingSource nella finestra di progettazione. Nella sezione successiva si aggiungerà codice al code-behind per impostare categoryBindingSource.DataSource sulla raccolta di entità attualmente rilevate da DbContext. Quando abbiamo trascinato e rilasciato Prodotti da sotto la Categoria, WinForms si è occupato di impostare la proprietà productsBindingSource.DataSource sulla categoryBindingSource e la proprietà productsBindingSource.DataMember su Prodotti. A causa di questa associazione, solo i prodotti che appartengono alla categoria attualmente selezionata verranno visualizzati nel prodottoDataGridView.

  • Abilitare il pulsante Salva sulla barra degli strumenti di spostamento facendo clic sul pulsante destro del mouse e selezionando Abilitato.

    Designer di Form 1

  • Aggiungere il gestore eventi per il pulsante Salva facendo doppio clic sul pulsante. Verrà aggiunto il gestore eventi e ti porterà al code-behind del modulo. Il codice per il gestore eventi categoryBindingNavigatorSaveItem_Click verrà aggiunto nella sezione successiva.

Aggiungere il codice che gestisce l'interazione dei dati

A questo punto si aggiungerà il codice per usare ProductContext per eseguire l'accesso ai dati. Aggiornare il codice per la finestra del modulo principale, come illustrato di seguito.

Il codice dichiara un'istanza a esecuzione prolungata di ProductContext. L'oggetto ProductContext viene utilizzato per eseguire query e salvare i dati nel database. Il metodo Dispose() nell'istanza productContext viene quindi chiamato dal metodo OnClosing sottoposto a override. I commenti del codice forniscono informazioni dettagliate sulle operazioni del codice.

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

Testare l'applicazione Windows Form

  • Compilare ed eseguire l'applicazione ed è possibile testare la funzionalità.

    Modulo 1 prima del salvataggio

  • Dopo il salvataggio, le chiavi generate dallo store vengono visualizzate sullo schermo.

    Modulo 1 dopo il salvataggio

  • Se hai usato Code First, vedrai anche che viene creato automaticamente un database WinFormswithEFSample.ProductContext .

    Esplora risorse server