Nel precedente post dedicato all'utilizzo di LINQ to SQL con gli ADO.NET Data Services, abbiamo visto come creare un nuovo servizio e di cosa necessitino le entità per essere interrogate sia in query string che da codice gestito, ossia l'attributo DataServiceKey nell'ambito di classi parziali.
Quest'oggi vediamo cosa invece serve per eseguire delle operazioni di inserimento/modifica/eliminazione nei confronti del DataContext, punto di ingresso in LINQ to SQL.
Per prima cosa, i crediti. Il codice che mostrerò in questo post è una traduzione autorizzata da Andrew Conrad di Microsoft, che ha realizzato un'implementazione in C# di ciò che andremo a vedere e che ha reso disponibile sulla MSDN Code Gallery a questo indirizzo. Andrew ha acconsentito alla traduzione (e successiva pubblicazione) di una versione Visual Basic del medesimo codice.
Negli ADO.NET Data Services, i giochi si svolgono attraverso l'interfaccia IUpdatable, che permette di avere a disposizione dei metodi per puntare alle risorse esposte dal servizio e di eseguire nei loro confronti alcune operazioni classiche, come ad esempio il salvataggio dei dati. In altre parole, IUpdatable offre un'infrastruttura per l'invio di richieste di tipo Http POST.
Dobbiamo anche in questo caso dichiarare una classe parziale (in modo da non intaccare il codice autogenerato), questa volta per la nostra classe NorthwindDataContext che fa da riferimento al modello a oggetti. Ci sono alcune direttive Imports preliminari e l'implementazione di IUpdatable:
Imports System.Data.Services
Imports System.Data.Services.Common
Imports System.Data.Linq
Imports System.Reflection
Imports System.Data.Linq.Mapping
Partial Public Class NorthwindDataContext
Implements IUpdatable
A questo punto l'IDE aggiungerà automaticamente le firme dei metodi richiesti dall'interfaccia. Il codice seguente, piuttosto lungo ma corredato da commenti XML per riprodurre fedelmente quanto proposto da Andrew Conrad, completa l'implementazione. Per non complicare troppo l'organizzazione del presente post, potete consultare questa pagina della documentazione ufficiale MSDN per avere una descrizione dei metodi utilizzati, mentre è possibile fare riferimento al contenuto dei commenti:
''' <summary>
''' Adds the given value to the collection
''' </summary>
''' <param name="targetResource">target object which defines the property</param>
''' <param name="propertyName">name of the property whose value needs to be updated</param>
''' <param name="resourceToBeAdded">value of the property which needs to be added</param>
Public Sub AddReferenceToCollection(ByVal targetResource As Object, ByVal propertyName As String, _
ByVal resourceToBeAdded As Object) _
Implements System.Data.Services.IUpdatable.AddReferenceToCollection
Dim pi As PropertyInfo = targetResource.GetType.GetProperty(propertyName)
If pi Is Nothing Then
Throw New Exception("Can't find property")
End If
Dim collection As IList = DirectCast(pi.GetValue(targetResource, Nothing), IList)
collection.Add(resourceToBeAdded)
End Sub
''' <summary>
''' Revert all the pending changes.
''' </summary>
''' <remarks></remarks>
Public Sub ClearChanges() Implements System.Data.Services.IUpdatable.ClearChanges
Throw New NotSupportedException
End Sub
''' <summary>
''' Creates the resource of the given type and belonging to the given container
''' </summary>
''' <param name="containerName">container name to which the resource needs to be added</param>
''' <param name="fullTypeName">full type name i.e. Namespace qualified type name of the resource</param>
''' <returns>object representing a resource of given type and belonging to the given container</returns>
Public Function CreateResource(ByVal containerName As String, ByVal fullTypeName As String) _
As Object Implements System.Data.Services.IUpdatable.CreateResource
Dim t As Type = Type.GetType(fullTypeName, True)
Dim table As ITable = GetTable(t)
Dim resource As Object = Activator.CreateInstance(t)
table.InsertOnSubmit(resource)
Return resource
End Function
''' <summary>
''' Delete the given resource
''' </summary>
''' <param name="targetResource">resource that needs to be deleted</param>
''' <remarks></remarks>
Public Sub DeleteResource(ByVal targetResource As Object) _
Implements System.Data.Services.IUpdatable.DeleteResource
Dim table As ITable = GetTable(targetResource.GetType)
table.DeleteOnSubmit(targetResource)
End Sub
''' <summary>
''' Gets the resource of the given type that the query points to
''' </summary>
''' <param name="query">query pointing to a particular resource</param>
''' <param name="fullTypeName">full type name i.e. Namespace qualified type name of the resource</param>
''' <returns>object representing a resource of given type and as referenced by the query</returns>
Public Function GetResource(ByVal query As System.Linq.IQueryable, ByVal fullTypeName As String) _
As Object Implements System.Data.Services.IUpdatable.GetResource
Dim resource As Object = query.Cast(Of Object).SingleOrDefault()
' fullTypeName can be null for deletes
If fullTypeName IsNot Nothing AndAlso resource.GetType.FullName <> fullTypeName Then
Throw New Exception("Unexpected type for resource")
End If
Return resource
End Function
''' <summary>
''' Gets the value of the given property on the target object
''' </summary>
''' <param name="targetResource">target object which defines the property</param>
''' <param name="propertyName">name of the property whose value needs to be updated</param>
''' <returns>the value of the property for the given target resource</returns>
Public Function GetValue(ByVal targetResource As Object, ByVal propertyName As String) _
As Object Implements System.Data.Services.IUpdatable.GetValue
Dim table As MetaTable = Mapping.GetTable(targetResource.GetType)
Dim member As MetaDataMember = table.RowType.DataMembers.Single(Function(x) x.Name = propertyName)
Return member.MemberAccessor.GetBoxedValue(targetResource)
End Function
''' <summary>
''' Removes the given value from the collection
''' </summary>
''' <param name="targetResource">target object which defines the property</param>
''' <param name="propertyName">name of the property whose value needs to be updated</param>
''' <param name="resourceToBeRemoved">value of the property which needs to be removed</param>
Public Sub RemoveReferenceFromCollection(ByVal targetResource As Object, ByVal propertyName As String, _
ByVal resourceToBeRemoved As Object) _
Implements System.Data.Services.IUpdatable.RemoveReferenceFromCollection
Dim pi As PropertyInfo = targetResource.[GetType]().GetProperty(propertyName)
If pi Is Nothing Then
Throw New Exception("Can't find property")
End If
Dim collection As IList = DirectCast(pi.GetValue(targetResource, Nothing), IList)
collection.Remove(resourceToBeRemoved)
End Sub
''' <summary>
''' Resets the value of the given resource to its default value
''' </summary>
''' <param name="resource">resource whose value needs to be reset</param>
''' <returns>same resource with its value reset</returns>
Public Function ResetResource(ByVal resource As Object) _
As Object Implements System.Data.Services.IUpdatable.ResetResource
Dim t As Type = resource.GetType
Dim table As MetaTable = Mapping.GetTable(t)
Dim dummyResource As Object = Activator.CreateInstance(t)
For Each member In table.RowType.DataMembers
If Not member.IsPrimaryKey AndAlso Not member.IsDeferred AndAlso Not member.IsAssociation _
AndAlso Not member.IsDbGenerated Then
Dim defaultValue As Object = member.MemberAccessor.GetBoxedValue(dummyResource)
member.MemberAccessor.SetBoxedValue(resource, defaultValue)
End If
Next
Return resource
End Function
''' <summary>
''' Returns the actual instance of the resource represented by the given resource object
''' </summary>
''' <param name="resource">object representing the resource whose instance needs to be fetched</param>
''' <returns>The actual instance of the resource represented by the given resource object</returns>
''' <remarks></remarks>
Public Function ResolveResource(ByVal resource As Object) _
As Object Implements System.Data.Services.IUpdatable.ResolveResource
Return resource
End Function
''' <summary>
''' Saves all the pending changes made till now
''' </summary>
''' <remarks></remarks>
Public Sub SaveChanges() Implements System.Data.Services.IUpdatable.SaveChanges
SubmitChanges()
End Sub
''' <summary>
''' Sets the value of the given reference property on the target object
''' </summary>
''' <param name="targetResource">target object which defines the property</param>
''' <param name="propertyName">name of the property whose value needs to be updated</param>
''' <param name="propertyValue">value of the property</param>
Public Sub SetReference(ByVal targetResource As Object, ByVal propertyName As String, _
ByVal propertyValue As Object) Implements System.Data.Services.IUpdatable.SetReference
CType(Me, IUpdatable).SetValue(targetResource, propertyName, propertyValue)
End Sub
''' <summary>
''' Sets the value of the given property on the target object
''' </summary>
''' <param name="targetResource">target object which defines the property</param>
''' <param name="propertyName">name of the property whose value needs to be updated</param>
''' <param name="propertyValue">value of the property</param>
Public Sub SetValue(ByVal targetResource As Object, ByVal propertyName As String, _
ByVal propertyValue As Object) Implements System.Data.Services.IUpdatable.SetValue
Dim table As MetaTable = Mapping.GetTable(targetResource.GetType)
Dim member As MetaDataMember = table.RowType.DataMembers.Single(Function(x) x.Name = propertyName)
member.MemberAccessor.SetBoxedValue(targetResource, propertyValue)
End Sub
End Class
Ora abbiamo tutto ciò che occorre e possiamo utilizzare le medesime tecniche che abbiamo imparato finora, lato client. A puro titolo esemplificativo, il seguente codice ottiene l'elenco degli ordini eseguiti da un certo cliente:
Sub GetSomeOrders()
Dim query = From ord In NorthwindContext.Orders _
Where ord.CustomerID = "ALFKI" _
Select ord
For Each ord In query
Console.WriteLine(ord.OrderID)
Next
Console.ReadLine()
End Sub
Mentre il seguente snippet aggiunge un nuovo ordine e lo relaziona a uno specifico cliente:
Sub AddNewOrder()
Dim ord As New Order
With ord
.Customer = NorthwindContext.Customers.Where(Function(cust) cust.CustomerID = "ALFKI").First
.OrderDate = DateTime.Today
.ShippedDate = DateTime.Today
.ShipCountry = "Italy"
.ShipCity = "Cremona"
End With
NorthwindContext.SetLink(ord, "Customer", ord.Customer)
NorthwindContext.AddToOrders(ord)
NorthwindContext.SaveChanges()
End Sub
Le tecniche non cambiano poiché noi andiamo a rapportarci sempre con l'istanza della classe DataService(Of T), che lato client dichiariamo sempre in questo modo:
Dim NorthwindContext As New NorthwindDataContext(New Uri("http://localhost:4693/NorthwindDataService.svc"))
La versione 1.5 degli ADO.NET Data Services, stando a quanto letto nel blog di Andrew Conrad, dovrebbe includere il supporto per provider diversi da ADO.NET Entity Framework senza implementare manualmente IUpdatable. Nel caso però fosse necessario, è bene sapere come fare.
Nel ringraziare Andrew Conrad per la disponibilità, potete scaricare un file di codice Visual Basic contenente la suesposta implementazione dall'area Download di Visual Basic Tips & Tricks a questo indirizzo.
Alessandro