How to Create a RIA Service Wrapper for an Editable OData Source

How to Create a RIA Service Wrapper for an Editable OData Source

Rate This
  • Comments 4

Introduction

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.

Complex Types

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.

Navigation Properties without Foreign Keys

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:

  1. Create a class library project
  2. Add a service reference to your Odata service
  3. Add a WCF DomainService to your project
  4. Add a metadata class to provide appropriate information to LightSwitch about the classes exposed by the service reference
  5. Add query methods to your DomainService to expose each entity class on the OData service
  6. Add methods to your DomainService to Create, Update and Delete each entity class

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.

Allowing LightSwitch To Specify Connection Information

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.

image

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

        Inherits DomainService

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)

        MyBase.Initialize(context)

 

        '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.")

        Else

            Dim url As String = Web.Configuration.WebConfigurationManager.ConnectionStrings(GetType(ProductService).FullName).ConnectionString

            _context = New ProductCatalog.ProductCatalogEntities(New Uri(url))

        End If

    End Sub

Providing a Submit Method for the RIA Service

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

                Return 1

            ElseIf TypeOf x Is ProductCatalog.Category AndAlso TypeOf y Is ProductCatalog.Product Then

                Return -1

            Else

                Return 0

            End If

        End Function

    End Class

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)

        _context.SaveChanges(Services.Client.SaveChangesOptions.Batch)

        Return True

    End Function

Providing the Create, Update and Delete Methods for Category

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

        _context.AddObject("Categories", c)

    End Sub

 

    Public Sub UpdateCategory(ByVal c As ProductCatalog.Category)

        'Attach the object to the context and specify that it has been updated

        _context.AttachTo("Categories", c)

        _context.UpdateObject(c)

    End Sub

 

    Public Sub DeleteCategory(ByVal c As ProductCatalog.Category)

        'Attach the object to the context and specify that it has been deleted

        _context.AttachTo("Categories", c)

        _context.DeleteObject(c)

    End Sub

Providing the Create, Update and Delete Methods for Product

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

        _context.AddToProducts(p)

 

        '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()

        End If

 

        'Set the link between the product and category

        _context.SetLink(p, "Category", p.Category)

    End Sub

 

    Public Sub UpdateProduct(ByVal p As ProductCatalog.Product)

        'Attach the object to the context and specify that it has been updated

        _context.AttachTo("Products", p)

        _context.UpdateObject(p)

    End Sub

 

    Public Sub DeleteProduct(ByVal p As ProductCatalog.Product)

        'Attach the object to the context and specify that it has been deleted

        _context.AttachTo("Products", p)

        _context.DeleteObject(p)

    End Sub

Conclusion

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!

Leave a Comment
  • Please add 7 and 4 and type the answer here:
  • Post
  • Thanks for this article.

    Do you have a downloadable working sample of this?

    Thanks,

    Dave

  • 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?

    Thank you.

  • Thanks, this helped me to create a RIA wrapper for my own webservices from LS.

Page 1 of 1 (4 items)