The easiest way to create business applications for the Desktop and the Cloud
LightSwitch has built-in support for SQL Server and SharePoint data sources. To access other data sources, you can write a custom WCF RIA DomainService. This post will show you how to read and write from an Odata Service by wrapping access to it in a DomainService.
There are a couple limitations on the OData Services that can be exposed using RIA Services and LightSwitch.
While both OData and RIA Services support complex types on entities, LightSwitch does not. If a complex type property is exposed on an entity, LightSwitch will import the entity, ignoring that property. There are a couple of workarounds for this that we will detail in another blog post.
An OData service can contain navigation properties that are not associated with any foreign key. This is likely the case with many-to-many relationships, but can also occur for 0..1-Many or 1-Many relationships. For example, the Netflix OData Catalog contains a many-to-many relationship between Titles and Genre. Unfortunately, RIA Service associations are foreign key based. If an OData association is not foreign key based, there isn't a good way to represent it over a RIA Service.
If an OData service does contain these types of associations, there isn't currently a way to represent these in LightSwitch. However, you can add parameterized queries on your RIA Service that can be called LightSwitch. Using this functionality, queries that represent these unsupported associations could be exposed. For Netflix, for example, you could define GetGenresByTitle and GetTitlesByGenre queries on your RIA Service, which call into the appropriate OData navigation properties.
The basic steps to create an OData wrapper DomainService for LightSwitch are as follows:
Steps 1-5 are covered in the How to create a RIA service wrapper for OData Source post. This blog post will build on and update the DomainService created in that post.
The first blog post assumed that the address of the OData service was hard-coded in your DomainService. We will now modify our class to allow the address to specified when consuming the RIA Service from LightSwitch.
The Add Data Source wizard in LightSwitch will prompt developers for a connection string when adding a DomainService data source. This connection string will be stored in the web.config for the project using the class name of the DomainService. We'll modify our DomainService to get the connection information from the web.config in its Initialize method.
First add references to System.Web and System.Configuration.
Add a Description attribute to the DomainService class. This description will be displayed in the Add Data Source wizard when requesting a connection string from the user.
<Description("Specify the address to the ProductCatalog Service")> _
Public Class ProductService
Modify the Initialize method to check for and use the address specified from LightSwitch.
Public Overrides Sub Initialize(ByVal context As System.ServiceModel.DomainServices.Server.DomainServiceContext)
'Get connection information from the web.config
If Web.Configuration.WebConfigurationManager.ConnectionStrings(GetType(ProductService).FullName) Is Nothing OrElse String.IsNullOrWhiteSpace(Web.Configuration.WebConfigurationManager.ConnectionStrings(GetType(ProductService).FullName).ConnectionString) Then
Throw New Exception("The address to RIA Service must be provided when attaching to this data source from LightSwitch.")
Dim url As String = Web.Configuration.WebConfigurationManager.ConnectionStrings(GetType(ProductService).FullName).ConnectionString
_context = New ProductCatalog.ProductCatalogEntities(New Uri(url))
The Submit method of the DomainService will be called whenever LightSwitch attempts to save any changes for the data source. The Submit method will need to process each changed entity and then save changes to the OData service.
In our OData Service, each Product has an associated Category. To ensure that the reference between Product and Category is correctly maintained, our RIA Service will need to process Categories prior to Products. This is done by re-ordering the set of changed entities prior to processing them. This reordering will need to be customized based on the structure of each OData service. The following class will handle ordering the change set.
Public Class ProductEntitiesComparer
Inherits Comparer(Of Object)
Public Overrides Function Compare(x As Object, y As Object) As Integer
If TypeOf x Is ProductCatalog.Product AndAlso TypeOf y Is ProductCatalog.Category Then
ElseIf TypeOf x Is ProductCatalog.Category AndAlso TypeOf y Is ProductCatalog.Product Then
Once the change set has been reordered, we will need to process each record in the change set. This is achieved by calling the base implementation of Submit. The base implementation of Submit simply calls the separate Update, Create and Delete methods for each entity type. We will provide these next.
After each record has been processed, we need to save the changes to the OData service. Since a given save can include more than one record and these records are dependent on each other, we'll need to save in batch mode.
Public Overrides Function Submit(changeSet As ChangeSet) As Boolean
'Reorder the change set to ensure that categories are processed before products. Products are dependent on categories.
Dim c As New ChangeSet(changeSet.ChangeSetEntries.OrderBy(Function(entry) entry.Entity, New ProductEntitiesComparer()))
Dim baseResult As Boolean = MyBase.Submit(c)
For each method, we first need to attach the Category to DataServiceContext object. For the Update and Delete methods, we also need to specify what operation is occurring on the attached object. The methods are listed below.
Public Sub CreateCategory(ByVal c As ProductCatalog.Category)
'Add the new category to the service reference context
Public Sub UpdateCategory(ByVal c As ProductCatalog.Category)
'Attach the object to the context and specify that it has been updated
Public Sub DeleteCategory(ByVal c As ProductCatalog.Category)
'Attach the object to the context and specify that it has been deleted
These methods are very similar to those for Category. However, for the CreateProduct method, we need to inform the DataServiceContext that there is a relationship (link) between Product and Category. This will ensure that the newly added Product will be correctly associated with a Category. It is necessary to do this on the "1" side of a relationship.
Public Sub CreateProduct(ByVal p As ProductCatalog.Product)
'Add the new product to the service reference context
'Need to set link between Product and Category (to ensure that inserts to the database are ordered correctly)
'For existing categories, get the category first
If p.Category Is Nothing Then
p.Category = _context.Categories.Where(Function(c) c.ID = p.CategoryID).FirstOrDefault()
'Set the link between the product and category
_context.SetLink(p, "Category", p.Category)
Public Sub UpdateProduct(ByVal p As ProductCatalog.Product)
Public Sub DeleteProduct(ByVal p As ProductCatalog.Product)
This method can be extended to an OData Service with an arbitrary number of entity types. The only modifications that need to be made are in the Submit and the Create<Entity> methods. In the Submit method, the change set will need to be re-ordered to ensure that parent types are processed before their children. In the Create<Entity> methods, the links between entity types will need to be specified on the "1" or child end of the relationship.
Thanks for this article.
Do you have a downloadable working sample of this?
Complex Types - There are a couple of workarounds for this that we will detail in another blog post.
That was one year ago - blogs.msdn.com/.../how-to-create-a-ria-service-wrapper-for-odata-source.aspx
Can you make hints about a couple of workarounds for this?
Can you please tell us the workarounds for Complex Types?
Thanks, this helped me to create a RIA wrapper for my own webservices from LS.