I’ve heard about OData back in 2012 but never used it. Now I might have a use case for OData and want to explore it. Below are the parts of my sample WCF DataService project.
Every part has the exceptions and how I solved them.
- Entities, the data objects
- DataContext, data provider based on reflection
- DataService, WCF data service
- Client, console app for testing
Entities
My first project contains the entities of the sample project. A Survey and the Reports on it. Master-Detail with the surveyId stored in the Report.
Exception
On data context type ‘SurveyProvider’, there is a top IQueryable property ‘Surveys’ whose element type is not an entity type. Make sure that the IQueryable property is of entity type or specify the IgnoreProperties attribute on the data context type to ignore this property.
Solution
This exception is from the datacontext in the next part, but the cause is the missing [DataServiceKey(“Id”)] on the entities.
Code
[DataServiceKey("Id")] public partial class Survey { public string Id { get; set; } public string SurveyName { get; set; } [InverseProperty("SurveyId")] public virtual ICollection<Report> Reports { get; set; } } [DataServiceKey("Id")] public partial class Report { public string Id { get; set; } public DateTime Created { get; set; } public string SurveyId { get; set; } [ForeignKey("SurveyId")] public virtual Survey Survey { get; set; } }
DataContext
The data project is for the reflection provider that holds the (test)data. All data is stored in lists.
Exception
HTTP 204 no content
Solution
Property is null in LoadProperty. Make sure the DataContext provides a value for the Master/Detail.
Code
public class SurveyProvider { private List<Survey> surveys = new List<Survey> { new Survey { Id = "1", SurveyName = "Flight" } }; private List<Report> reports = new List<Report> { new Report { Id = "1", SurveyId = "1", Created = new DateTime(2015,1,1,10,0,0) }, new Report { Id = "2", SurveyId = "1", Created = new DateTime(2015,1,1,11,0,0) }, new Report { Id = "3", SurveyId = "1", Created = new DateTime(2015,1,1,12,0,0) }, new Report { Id = "4", SurveyId = "1", Created = new DateTime(2015,1,1,13,0,0) }, new Report { Id = "5", SurveyId = "1", Created = new DateTime(2015,1,1,14,0,0) }, }; public SurveyProvider() { surveys.ForEach(s => s.Reports = reports.Where(r => r.SurveyId == s.Id).ToList()); reports.ForEach(r => r.Survey = surveys.First(s => s.Id == r.SurveyId)); } public IQueryable<Survey> Surveys { get { return surveys.AsQueryable(); } } public IQueryable<Report> Reports { get { return reports.AsQueryable(); } } }
DataService
For easy debugging and trouble shooting enable the extra information on exceptions for the DataService. (See the code)
Also look at the metadata with the $metadata query option: http://localhost:53021/SurveyServiceOData.svc/$metadata
Host this in IIS to capture all traffic, because you’ll miss the lazy loading when hosting in IISExpress.
Code
[ServiceBehavior(IncludeExceptionDetailInFaults = true)] public class SurveyServiceOData : DataService<SurveyProvider> { public static void InitializeService(DataServiceConfiguration config) { config.UseVerboseErrors = true; config.SetEntitySetAccessRule("*", EntitySetRights.AllRead); config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3; } }
Client
Exception
HTTP Error 400. The request hostname is invalid.
or no traffic captured with fiddler.
Solution
Use the localhost.fiddler when hosting in IISExpress.
var uri = new Uri("http://localhost.fiddler:53021/SurveyServiceOData.svc");
Exception
The remote name could not be resolved: ‘localhost.fiddler’.
Solution
Set the proxy to fiddler.
<configuration> <!-- other settings in app.config --> <system.net> <defaultProxy enabled="true" > <proxy proxyaddress="http://localhost:8888"/> </defaultProxy> </system.net> </configuration>
Exception
The MaxDataServiceVersion ‘2.0’ is too low for the response. The lowest supported version is ‘3.0’.
Solution
Make sure the versions of the DataService and Client match. See also the InitializeService method of the DataService.
var ctx = new DataServiceContext(uri, DataServiceProtocolVersion.V3);
Exception
A property with name ‘Reports’ on type ‘Reporting.Entities.Survey’ has kind ‘Structural’, but it is expected to be of kind ‘Navigation’.
Solution
install-package EntityFramework
Code
static void Main(string[] args) { // use MachineName var uri = new Uri("http://localhost.fiddler:53021/SurveyServiceOData.svc"); var context = new DataServiceContext(uri, DataServiceProtocolVersion.V3); var survey = context.CreateQuery<Survey>("Surveys").First(); Console.WriteLine("Found survey {0} with {1} reports", survey.SurveyName, survey.Reports == null ? "null" : survey.Reports.Count.ToString()); var response = context.LoadProperty(survey, "Reports"); Console.WriteLine("LoadProperty Reports on Survey returned {0}", response.StatusCode); Console.WriteLine("Found survey {0} with {1} reports", survey.SurveyName, survey.Reports.Count.ToString()); var pointInTime = new DateTime(2015, 1, 1, 12, 0, 0); var report = context.CreateQuery<Report>("Reports") .Where(r => r.SurveyId == "1") .Where(r => r.Created == pointInTime) .First(); Console.WriteLine("Found report on {0} for Survey {1}", report.Created, report.Survey == null ? "null" : report.Survey.SurveyName); response = context.LoadProperty(report, "Survey"); Console.WriteLine("LoadProperty Survey on Report returned {0}", response.StatusCode); Console.WriteLine("Found report on {0} for Survey {1}", report.Created, report.Survey.SurveyName); Console.ReadLine(); }
Conclusion
The potential of WCF data service is there. Just expose the data and let the clients filter and query what they need.
References
Demo project for download
Odata.org with all the documentation
Custom Data Service Providers on MSDN