ComplexTypes in RIA Services
Our team has just released WCF RIA Services V1.0 SP1 Beta and WCF RIA Services Toolkit October 2010! You can also access these download links from https://silverlight.net/riaservices. In addition to lots of bug fixes, the release also includes several updates which address some of the top pain points reported by our users. I’d like to use this post to talk about just one of these : the new support we’ve added for complex types. You can download a sample application for complex types here. We’ll be exploring this sample in detail below.
To summarize briefly, the feature adds support for non-Entity complex types (CTs) to the framework. Previously the only structured types we supported and code-genned were Entities. Now you can define types like Currency or Address and use those as Entity properties or parameters into or return values from DomainService methods. Some of the feature highlights:
- Codegen – full codegen support for CTs giving you corresponding client proxies for your types. The codegen is just as rich as it is for entities (partial extensibility methods, validation metadata, etc.). Codegenned CTs derive from a new ComplexObject base class.
- Metadata support – as with entities, you can apply additional metadata on the server via buddy classes to your ComplexTypes, and that metadata will flow through the system. Similarly, we also infer DAL metadata for ComplexTypes (e.g. inferring StringLengthAttribute for members based on your EF model). CTs participate in the metadata pipeline the same way entities do.
- Deep Validation – validation is performed as CT members are modified in the same way that it is for entity members. In addition, validation errors for nested CT members propagate up the containment hierarchy. Deep submit time validation is performed on both tiers for entity CT members.
- Change tracking – entity CT members participate in entity change tracking and the rest of the Accept/RejectChanges pipeline. As nested CT members are changed, those changes are reported up hierarchically causing the parent entity to become dirty. If changes are rejected on the entity, all nested CT changes are reverted.
- Edit Sessions – entity CT members participate in entity edit sessions. If a BeginEdit is performed on an entity, the state snapshot for the entity includes all nested CT state recursively. Similarly if Cancel/EndEdit are performed, the changes are applied recursively. Additionally, ComplexObject itself implements IEditableObject, so you get full edit session support for CTs not hosted by entities.
- ComplexType Parameters – CTs (or collections of CTs) can be passed as parameters to Invoke operations, returned from them, etc. They can also be passed as custom update method parameters.
Lets take a quick walkthrough of the sample app and see some of the above items in action. The below content is also included in the ComplexTypesSample.doc included with the sample. The sample demonstrates use of the new ComplexTypes feature in a variety of scenarios including:
- Use of EntityFramework mapped complex types in the data model, both as nested Entity members as well as mapped stored procedure return types
- Use of POCO complex types as parameters to DomainService invoke operations
- Use of complex types as return values from DomainService invoke operations
Application Details
The sample app was created using the Silverlight Business Application Template. The Order Management page allows you to select a customer, at which time the orders for that customer are asynchronously loaded in a master/details view:
The Customer Management page shows a master/detail view of Customers, and also displays the order history for the selected customer. These results are loaded on demand as customers are selected by executing an Invoke operation mapped to an EF stored procedure:
ComplexTypes in the EntityFramework Data Model
Opening the Northwind.edmx file in the server project Models folder you can see how several EF complex types have been mapped in the model. The EF model browser shows the two types:
The Address CT is shared by both the Customer Entity as well as the Order entity, where the address details for each are mapped to the CT. Creation of a new complex type can be done in the EF designer simply by selecting one or more properties of an entity and selecting “Refactor into new Complex Type” context menu option. After this is done the set of properties will now be exposed by the entity as a single CT property:
You can then use the Mapping Details window to map the database columns to your CT properties:
In a similar way, you can also map a sproc return Type to a CT. In this sample I mapped the CustOrdHistory sproc to a new CT Type CustomerOrderHistoryResult:
This sproc is used in the sample app exposed by the CustomerManagement service GetCustomerOrderHistory operation.
ComplexType usage in RIA Services
The generated Northwind ObjectContext for the above model is used in this sample by both the CustomerManagement and OrderManagement DomainServices. RIA Services recognizes these types as ComplexTypes and uses this information during codegen to generate the corresponding proxy Types, members and methods. For example, in the client project an Address class has been generated that derives from ComplexObject:
The Order and Customer entities have corresponding ComplexObject properties generated. You’ll also notice that the metadata pipeline for CTs behaves the same way it does for entities. For example, you can apply additional metadata via a “buddy class”. In addition, some of the EF model metadata also flows to the client. Below you can see both a Max Length model attribute as well as a buddy class DisplayAttribute being carried to the client codegen:
Standard Create/Update/Delete Functionality
The Order.ShipAddress and Customer.Address CT properties participate in updates as you’d expect. The client detects modifications, performs cross tier validation, etc. On the server, an updated entity with CT member updates is attached normally.
ComplexType Parameters in DomainService Operations
ComplexTypes can be passed as parameters to Invoke and Custom Update operations, and can be returned from Invoke operations. In the sample, the CustomerOrderHistory sproc is exposed in the CustomerManagement service via an Invoke operation and it returns a collection of CTs:
This DomainService method results in a CustomerOrerHistoryResult ComplexObject being generated on the client, along with a corresponding Invoke operation on the CustomerManagement DomainContext:
As an example of POCO CTs that aren’t mapped in the EntityFramework model, this sample has two examples. First the UserRegistrationService RegistrationData class has been updated to be a CT:
These are actually a product changes that we’ll be making – to make our BAT use the ComplexTyeps feature. The BAT itself was forced to employ several workarounds due to the lack of ComplexType support in the past. On the client this is codegenned as a CT deriving from ComplexObject. The full IEditableObject and Validation behavior provided by the ComplexObject base class can be seen in the UI:
Similarly the LoginInfo Type used on the login screen has also been made a ComplexObject. These modifications to the standard Business Application Template (BAT) to use ComplexTypes will be made in the actual product soon. This will allow us to remove several workarounds the BAT currently employs.
ComplexType UI Binding
In general the binding story for CT properties is straight forward – in your xaml you simply use dotted paths to reference the nested CT properties. Since ComplexObject implements the right set of binding interfaces (INotifyPropertyChanged, IEditableObject, INotifyDataErrorInfo), you get rich UI functionality. Below the DataForm xaml and resulting UI are shown for the bound members of Order.ShipAddress:
Since DataForm wasn’t designed to do deep change tracking on nested instances, it doesn’t work properly in update scenarios where you’re modifying nested CT properties. The underlying issue is that the DataForm only tracks top level properties to determine if the current instance has been modified in the session. This will result in the commit/cancel buttons not behaving properly. This sample works around this by configuring the DataForm in a certain way (AutoEdit="False" AutoCommit="True" CommandButtonsVisibility="Edit, Cancel"). You can also create your own update UI bound to your CTs and control the entire experience yourself.
The sample also demonstrates the validation behavior of ComplexObject in nesting scenarios. In the below scenario, the Order.ShipAddress.PostalCode member has been set to an invalid value. You see the error reported on the control itself since Address instance implements INDEI which the control is bound to. In addition, the error summary control is bound to INDEI on the Order instance – this demonstrates that nested CT instances report their validation attributes up to their parent.
Cross-Tier Validation
Above you saw client side validation occurring as properties were set. As with entities, all this validation also runs server side. In addition to validation that runs on both tiers, you can also have server side only validation (declarative or imperative). In the sample, the UpdateCustomer method the Customer.Address.Country member is validated:
Notice that on the server the dotted path to the invalid member is specified. This validation error will be sent back to the client, and the error will be applied down the hierarchy, ultimately registering on the Customer.Address.Country member which will cause the UI to display the server error:
IEditableObject Behavior
Since ComplexObject implements IEO, when bound to a DataForm or other control providing edit sessions based on this interface, changes can be committed/cancelled easily. Below, the current Order has been put into edit mode and a property has been changed. Hitting the Cancel button will cancel the edit and the value will be reverted. To commit the edit, navigate to another Order (again working around the issue that the DataForm Commit button doesn’t work for nested CT edits).
SubmitChanges/RejectChanges
Once one or more edits have been made, the Save/Reject buttons will become enabled. You can set a breakpoint on the server to see that the nested CT instance is sent as part of the changeset and will be populated in both the current and original instances passed to your update method.
As with entities, the RoundtripOriginalAttribute governs which properties are roundtripped to the server as part of the original object. You must either mark the members of your ComplexType as ConcurrencyMode=Fixed in your EF model (in which case the FX will infer RTO for you), or via the buddy class:
In addition to setting the concurrency mode on the individual members of the ComplexType, you must also apply RoundtripOriginalAttribute on the Order.ShipAddress property itself. See the Order and Customer buddy metadata classes in the sample.
In summary, if your RIA Services application development has been hampered in the past by lack of this support and you’ve been forced to implement ugly workarounds, let us know how the feature addresses your scenarios and give us feedback!
Comments
Anonymous
October 31, 2010
Great, thanks so much for this update to Ria Services. It is one that I have been waiting for! GregAnonymous
November 01, 2010
Great and indispensable feature ! Thanks ! But, why we can't pass any serializable object (a shared object between client and server, not an Entity or a ComplexObject) as parameters to Invoke and Custom Update operations ?Anonymous
November 02, 2010
Kakone - the framework has the type restrictions it does because we want to ensure that all types used will work well with all the rest of the framework, for example LINQ queries. Most of the base types are supported, and we have a few yet we'd like to add (e.g. DateTimeOffset). Serialization "just work"s for these types, but we have to modify query serialization/deserialization to get full query support for such types.Anonymous
November 02, 2010
Mathew - I can understand the type restrictions for the results of Query operations (so, for parameter of Insert, Update and Delete operations) but not as parameters of Query or Invoke operations. Each time I need to pass some search parameters to a Query method (I think it happens very often in every business application ;-)), I didn't find another solution than serialize them to a string (and it is horrible to have to do that...) But, for all the rest, I like RIA Services !Anonymous
November 03, 2010
The reason for the stricter restrictions on parameter types for query methods is that they need to be representable in the GET request URL. Invoke operations are less restrictive in their parameter types.Anonymous
November 17, 2010
Do you have to install the Toolkit as well as SP1? Whenever I install the tooklit VS2010 becomes unstable and goes into a "Not Responding" state whenever I move thru the UI.Anonymous
November 18, 2010
Nathanh - No the toolkit isn't required for this sample. If you're having install issues with the toolkit, I recommend sharing/reporting your issue on our forum (forums.silverlight.net/.../53.aspx).Anonymous
December 10, 2010
What does the "INDEI" term in the .docx file refer to? I.e., "...the error summary control is bound to INDEI..."Anonymous
December 10, 2010
INDEI = INotifyDataErrorInfo :)Anonymous
January 12, 2011
Mathew, To pick up on something Kakone said: Is it not possible to pass complex types to Query RIA methods? Please see my full question here: forums.silverlight.net/.../512818.aspx Thanks, PaulAnonymous
January 13, 2011
PaulR - In V1 Query methods don't take entity parameters (but Invoke operations can). This was primarily because Query methods are GET by default (invoke operations are POST), which would require the parameters to be serialized to the URL. We were concerned with large URL sizes. When adding the CT feature we followed this existing restriction for CTs as well. To support CT parameters to query methods, we'd also then have to support collections of CTs, which could easily result in exceeding the max URL length. In the future we'll look at what we can do to remove this restriction in a safe way, but we didn't have time to do that in SP1.Anonymous
January 13, 2011
Mathew or anybody else, Does this added feature of ria services makes it easier to reuse ria services in WP7. The problem I have now is that entity relations are not generated on the client side when using the soap endpoint. See my question on the ria service forum. forums.silverlight.net/.../513128.aspx Thx in advance Stijn LiesenborghsAnonymous
January 20, 2011
Was just testdriving RIA services in combination with EF ans Silverlight, and this feature is JUST what I was I needed. Green light for a huge new piece of cloud software on my side! Thanx!Anonymous
January 20, 2011
I've added a function import and created a new complex type based on my stored procedure. However when I generate the domain service and metadata I see no change in the generated code related to my sproc. I've installed ria sp1 beta. How can I confirm that sp1 is installed and is being used? The generated code looks just like it did before the installation. I posted this question on StackOverflow but have gotten no response. stackoverflow.com/.../using-wcf-ria-services-sp1-how-do-i-consume-a-stored-procedure-via-an-entity-fram Something is not working for me. I found a pretty good step-by-step post but I'm wondering if it was written before sp1 was released? onmick.com/.../Pulling-Data-from-Stored-Procedures-in-WCF-RIA-Services-for-Silverlight.aspx Any help is appreciated.Anonymous
February 16, 2011
With this complex type support can we have stored proc map to complex type used for retrieval of data?Anonymous
February 17, 2011
DevNc - Yes - where you get the CT instances from in your DomainService methods is up to you. So for example, you could have an Invoke operation that returnes CTs from a sproc.Anonymous
August 15, 2011
The link to the sample is not working. Can you please update the link?! cheers!!Anonymous
October 24, 2011
Pls publish direct link to the sample for this article.