WCF Data Services performance
By Mirek on (tags: Data Services, OData, WCF, categories: architecture, code)The main benefit of using WCF Data Services (formerly ADO.NET Data Services, codename "Astoria) is that this is interoperable, which means it can be accessed from clients created in different technologies. Moreover standard WCF services offers the only exposed set of methods. Data Services, as implementation of OData protocol, offers the Resource to be queried and altered using HTTP methods. That’s are the pros of WCF Data Services. And what about the performance in comparison to WCF services?
In this post I will try to measure this performance.
Testing solution will consists of MVC 3 application which will hosts WCF Data Services service and WCF service, and console clients for OData and wcf service. Both services will expose the collection of Person entity
1: [DataServiceKey("Id")]
2: [DataContract]
3: public class Person
4: {
5: [DataMember]
6: public int Id { get; set; }
7:
8: [DataMember]
9: public string Name { get; set; }
10:
11: [DataMember]
12: public DateTime CreatedOn { get; set; }
13:
14: [DataMember]
15: public double FloatNumber { get; set; }
16:
17: [DataMember]
18: public IList<Order> Orders { get; set; }
19: }
One test will be based on receiving flat list of Person objects without Orders loaded. And at second step the Orders will be filled with some random objects of type
1: [DataServiceKey("Id")]
2: [DataContract]
3: public class Order
4: {
5: [DataMember]
6: public int Id { get; set; }
7:
8: [DataMember]
9: public DateTime CreatedOn { get; set; }
10:
11: [DataMember]
12: public int PersonId { get; set; }
13:
14: [DataMember]
15: public string ProductName { get; set; }
16: }
To avoid any additional performance hit we will use some mock repository for both services which will generate the collection of Person and Order entities
1: public class PersonContextMock
2: {
3: private static readonly int CollectionCount = 1000;
4:
5: private static readonly bool LoadOrders = false;
6:
7: private static List<Person> people = null;
8:
9: public static List<Person> GetPeople()
10: {
11: if (people == null)
12: {
13: people = new List<Person>(CollectionCount);
14: DateTime now = DateTime.Now;
15: for (int idx = 1; idx <= CollectionCount; idx++)
16: {
17: people.Add(new Person
18: {
19: Id = idx,
20: Name = "Person" + idx.ToString(),
21: FloatNumber = (double)(idx + 0.123456),
22: CreatedOn = now.AddMilliseconds(idx),
23: Orders = new List<Order>()
24: });
25: }
26:
27: if (LoadOrders)
28: {
29:
30: for (int idx = 0; idx < CollectionCount; idx++)
31: {
32: people[idx].Orders.Add(new Order
33: {
34: Id = CollectionCount + idx + 1,
35: ProductName = "OrderedProduct" + (CollectionCount + idx + 1).ToString(),
36: CreatedOn = now.AddMilliseconds(idx),
37: PersonId = people[idx].Id
38: });
39: people[idx].Orders.Add(new Order
40: {
41: Id = CollectionCount + idx + 2,
42: ProductName = "OrderedProduct" + (CollectionCount + idx + 2).ToString(),
43: CreatedOn = now.AddMilliseconds(idx),
44: PersonId = people[idx].Id
45: });
46: }
47: }
48: }
49:
50: return people;
51: }
52:
53: public IQueryable<Person> People
54: {
55: get
56: {
57: return GetPeople().AsQueryable<Person>();
58: }
59: }
60:
61: public IQueryable<Order> Orders
62: {
63: get
64: {
65: return (from pr in GetPeople()
66: from or in pr.Orders
67: select or).AsQueryable();
68: }
69: }
70:
71: public List<Person> GetPerson()
72: {
73: return GetPeople();
74: }
75: }
In line 53 and 61 there are IQueryable properties which will be automatically exposed in WCF Data Services. That’s the one of WCF Data Service provider requirements to have public IQueryable collections. Another is that the entity type (here Person) has an DataServiceKeyAttribute applied.
The WCF Data Service looks like this
1: [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
2: public class ODataPersonService : DataService<PersonContextMock>
3: {
4: public static void InitializeService(DataServiceConfiguration config)
5: {
6: config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
7: config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
8: config.UseVerboseErrors = true;
9: }
10: }
Simple isn’t it?
And WCF service looks like this
1: [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
2: public class WCFPersonService : IWCFPersonService
3: {
4: public IList<core.model.Person> GetPeople()
5: {
6: return PersonContextMock.GetPeople();
7: }
8: }
Also simple. It is standard WCF service using basicHttpBinding so messages are encoded as a text and sent through HTTP. Current functionality of this service is only a fraction of above OData service, but this is not an subject of this post.
I will not show the code of the clients, since it is trivial.
In the tests we will measure the time from request to the response. The standard WCF services will be tested with two message encodings: Text and binary, both using HTTP protocol.
Last row in each the table contains the median of the results.
Test 1
Getting a 1000 of Person without Orders
Try | OData (ms) | WCF Text encoding (ms) | WCF Binary encoding (ms) |
1 | 1717,0811 | 1642,09 | 1148,0657 |
2 | 647,037 | 58,0033 | 50,0029 |
3 | 607,0347 | 79,0045 | 50,0029 |
4 | 581,0332 | 60,0034 | 48,0028 |
5 | 571,0327 | 71,0041 | 57,0033 |
6 | 596,0341 | 71,0041 | 48,0028 |
7 | 568,0325 | 60,0035 | 48,0028 |
8 | 581,0333 | 58,0033 | 47,0027 |
9 | 572,0327 | 58,0033 | 49,0028 |
10 | 589,0337 | 77,0044 | 47,0027 |
11 | 556,0318 | 64,0037 | 49,0028 |
med | 581,03 | 64,0037 | 48,0028 |
Test 2
Getting a 1000 of Person with Orders loaded
The only change required for this test is to set the
1: private static readonly bool LoadOrders = true;
so all Person entities will be stored with its Orders loaded in mock repository. In WCF there is no change needed while OData service client must be modified, because OData service does not automatically loads the collections. We have to use Expand method on client side like this
1: var people = client.People.Expand("Orders").ToList();
so the WCF Data Service will load Orders for each Person entity on sever side.
Try | OData (ms) | WCF Text encoding (ms) | WCF Binary encoding (ms) |
1 | 3907,22 | 1331,0761 | 1671,096 |
2 | 2303,13 | 131,0074 | 100,0057 |
3 | 2282,13 | 133,0076 | 103,0059 |
4 | 2278,13 | 122,007 | 104,0059 |
5 | 2270,13 | 128,0073 | 90,0052 |
6 | 2252,13 | 118,0068 | 124,0071 |
7 | 2250,13 | 122,0069 | 88,0051 |
8 | 2325,13 | 151,0086 | 98,0056 |
9 | 2264,13 | 134,0077 | 101,0057 |
10 | 2252,13 | 116,0066 | 99,0057 |
11 | 2316,13 | 120,0068 | 92,0052 |
med | 2278,13 | 128,01 | 100,01 |
Here you can see how bad performance has OData service. Especially Expand functionality is very not efficient.
Test 3
OData service in comparison with direct database access using Entity Framework.
In the last test we will compare the OData service with Entity Framework Code First as a backend data source performance with direct Entity Framework Code First context operation. Test data contains 1000 Person entities without orders loaded. Direct access application has duplicated database in MS Sql Server CE 4.0 format and is a console application. OData client is also an console application and connects to the service hosted in MVC web site.
Try | OData (ms) | Direct EF access (ms) |
1 | 3378,0799 | 3190,182 |
2 | 716,0409 | 25,0014 |
3 | 650,0372 | 24,0014 |
4 | 599,0342 | 23,0014 |
5 | 532,0304 | 25,0014 |
6 | 544,0311 | 27,0016 |
7 | 483,0276 | 23,0013 |
8 | 482,0275 | 26,0015 |
9 | 497,0284 | 38,0022 |
10 | 685,0392 | 24,0013 |
11 | 629,036 | 23,0013 |
med | 599,03 | 25,00 |