This sample shows how to implement a connection pool for SAP connections.
About
In some situations it can be useful to use a connection pool. This means that several processes or threads use a set of SAP connections together, e.g., in a web application: 30 users work with an application but there are only 10 concurrent connections to SAP. Everytime an application process needs a connection a free connection is allocated by the pool. After having used the connection it is freed by the process and can be used by another one.
Prerequisites
Before the ConnectionPool class can be used, the R3Connection class must be extended by inheriting it. The two new properties LastUsage and IsInUse are used later.
The following sample code shows how to use the methods of the ConnectionPool class.
In the constructor of the class a timer is initialized. The timer is responsible for closing connections that are not used for a certain period of time.
classR3ConnectionPool{privateSystem.Timers.TimerMyTimer=newSystem.Timers.Timer();publicR3ConnectionPool(){// When this static class is created// initialize the timer and handle elapsed eventMyTimer.Interval=1000;MyTimer.Elapsed+=newSystem.Timers.ElapsedEventHandler(MyTimer_Elapsed);MyTimer.Enabled=true;}privateInt32_MaxNoOfConnection=10;publicInt32MaxNoOfConnection{get{return_MaxNoOfConnection;}set{_MaxNoOfConnection=value;}}privatestring_ConnectionString="";publicstringConnectionString{set{_ConnectionString=value;}}publicInt32CurrentNumberOfConnection{get{returnMyConnectionList.Count;}}
The generic list MyConnectionList holds all active connections. When the last usage was more than 60 seconds ago and it is not currently in use, the connection is closed and removed from the list.
privateSystem.Collections.Generic.ListMyConnectionList=newSystem.Collections.Generic.List();privatevoidMyTimer_Elapsed(objectsender,System.Timers.ElapsedEventArgse){// Loop through the list// and check, if a connection is not used for more than 60 second,// if so, close it an remove it from the listlock(MyConnectionList){foreach(R3ConnectionExconinMyConnectionList){if(!con.IsInUse&&con.LastUsage.AddSeconds(60)<DateTime.Now){con.Close();MyConnectionList.Remove(con);return;}}}}
The two private functions AllocConnection() and FreeConnection() are for allocating and deallocating connections. If there is no free connection available a new connection is created and added to the connection list.
privateR3ConnectionExAllocConnection(){lock(MyConnectionList){foreach(R3ConnectionExconinMyConnectionList){if(!con.IsInUse){con.IsInUse=true;returncon;}}if(MyConnectionList.Count<this.MaxNoOfConnection){R3ConnectionExcon=newR3ConnectionEx();con.Open(this._ConnectionString);this.MyConnectionList.Add(con);con.IsInUse=true;returncon;}if(MyConnectionList.Count>=this.MaxNoOfConnection)thrownewException("Maximun Number of connection exceeded");elsethrownewException("Unable to allocate a new connection");}}privatevoidFreeConnection(R3ConnectionExcon){con.LastUsage=DateTime.Now;con.IsInUse=false;}
Without the pool you would call CreateFunction() directly, e.g., con.CreateFunction(). When using the new pool class the CreateFunction() method is called by the pool after having allocated a connection dynamically. The RFCFunction object is cashed with the help of XML serialization and deserialization. This avoids retrieving the function's meta data from SAP every time CreateFunction() is called.
privateHashtableFunctionHash=newHashtable();publicRFCFunctionCreateFunction(stringFunctionName){lock(FunctionHash){stringxml=(string)FunctionHash[FunctionName];if(xml==null){// The function has not been created yet in this poolR3ConnectionExcon=this.AllocConnection();try{RFCFunctionfunc=con.CreateFunction(FunctionName);FreeConnection(con);// store in hash for later useFunctionHash.Add(FunctionName,func.SaveToXML());returnfunc;}catch(Exceptione1){// Check if connection is still alive// if not, remove itif(!con.Ping())MyConnectionList.Remove(con);elseFreeConnection(con);// rethrow exceptionthrowe1;}}else{// We can create the function object without calling the CreateFunction methodRFCFunctionfunc=newRFCFunction(FunctionName);func.LoadFromXMLString(xml);returnfunc;}}}
The execution of the function uses the same principle as CreateFunction():
publicvoidExecuteFunction(RFCFunctionfunc){R3ConnectionExcon=this.AllocConnection();try{func.Connection=(R3Connection)con;func.Execute();}catch(Exceptione1){// Check if connection is still alive// if not, remove itif(!con.Ping())MyConnectionList.Remove(con);FreeConnection(con);// rethrow exceptionthrowe1;}FreeConnection(con);}
Test the connection pool
The following console program shows how to test and apply the connection pool class.
How it works:
First 3 separate threads are started.
After pressing [Enter] 3 more threads are started.
The timer shows the current number of active connections.
Depending on how many threads have already finished after the new ones have been started, the connections are recycled or newly connected.
The output shows:
3 threads are started
2 have finished
3 news threads are started
Result: 4 active SAP connections, because after 2 have finished only 1 more is needed.
classProgram{staticR3ConnectionPoolConPool=newR3ConnectionPool();staticSystem.Timers.Timertimer=newSystem.Timers.Timer();[STAThread]staticvoidMain(string[]args){timer.Interval=1500;timer.Elapsed+=newSystem.Timers.ElapsedEventHandler(timer_Elapsed);timer.Enabled=true;ConPool.ConnectionString="USER=Theobald LANG=DE CLIENT=XXX SYSNR=XX ASHOST=XXX PASSWD=XXX ";Start3Threads("TH*","H*","X*");Console.WriteLine("3 threads started. Press enter to start 3 more threads");Console.Read();Start3Threads("A*","B*","C*");Console.WriteLine("3 additional threads started. Press enter to quit.");Console.ReadLine();Console.ReadLine();Console.ReadLine();Console.ReadLine();}staticvoidStart3Threads(stringSearchKey1,stringSearchKey2,stringSearchKey3){System.Threading.Threadt4=newSystem.Threading.Thread(newSystem.Threading.ParameterizedThreadStart(ExecuteALongRunningFunctionModule));t4.Name=SearchKey1;t4.Start(SearchKey1);System.Threading.Threadt5=newSystem.Threading.Thread(newSystem.Threading.ParameterizedThreadStart(ExecuteALongRunningFunctionModule));t5.Name=SearchKey2;t5.Start(SearchKey2);System.Threading.Threadt6=newSystem.Threading.Thread(newSystem.Threading.ParameterizedThreadStart(ExecuteALongRunningFunctionModule));t6.Name=SearchKey3;t6.Start(SearchKey3);}staticvoidtimer_Elapsed(objectsender,System.Timers.ElapsedEventArgse){Console.WriteLine("Current Number of connections: "+ConPool.CurrentNumberOfConnection);}staticvoidExecuteALongRunningFunctionModule(objectSearchTerm){RFCFunctionfunc=ConPool.CreateFunction("BAPI_EMPLOYEE_GETLIST");func.Exports["SUR_NAME_SEARK"].ParamValue=SearchTerm.ToString();func.Exports["SEARCH_DATE"].ParamValue="20070101";ConPool.ExecuteFunction(func);Console.WriteLine(func.Tables["EMPLOYEE_LIST"].Rows.Count.ToString()+" rows received -> SearchKey: "+System.Threading.Thread.CurrentThread.Name);}}