Background
In our project, we are using SAP as the backend data source and the NWGW (Netweaver Gateway) adapter to consumable from .NET client as OData format. Since the NWGW component is hosted on premise and our .NET client is hosted in Azure, we are consuming this data from Azure through the Service Bus relay. While transferring data from on premise to Azure over SB relay, we are facing performance issues for single user for large volumes of data as well as in relatively small data for concurrent users. So I did some POC for improving performance by consuming the OData service in JSON format.
What I Did?
I’ve created a simple WCF Data Service which has no underlying data source connectivity. In this service when the context is initializing, a list of text messages is generated and exposed as OData.
Here is that simple service code:
[Serializable]
public class Message
{
public int ID { get; set; }
public string MessageText { get; set; }
}
public class MessageService
{
List<Message> _messages = new List<Message>();
public MessageService()
{
for (int i = 0; i < 100; i++)
{
Message msg = new Message
{
ID = i,
MessageText = string.Format("My Message No. {0}", i)
};
_messages.Add(msg);
}
}
public IQueryable<Message> Messages
{
get
{
return _messages.AsQueryable<Message>();
}
}
}
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class WcfDataService1 : DataService<MessageService>
{
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("Messages", EntitySetRights.AllRead);
config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
}
}
Exposing one endpoint to Azure SB so that client can consume this service through SB endpoint. After hosting the service, I’m able to fetch data by simple OData query from browser.
I’m also able to fetch the data in JSON format.
After that, I create a console client application and consume the service from there.
Sample Client Code
class Program
{
static void Main(string[] args)
{
List<Thread> lst = new List<Thread>();
for (int i = 0; i < 100; i++)
{
Thread person = new Thread(new ThreadStart(MyClass.JsonInvokation));
person.Name = string.Format("person{0}", i);
lst.Add(person);
Console.WriteLine("before start of {0}", person.Name);
person.Start();
}
Console.ReadKey();
foreach (var item in lst)
{
item.Abort();
}
}
}
public class MyClass
{
public static void JsonInvokation()
{
string personName = Thread.CurrentThread.Name;
Stopwatch watch = new Stopwatch();
watch.Start();
try
{
SimpleService.MessageService svcJson =
new SimpleService.MessageService(new Uri
("https://abc.servicebus.windows.net/SimpleService /WcfDataService1"));
svcJson.SendingRequest += svc_SendingRequest;
svcJson.Format.UseJson();
var jdata = svcJson.Messages.ToList();
watch.Stop();
Console.WriteLine("Person: {0} - JsonTime First Call time: {1}",
personName, watch.ElapsedMilliseconds);
for (int i = 1; i <= 10; i++)
{
watch.Reset(); watch.Start();
jdata = svcJson.Messages.ToList();
watch.Stop();
Console.WriteLine("Person: {0} - Json Call {1} time:
{2}", personName, 1 + i, watch.ElapsedMilliseconds);
}
Console.WriteLine(jdata.Count);
}
catch (Exception ex)
{
Console.WriteLine(personName + ": " + ex.Message);
}
Thread.Sleep(100);
}
public static void AtomInvokation()
{
string personName = Thread.CurrentThread.Name;
try
{
Stopwatch watch = new Stopwatch();
watch.Start();
SimpleService.MessageService svc =
new SimpleService.MessageService(new Uri
("https://abc.servicebus.windows.net/SimpleService/WcfDataService1"));
svc.SendingRequest += svc_SendingRequest;
var data = svc.Messages.ToList();
watch.Stop();
Console.WriteLine("Person: {0} - XmlTime First Call time: {1}",
personName, watch.ElapsedMilliseconds);
for (int i = 1; i <= 10; i++)
{
watch.Reset(); watch.Start();
data = svc.Messages.ToList();
watch.Stop();
Console.WriteLine("Person: {0} - Xml Call {1} time:
{2}", personName, 1 + i, watch.ElapsedMilliseconds);
}
Console.WriteLine(data.Count);
}
catch (Exception ex)
{
Console.WriteLine(personName + ": " + ex.Message);
}
Thread.Sleep(100);
}
}<span style="font-size: 9pt;"> </span>
What I Test After That
I tested two separate scenarios:
Scenario I: Single user with small and large volume of data
Measuring the data transfer time periodically in XML format and then JSON format. You might notice that first call I’ve printed separately in each screen shot as it is taking additional time to connect to SB endpoint. In the first call, the secret key authentication is happening.
Small data set (array size 10): consume in XML format.
Consume in JSON format:
For small set of data, Json and XML response time over service bus relay is almost same.
Consuming Large volume of data (Array Size 100)
Here the XML message size is around 51 KB. Now I’m going to consume the same list of data (Array size 100) in JSON format.
So from the above test scenario, it is very clear that JSON response time is much faster than XML response time and the reason
for that is message size. In this test, when I’m getting the list of 100 records in XML format message size is 51.2 KB but JSON message size is 4.4 KB.
Scenario II: 100 Concurrent user with large volume of data (array size 100)
In this concurrent user load test, I’ve done any service throttling or max concurrent connection configuration.
In the above screen shot, you will find some time out error that I’m getting in XML response. And it is happening due to high response
time over relay. But when I execute the same test with JSON response, I found the response time is quite stable and faster than XML response and I’m not getting any time out.
How Easy to Use UseJson()
If you are using WCF Data Service 5.3 and above and VS2012 update 3, then to consume the JSON structure from the client, I have to instantiate the proxy / context with <ServiceInstance>.Format.UseJson()
. Here you don’t need to load the Edmx structure separately by writing any custom code. .NET
CodeGen will generate that code when you add the service reference.
But if that code is not generated from your environment, then you have to write a few lines of code to load the
edmx and use
it as <service instance>.Format.UseJson(LoadEdmx(<service name>))
;
Sample Code for Loading Edmx
public static IEdmModel LoadEdmx(string srvName)
{
string executionPath = Directory.GetCurrentDirectory();
DirectoryInfo di = new DirectoryInfo(executionPath).Parent;
var parent1 = di.Parent;
var srv = parent1.GetDirectories("Service References\\" +
srvName)[0].GetFiles("service.edmx")[0].FullName;
XmlDocument doc = new XmlDocument();
doc.Load(srv);
var xmlreader = XmlReader.Create(new StringReader(doc.DocumentElement.OuterXml));
IEdmModel edmModel = EdmxReader.Parse(xmlreader);
return edmModel;
}
Happy coding…