This is a follow up on my OData first look post. Since then I’ve moved the service to IIS for better fiddler support. The sample code builds on the previous post and shows how to load related data in one call, the ability to overcome model changes and how to use operations.
Every part has the exceptions and how I solved them.
- Expand properties in one message
- Changes to the model and the three clients
- Operations, to have more control
- Operations with parameter, even more control
Expand
With the Expand operation the eager loading of related data is executed. This means that the property (set in the Expand parameter) will be supplied as an object in the response. Default a link would be returned for later retrieval of the object.
Exception
The property is returned in the response, but not available in the result object. With LoadProperty the property is set, but that is not why you call expand.
Solution
MergeOption of the context should be set. The default AppendOnly does not allow “changes” and ignores the expanded data. With NoTracking the LoadProperty will throw an exception, but the expand operation is executed and applied. PreserveChanges or OverwriteChanges support both Expand and LoadProperty, since my data is readonly both options are applicable.
Code
context.MergeOption = MergeOption.OverwriteChanges; var lastReport = context.CreateQuery<Report>("Reports") .Expand("Survey") .Where(r => r.SurveyId == "1") .OrderByDescending(r => r.Created) .First();
Changes to the model
You can create a client for you odata service on three different ways
- Reference the entities assembly, update the client when the service changes
- Create a copy of the entities, your project is more standalone
- Generate a Service Reference, this combines 1 and 2, by generating the types from the entities assembly as a copy in the project
What type of client you use, changes to the model can break the application.
Exception
The property ‘x’ does not exist on type ‘y’. Make sure to only use property names that are defined by the type.
Solution
On the DataServiceContext is a property that instructs it to ignore missing properties. Whatever property cannot be mapped (renamed, removed or added) the value stays null or default.
Make note that changing the type of a property can cause an exception. A DateTime will fit in a String, but not every String will fit in a DateTime.
Code
var context = new DataServiceContext(uri, DataServiceProtocolVersion.V3); context.IgnoreMissingProperties = true;
Operations
Operations can be used to handle the data. This offers more control to the developer. My sample does the filtering on the name. Maybe for security reasons or for performance.
Exception
I’ve got a 404 Not found exception on my first try.
Solution
The uri constructor replaces everything after the last slash. Make sure you end the baseUri with a ‘/’.
Code
// Dataservice: public static void InitializeService(DataServiceConfiguration config) { // other config left out for clarity config.SetServiceOperationAccessRule("*", ServiceOperationRights.AllRead); } [WebGet] public Reporting.Entities.Survey FlightSurvey() { return CurrentDataSource.Surveys .Where(x => x.SurveyName.Equals("flight")) .FirstOrDefault(); } // client var uri = new Uri ("http://MACHINENAME/Reporting.Service/SurveyServiceOData.svc/"); var context = new DataServiceContext(uri, DataServiceProtocolVersion.V3); var flight = new Uri (uri, "FlightSurvey"); var survey = context.Execute<Survey>(flight).First();
Operations with parameter
Operations can have parameters. In my sample above the name could be a parameter.
Exception
Query options $select, $expand, $filter, $orderby, $inlinecount, $skip, $skiptoken and $top are not supported by this request method or cannot be applied to the requested resource.
Solution
Do a ToList() on the query before calling First(). This executes the odata query (without $top=1), reads the result in a list and then returns the first item.
Code
// DataService: [WebGet] public Reporting.Entities.Survey GetSurvey(string surveyName) { return CurrentDataSource.Surveys .Where(x => x.SurveyName.Equals(surveyName)) .FirstOrDefault(); } // client var uri = new Uri ("http://MACHINENAME/Reporting.Service/SurveyServiceOData.svc/"); var context = new DataServiceContext(uri, DataServiceProtocolVersion.V3); var survey = context.CreateQuery<Survey >("GetSurvey" ) .AddQueryOption("surveyName", "'flight'") .ToList().First();
Conclusion
With lazy loading and operations I can limit the traffic. Looking deeper into WCF data service, my use-case implementation is getting clearer.
References
Calling Service Operations article on MSDN
Framework source code of ClientType showing how IgnoreMissingProperties works. Exception or Ignore, no hooks for custom handling.
Pingback: OData third look | .NET Development by Eric