Data binding con WPF

Importante

Questo documento è valido solo per WPF in .NET Framework

Questo documento descrive l'associazione dati per WPF in .NET Framework. Per i nuovi progetti .NET Core, è consigliabile usare EF Core anziché Entity Framework 6. La documentazione relativa al databinding in EF Core è disponibile qui: Introduzione a WPF.

Questa procedura dettagliata illustra come associare tipi POCO ai controlli WPF in un modulo "master-detail". L'applicazione usa le API di 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 alla relazione uno-a-molti: Category (principal\master) e Product (dependent\detail). Gli strumenti di Visual Studio vengono quindi usati per associare i tipi definiti nel modello ai controlli WPF. Il framework di data binding WPF consente la navigazione tra oggetti correlati: la selezione di righe nella visualizzazione master determina l'aggiornamento della visualizzazione dettagli 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.

Usare l'opzione 'Object' per la creazione di origini dati WPF

Con la versione precedente di Entity Framework è stato usato per usare l'opzione Database durante la creazione di una nuova origine dati basata su un modello creato con Entity Designer. Ciò è dovuto al fatto che la finestra di progettazione genera un contesto derivato da ObjectContext e dalle classi di entità derivate da EntityObject. L'uso dell'opzione Database consente di scrivere il codice migliore per interagire con questa superficie dell'API.

Ef Designer per Visual Studio 2012 e Visual Studio 2013 generano un contesto che deriva da DbContext insieme a semplici classi di entità POCO. Con Visual Studio 2010 è consigliabile eseguire lo scambio con un modello di generazione di codice che usa DbContext come descritto più avanti in questa procedura dettagliata.

Quando si usa l'area API DbContext, è consigliabile usare l'opzione Object quando si crea una nuova origine dati, come illustrato in questa procedura dettagliata.

Se necessario, è possibile ripristinare la generazione di codice basata su ObjectContext per i modelli creati con Entity Framework Designer.

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

  • Aprire Visual Studio.
  • File -> Nuovo -> Progetto....
  • Selezionare Windows nel riquadro sinistro e WPFApplication nel riquadro destro
  • Immettere WPFwithEFSample 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).

    Nota

    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.

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 invertire il modello da un database usando la finestra 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 a WPFwithEFSample:
    • Fare clic con il pulsante destro del mouse sul nome del progetto
    • Selezionare Aggiungi, quindi Nuovo elemento
    • Selezionare Classe e immettere Product per il nome della classe
  • Sostituire la definizione della classe Product con il codice seguente:
    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; }
        }
    }
  • Aggiungere una classe Category con la definizione seguente:
    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; }
        }
    }

La proprietà Products nella classe Category e nella proprietà Category della classe Product sono proprietà di navigazione. In Entity Framework le proprietà di spostamento consentono di spostarsi tra due tipi di entità per spostarsi tra due tipi di entità.

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

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 con la definizione seguente:
    using System.Data.Entity;

    namespace WPFwithEFSample
    {
        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 invertire il modello da un database usando la finestra di progettazione di Entity Framework. Se è stata completata la sezione precedente (opzione 1: Definire un modello usando Code First), ignorare questa sezione e passare direttamente alla sezione Caricamento differita.

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 si usa Visual Studio 2010, si creerà un database SQL Express.
  • Se si usa Visual Studio 2012, si creerà un database Local DB.

Procedere e generare il database.

  • Visualizzazione -> Esplora server

  • Fare clic con il pulsante destro del mouse su Data Connessione ions -> Aggiungi Connessione ion...

  • Se non si è connessi a un database da Esplora server prima di dover selezionare Microsoft SQL Server come origine dati

    Change Data Source

  • Connessione a Local DB o SQL Express, a seconda di quale sia stato installato e immettere Products come nome del database

    Add Connection LocalDB

    Add Connection Express

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

    Create 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 reverse engineer

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 Creazione guidata modello di dati di entità

  • Selezionare Genera dal database e fare clic su Avanti

    Choose Model Contents

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

    Choose Your Connection

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

    Choose Your Objects

Dopo aver completato il processo di reverse engineer, il nuovo modello viene aggiunto al progetto e aperto per la visualizzazione in Entity Framework Designer. È stato aggiunto anche un file App.config al progetto 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 usando WPF, è consigliabile usare ObservableCollection per le proprietà della raccolta in modo che WPF possa tenere traccia delle modifiche apportate alle raccolte. A questo scopo, verranno modificati i modelli per l'uso di ObservableCollection.

  • Aprire il Esplora soluzioni e trovare il file ProductModel.edmx

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

    WPF Product Model Template

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

  • Trovare e sostituire le due occorrenze di "ICollection" con "ObservableCollection". Si trovano approssimativamente alle linee 296 e 484.

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

  • Trovare e sostituire l'unica occorrenza di "System.Collections.Generic" con "System.Collections.ObjectModel". Si trova approssimativamente alla riga 424.

  • Salvare il file ProductModel.tt. Questo dovrebbe causare 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 l'insieme Products ha il tipo ObservableCollection<Product>.

Compilare il progetto.

Caricamento lazy

La proprietà Products nella classe Category e nella proprietà Category della classe Product sono proprietà di navigazione. In Entity Framework le proprietà di spostamento consentono di spostarsi tra due tipi di entità per spostarsi 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 differita), tenere presente che la prima volta che si accede a ogni proprietà di navigazione verrà 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 differita creando istanze di tipi proxy derivati durante il runtime e quindi eseguendo l'override delle proprietà virtuali nelle classi per aggiungere l'hook di caricamento. Per ottenere il caricamento differita di oggetti correlati, è necessario dichiarare i getter della 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 si è 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 WPF.

  • Fare doppio clic su MainWindow.xaml in Esplora soluzioni per aprire il modulo principale

  • 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 gli oggetti dati espandere WPFwithEFSample due volte e selezionare Categoria
    Non è necessario selezionare l'origine dati Product, perché verrà visualizzata tramite la proprietà Product nell'origine dati Category

    Select Data Objects

  • Fare clic su Fine.

  • La finestra Origini dati viene aperta accanto alla finestra MainWindow.xaml 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.

    Data Sources

  • Selezionare l'origine dati Categoria e trascinarla nel form.

Quando è stata trascinata questa origine, si è verificato quanto segue:

  • La risorsa categoryViewSource e il controllo categoryDataGrid sono stati aggiunti a XAML
  • La proprietà DataContext nell'elemento Grid padre è stata impostata su "{StaticResource categoryViewSource }". La risorsa categoryViewSource funge da origine di associazione per l'elemento Grid outer\parent. Gli elementi Grid interni ereditano quindi il valore DataContext dalla griglia padre (la proprietà ItemsSource di categoryDataGrid è impostata su "{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>

Aggiunta di una griglia dettagli

Ora che è disponibile una griglia per visualizzare categorie, aggiungere una griglia dei dettagli per visualizzare i prodotti associati.

  • Selezionare la proprietà Products dall'origine dati Category e trascinarla nel form.
    • La risorsa categoryProductsViewSource e la griglia productDataGrid vengono aggiunte a XAML
    • Il percorso di associazione per questa risorsa è impostato su Products
    • Il framework di data binding WPF garantisce che solo i prodotti correlati alla categoria selezionata siano visualizzati in productDataGrid
  • Dalla casella degli strumenti trascinare Button on nel form. Impostare la proprietà Name su buttonSalva e la proprietà Content su Salva.

Il modulo dovrebbe essere simile al seguente:

Designer Form

Aggiungere codice che gestisce l'interazione dei dati

È il momento di aggiungere alcuni gestori eventi alla finestra principale.

  • Nella finestra XAML fare clic sull'elemento <Window , che seleziona la finestra principale

  • Nella finestra Proprietà scegliere Eventi in alto a destra, quindi fare doppio clic sulla casella di testo a destra dell'etichetta Caricata

    Main Window Properties

  • Aggiungere anche l'evento Click per il pulsante Salva facendo doppio clic sul pulsante Salva nella finestra di progettazione.

Verrà ora modificato il codice per usare ProductContext per eseguire l'accesso ai dati. Aggiornare il codice per MainWindow, 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. L'oggetto Dispose() nell'istanza di ProductContext viene quindi chiamato dal metodo OnClosing sottoposto a override. I commenti del codice forniscono informazioni dettagliate sulle operazioni del codice.

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

    }

Testare l'applicazione WPF

  • Compilare l'applicazione ed eseguirla. Se è stato usato Code First, si noterà che viene creato automaticamente un database WPFwithEFSample.ProductContext .

  • Immettere un nome di categoria nella griglia superiore e i nomi dei prodotti nella griglia inferiore Non immettere nulla nelle colonne ID, perché la chiave primaria viene generata dal database

    Main Window with new categories and products

  • Premere il pulsante Salva per salvare i dati nel database

Dopo la chiamata a SaveChanges() di DbContext, gli ID vengono popolati con i valori generati dal database. Poiché è stato chiamato Refresh() dopo SaveChanges() i controlli DataGrid vengono aggiornati anche con i nuovi valori.

Main Window with IDs populated

Risorse aggiuntive

Per altre informazioni sul data binding alle raccolte tramite WPF, vedere questo argomento nella documentazione di WPF.