High performance of remoting ADO.NET
objects across desktop and devices
udaparts
support@udaparts.com
Updated on 04/01/2008
Contents
DataSet object is the center of ADO.NET. No matter
your application is a window desktop, a middle tier, a web or a smart device
application, you can easily bind a DataSet object with one grid or view
control. Therefore, while developing .NET based distributed applications, most
likely you need to deal with sending ADO.NET objects across machines. Microsoft
.NET framework provides two key communication components, .NET remoting and
window communication framework (WCF), for this purpose. However, the two
components have troubles with DataSet object, especially when an ADO.NET object
contains a large collection of data.
Recently,
a few our potential customers push us to write some code to ease remoting
ADO.NET objects with our SocketPro. The suggestion is indeed useful to our
customers. We spent a few days to improve our SocketProAdapter for supporting
remoting ADO.NET objects. This short article tells you the design and usage of
remoting ADO.NET objects with SocketPro in batching, asynchrony and parallel
fashion.
A list of drawbacks of Microsoft .NET approach
SocketPro removes all of the above drawbacks of
Microsoft .NET approach
protected void GetDataReader(string strSQL)
{
IDataReader dr = null;
SqlConnection conn = new
SqlConnection("server=localhost\\sqlexpress;Integrated
Security=SSPI;database=northwind");
try
{
conn.Open();
}
catch
(Exception err)
{
Console.WriteLine(err.Message);
return;
}
SqlCommand cmd = new SqlCommand(strSQL, conn);
try
{
dr = cmd.ExecuteReader();
}
catch
(Exception err)
{
Console.WriteLine(err.Message);
conn.Close();
return;
}
Send(dr); //send a
data reader to a remote end
conn.Close();
}
The above
code is typically used for sending one of ADO.NET objects, DataSet, DataTable
and DataReader, onto a remote end. Internally, SocketProAdapter sends a header
of the three ADO.NET objects first. Afterwards, it sends records in chunk as
shown in the below.
public virtual long Send(IDataReader dr, CUQueue UQueue, int nBatchSize)
{
long nSize;
bool bSuc;
if (dr == null)
throw new
Exception("Must pass in a valid data reader interface!");
if (UQueue == null)
{
UQueue
= m_UQueue;
}
UQueue.SetSize(0);
bool bBatching = IsBatching;
if (!bBatching)
{
bSuc
= StartBatching();
}
m_AdoSerialier.PushHeader(UQueue,
dr);
int res =
SendReturnData(CAsyncAdoSerializationHelper.idDataReaderHeaderArrive, UQueue);
//monitor socket close event and cancel request
if (res == SocketProAdapter.ServerSide.CClientPeer.REQUEST_CANCELED
|| res == SocketProAdapter.ServerSide.CClientPeer.SOCKET_NOT_FOUND)
{
if (!bBatching)
{
bSuc
= CommitBatching();
}
return res;
}
nSize = res;
UQueue.SetSize(0);
if (nBatchSize < 2048)
nBatchSize
= 2048;
while (dr.Read())
{
m_AdoSerialier.Push(UQueue,
dr); //pack one record into UQueue
if (UQueue.GetSize() > nBatchSize)
{
res
= SendReturnData(CAsyncAdoSerializationHelper.idDataReaderRecordsArrive,
UQueue);
if (BytesBatched > 2 * nBatchSize)
{
//if we find too much are stored in batch queue, we send
them immediately and start a new batching
bSuc
= CommitBatching();
bSuc
= StartBatching();
}
//monitor socket close event and cancel request
if (res ==
SocketProAdapter.ServerSide.CClientPeer.REQUEST_CANCELED || res ==
SocketProAdapter.ServerSide.CClientPeer.SOCKET_NOT_FOUND)
{
if (!bBatching)
{
bSuc
= CommitBatching();
}
return res;
}
UQueue.SetSize(0);
nSize
+= res;
}
}
if (UQueue.GetSize() > 0) //remaining
{
res
= SendReturnData(CAsyncAdoSerializationHelper.idDataReaderRecordsArrive,
UQueue);
//monitor socket close event and cancel request
if (res ==
SocketProAdapter.ServerSide.CClientPeer.REQUEST_CANCELED || res ==
SocketProAdapter.ServerSide.CClientPeer.SOCKET_NOT_FOUND)
{
if (!bBatching)
{
bSuc
= CommitBatching();
}
return res;
}
UQueue.SetSize(0);
nSize
+= res;
}
if (!bBatching)
{
bSuc
= CommitBatching();
}
return nSize;
}
By
default, the above parameter nBatchSize is 10 Kilo bytes. The above code packs
all of records into an instance (UQueue) of CUQueue. Once the instance UQueue
contains data in size more than nBatchSize, the code will automatically send
the records onto a remote end. It does similarly for remoting DataSet and
DataTable objects. Further, the above code is also monitoring network event
close or cancel. Once they are found, sending records are stopped elegantly.
After analyzing the code
carefully, you’ll find that SocketProAdapter does not require loading data
records first if you send records from a DataReader object. Also, it does not
serialize the whole set of records into a large memory chunk at all like .NET
remoting and WCF. By this way, SocketProAdapter reduces memory required and
footprint, which leads much better performance and scalability. After testing,
we find that in general our SocketProAdapter is about 2.5 times faster than
.NET remoting even in binary format! In comparison to XML/SOAP, our SocketProAdapter
is usually ten times faster.
SocketProAdapter provides the
same method Send for sending ADO.NET objects, DataSet, DataTable and DataReader
on both client and server sides with extremely simple way, as shown in the
sample RAdo at the directory C:\Program Files\udaparts\SocketPro\samples\RAdo.
private void
frmRemotingAdoNet_Load(object sender,
System.EventArgs e)
{
m_RAdo.Attach(m_ClientSocket);
m_ClientSocket.m_OnSocketClosed += new DOnSocketClosed(OnSocketClosed);
m_ClientSocket.m_OnSocketConnected += new DOnSocketConnected(OnSocketConnected);
m_ClientSocket.m_OnRequestProcessed += new DOnRequestProcessed(OnRequestProcessed); //prepare for a delegate
}
private void OnRequestProcessed(int
hSocket, short sRequestID, int nLen, int nLenInBuffer,
USOCKETLib.tagReturnFlag ReturnFlag)
{
if(ReturnFlag
!= USOCKETLib.tagReturnFlag.rfCompleted)
return;
switch(sRequestID)
{
case
(short)CAsyncAdoSerializationHelper.idDataReaderHeaderArrive:
case
(short)CAsyncAdoSerializationHelper.idDataTableHeaderArrive:
dgTable.DataSource = m_RAdo.CurrentDataTable; //bind a DataTable with either DataGrid or DataGridViewer
control
break;
default:
break;
}
}
private void btnGetDR_Click(object
sender, System.EventArgs e)
{
DataTable dt =
m_RAdo.GetDataReader("Select * from Employees");
// You
can use the below code to display result if data record set size is not large.
// You
should consider latency and use delegate OnRequestProcessed instead, if either
record set is large or network bandwith is small.
/* if(dt
!= null)
{
dgTable.DataSource
= dt;
}*/
}
Our SocketPro is especially great with tough tasks like
remoting ADO.NET objects. By this way, your application UI will has great response
to impress your application users!
How I remote ADO.NET objects with
SocketProAdapter
No matter what version of SocketProAdapter you use, your
client asynchronous handler must inherit from the class CAsyncAdoHandler
instead of CRequestAsynHandlerBase. Similarly, your server request handler must
inherit from the class CAdoClientPeer instead of CClientPeer. Afterwards,
simply call the method Send. See the sample RAdo at the directory C:\Program
Files\udaparts\SocketPro\samples\RAdo.
You need to pay attention to dealing with processing
header and data records as shown in the below on client side.
//When a
result comes from a remote SocketPro server, the below virtual function will be
called.
//We always process returning
results inside the function.
protected override void
OnResultReturned(short sRequestID, CUQueue
UQueue)
{
if(SocketProServerException.HResult
!= 0) return; //exception
transferred from SocketPro server
switch(sRequestID)
{
case CAsyncAdoSerializationHelper.idDataSetHeaderArrive:
case
CAsyncAdoSerializationHelper.idDataTableHeaderArrive:
case
CAsyncAdoSerializationHelper.idDataReaderHeaderArrive:
case
CAsyncAdoSerializationHelper.idDataReaderRecordsArrive:
case CAsyncAdoSerializationHelper.idDataTableRowsArrive:
case
CAsyncAdoSerializationHelper.idEndDataTable:
case
CAsyncAdoSerializationHelper.idEndDataReader:
case CAsyncAdoSerializationHelper.idEndDataSet:
base.OnResultReturned(sRequestID,
UQueue); //chain down to CAsyncAdohandler for
processing
break;
case
RAdoConst.idGetDataSetCRAdo:
break;
case
RAdoConst.idGetDataReaderCRAdo:
break;
case
RAdoConst.idSendDataSetCRAdo:
UQueue.Pop(ref m_SendDataSetRtn);
break;
case
RAdoConst.idSendDataReaderCRAdo:
UQueue.Pop(ref m_SendDataReaderRtn);
break;
default:
break;
}
}
All you need to do is to call the base method OnResultReturned. To handle requests on server side, it is similar. For details, see the provided sample RAdo.
Extend your special data with ADO.NET
objects
SocketProAdapter ignores a few special properties of
ADO.NET objects such as ExtendedProperties, Site, Local, RemotingFormat, and SchemaSerializationMode.
Usually, you don’t need to serialize these properties.
In
case you need to remote these properties or your own special data, you can
override the virtual method Send of either class CAsyncAdoHandler or CAdoClientPeer.