Nel precedente articolo abbiamo usato il database Northwind sfruttando le tabelle Customers, Orders e Order Details. Lo schema delle tabelle è quello riportato qui sotto. L'immagine è prelevata dal designer di Visual Studio che permette di manipolare gli oggetti creati dal wizard di Visual Studio.
Il contesto
Come abbiamo visto nell'articolo precedente, il contesto ha il compito in fase di ricerca dati di fornire un punto di accesso per le query e durante la sua vita deve mantenere traccia di tutte le modifiche effettuate a questi riportandole poi sul database. Nel caso di questo articolo, la seconda caratteristica è sicuramente quella che più ci interessa.
All'interno di Entity Framework il contesto è rappresentato dalla classe ObjectContext. Questa classe ha diverse funzionalità, ma quella che ci interessa è rappresentata dalla proprietà ObjectStateManager. Questa proprietà, di tipo ObjectStateManager, contiene i valori originali di tutte le entità che sono state caricate in memoria ed il loro stato attuale.
Esistono due diversi modi per caricare un'entità nel contesto; il primo è eseguire una query che ritorna entità intere (gli oggetti restituiti da query che fanno proiezioni non vengono presi in considerazione) ed il secondo è aggiungere a mano uno o più oggetti sfruttando i metodi della classe ObjectContext.
Ogni entità caricata nel contesto ha uno stato. Lo stato indica se l'entità è da aggiungere, modificare, cancellare o è invariata. E' grazie allo stato che il contesto riesce a capire quali entità devono essere persistite sul database.
Quando un'entità viene caricata tramite query, automaticamente viene messa nello stato di Unchanged e quindi nel momento di salvare non viene presa in considerazione. Nel momento in cui un'entità viene modificata, passa dallo stato di Unchanged a quello di Modified segnalando quindi che i nuovi valori devono essere aggiornati sul database.
Per inserire a mano un'entità nel contesto, ci sono due metodi a seconda che l'oggetto debba essere inserito o modificato sul database. Il primo passo ovviamente consiste nel creare un'istanza dell'oggetto e poi, a seconda della funzione, invocare l'API corretta che è AddObject per inserire l'oggetto nello stato di Added (quindi verrà persistito come nuovo record) e Attach o AttachTo per inserire l'oggetto in stato di Unchanged.
Infine, per impostare un oggetto nello stato di Deleted, bisogna prima attaccarlo al contesto, recuperandolo dal database con una query o utilizzando uno dei metodi di Attach, e poi invocare l'API DeleteObject per cambiarne lo stato in Deleted.
Quando si devono persistere tutte le modifiche effettuate agli oggetti sul database (per modifiche si intendono aggiunte, modifiche e cancellazioni), bisogna invocare l'API SaveChanges. Questo metodo scorre tutti gli oggetti presenti nell'ObjectStateManager e ne verifica lo stato in base al quale decide che tipo di operazione lanciare sul database per ognuno di essi.
Cominciamo adesso a vedere come effettuare le varie operazioni di aggiornamento.
Inserire dati nel database
Fondamentalmente, ad ogni oggetto corrisponde una riga su una o più tabelle nel database. I passi quindi da effettuare per aggiungere i dati di un oggetto al database sono:
- Creazione dell'oggetto con i dati necessari all'inserimento;
- Aggiunta dell'oggetto al contesto;
- Invocazione del metodo di salvataggio dei dati sul database.
Come primo esempio vediamo come persistere un nuovo cliente nel database.
La fase di creazione dell'oggetto in questo caso prevede l'istanziazione di un oggetto di tipo Customer e la valorizzazione delle proprietà che verranno poi riversate sul database.
La seconda fase consiste nell'instanziazione e nell'aggiunta dell'oggetto da persistere al contesto. In questo caso, non si deve ritrovare alcun oggetto in quanto stiamo facendo un inserimento e quindi dobbiamo semplicemente chiamare il metodo AddObject passando in input in nome dell'entità che stiamo aggiungendo e poi l'entità stessa. Per semplificare le cose, il wizard che auto genera le classi dal database crea una classe che eredita da ObjectContext e vi inserisce dei metodi già tipizzati pronti da usare. Nel nostro caso, il metodo AddToCustomerSet accetta in input già un oggetto Customer ed internamente fa lui la chiamata al metodo AddObject con i parametri corretti.
L'ultima fase è l'invocazione del metodo SaveChanges che si occupa di persistere gli oggetti sul database.
Customer c = new Customer { CustomerID = "NEWCO", Address = "Address", City = "City", CompanyName = "Name", ContactName = "ContactName", ContactTitle = "ContactTitle", Country = "Country", Fax = "FAX", Phone = "PHONE", PostalCode = "PostalCode", Region = "Region" }; using (northwindEntities ctx = new northwindEntities()) { ctx.AddToCustomerSet(c); ctx.SaveChanges(); }
Inserire un cliente è una cosa abbastanza banale perché coinvolge una sola entità senza alcuna relazione. L'inserimento di un ordine è un'operazione molto più complessa in quanto coinvolge sia l'ordine, sia i suoi dettagli che il cliente che l'ha piazzato. Ci sono due strade che si possono percorrere per realizzare questa funzionalità, analizziamole entrambe.
La prima strada prevede che quando si valorizzano le proprietà della classe Order, venga valorizzata anche la proprietà Customer con un oggetto Customer che contiene il solo ID del cliente da associare all'ordine. Per quando riguarda i dettagli, la classe Order ha una proprietà Order_Details, di tipo EntityCollection<Order_Detail>, che rappresenta i dettagli legati all'ordine quindi basta una chiamata al metodo Add per ogni dettaglio ed il gioco è fatto. Quando si deve persistere l'ordine, si utilizza il metodo AddToOrderSet passando in input l'oggetto Order e si invoca il metodo SaveChanges().
Questo sembrerebbe un approccio semplice ma c'è un problema. Poiché si utilizza il metodo AddObject per inserire l'ordine, il contesto inserisce anche il cliente come oggetto da aggiungere e poiché il cliente già esiste, viene generata un'eccezione a runtime. Per far funzionare questo approccio, bisogna creare un'istanza della classe Customer e attaccarla al contesto. Successivamente, si deve creare l'ordine, senza associarlo al cliente, e associarlo al contesto. Infine, si associa l'ordine al cliente. Questa associazione differita del cliente all'ordine è necessaria poiché si rischierebbe di attaccare due volte lo stesso cliente al contesto generando un'eccezione a runtime. Inoltre, essendo il cliente attaccato al contesto come Unchanged, non viene considerato dal metodo SaveChanges per gli aggiornamenti.
Customer c = new Customer { CustomerID = "ALFKI" }; Order o = new Order { ShipAddress = "Address", ShipCity = "City", ShipCountry = "Country", ShipName = "Name", ShipPostalCode = "ZipCode", ShipRegion = "Region", OrderDate = DateTime.Now }; o.Order_Details.Add( new Order_Detail { Discount = 0, ProductID = 1, Quantity = 3, UnitPrice = (decimal)3.4 }); o.Order_Details.Add( new Order_Detail { Discount = 0, ProductID = 2, Quantity = 5, UnitPrice = (decimal)50 }); o.Order_Details.Add( new Order_Detail { Discount = 0, ProductID = 3, Quantity = 10, UnitPrice = (decimal)20 }); using (northwindEntities ctx = new northwindEntities()) { ctx.AttachTo("northwindEntities.CustomerSet", c); ctx.AddToOrderSet(o); o.Customer = c; ctx.SaveChanges(); }
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Persistere la ChatHistory di Semantic Kernel in ASP.NET Core Web API per GPT
Ottimizzare il mapping di liste di tipi semplici con Entity Framework Core
Filtrare i dati di una QuickGrid in Blazor con una drop down list
Eseguire una query su SQL Azure tramite un workflow di GitHub
Filtering sulle colonne in una QuickGrid di Blazor
Utilizzare Azure Cosmos DB con i vettori
Change tracking e composition in Entity Framework
Le novità di Entity Framework 8
Usare le navigation property in QuickGrid di Blazor
Utilizzare QuickGrid di Blazor con Entity Framework
Utilizzare il metodo CountBy di LINQ per semplificare raggruppamenti e i conteggi
Utilizzare Copilot con Azure Cosmos DB