High performance of remoting ADO.NET objects across desktop and devices

 

 udaparts
support@udaparts.com
Updated on 04/01/2008

 

Contents

  1. Introduction
  2. A list of drawbacks of Microsoft .NET approach
  3. SocketPro removes all of the above drawbacks of Microsoft .NET approach
  4. How I remote ADO.NET objects with SocketProAdapter
  5. Extend your special data with ADO.NET objects

 

Introduction

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.