Data Transfer using WCF

So forgetting OData for a minute (not hard to do), I was thinking about how to transfer SDO classes to and from a WCF service.  All of the WCF projects I have worked on have been POCOs with the appropriate WCF attributes from System.ServiceModel and System.Runtime.Serialization.  I never thought about putting an ADO.NET recordset as a return value from a WCF method.  I also wondered about putting the recordset as a parameter to a WCF method.  I assume it is possible, I was curious about how much effort it would take.  I Binged on Google (or was it Googled on Bing) and these was nothing that jumped out.

I fired up a typical WCF project and then added a consuming console app to the solution.  I then wrote an interface that returns a dataTable like so:

[ServiceContract]
public interface IDataFactory
{
    [OperationContract]
    DataTable GetDataTable();
}
public class DataFactory : IDataFactory
{
    public DataTable GetDataTable()
    {
        throw new NotImplementedException();
    }
}

I then hit F6 and sure enough it compiled.  I then changed the implementation to this

public DataTable GetDataTable()
{
    DataTable dataTable = new DataTable();
    dataTable.TableName = "Customers";
    dataTable.Columns.Add("CustomerId");
    dataTable.Columns.Add("CustomerName");

    DataRow row1 = dataTable.NewRow();
    row1[0] = 1;
    row1[1] = "Customer #1";
    dataTable.Rows.Add(row1);
    DataRow row2 = dataTable.NewRow();
    row2[0] = 2;
    row2[1] = "Customer #2";
    dataTable.Rows.Add(row2);

    return dataTable;

}

and I am still compiling.  So then I went to the client and added a reference and it worked:

image

I then fired up the client like so:

static void Main(string[] args)
{
    Console.WriteLine("Starting");

    DataFactoryClient client = new DataFactoryClient();
    DataTable table = client.GetDataTable();

    foreach (DataRow row in table.Rows)
    {
        Console.WriteLine(String.Format("Customer {0} named {1}.",row[0],row[1]));
    }

    Console.WriteLine("Ending");
    Console.ReadKey();
}

And I hit F5:

image

 

Wow.  Microsoft made this stupid simple.

I then though about how to pass in an individual data row. 

[OperationContract]
String InsertDataRow(DataRow row);
public String InsertDataRow(DataRow row)
{
    return String.Format("You entered {0}.", row[0].ToString());
}

And when I hit update reference from my consuming app, I got this:

image

Crud!  I then thought I could just add a serializable data row like so:

[Serializable]
public class SerializableDataRow: DataRow
{
}

And this:

[OperationContract]
String InsertDataRow(SerializableDataRow row);

But no, I get this:

image

So now I have to jump down a rabbit hole and possible violate the Liskov Substitution Principle.  Since I want things to be stupid simple, I gave up with inheritance.  I then found this post.  So either use a datatable (and suffer the overhead) or convert the DataRow into something that can be seialized (like XML, custom classes, etc..)

So I give Microsoft a C on this – somewhat stupid simple, but not entirely…

ADO.NET and Connection Pooling

I decided that I needed to learn more about ConnectionPooling – especially ConnectionPool fragmentation.   I ran into a great article here that explains in the ins and outs of connection pool fragmentation.  I decided to try out some scenarios.

I first created a class library that calls a select on a Northwind table:

public class NorthwindFactory
{
    public Dictionary<String, String> GetRegions(String connectionString)
    {
        Dictionary<String, String> regionDictionary = new Dictionary<string, string>();

        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            String commandText = "Select * from Region";
            using(SqlCommand command = new SqlCommand(commandText, connection))
            {
                connection.Open();
                SqlDataReader reader = command.ExecuteReader();
                while (reader.Read())
                {
                    regionDictionary.Add(reader[0].ToString(), reader[1].ToString());
                }
            }
        }

        return regionDictionary;
    }
}

I then added a unit (really integration) test to run this function:

[TestClass()]
public class NorthwindFactoryTests
{

    [TestMethod()]
    public void GetRegionsTest()
    {
        NorthwindFactory target = new NorthwindFactory();
        string connectionString = @"Data Source=Dixon12;database=Northwind;Uid=NorthwindUser;Pwd=password";
        Dictionary<string, string> regionDictionary = target.GetRegions(connectionString);

        Int32 expected = 4;
        Int32 actual = regionDictionary.Count;
        Assert.AreEqual(expected, actual);
    }
}

I then opened up Sql Server Management Studio to see the impact that this call had:

select spid, loginame, status, program_name,cmd from master..sysprocesses where spid > 50

When I ran the test, nothing came out of sysprocess – by the time I flipped windows from VS to SSMS, the test ran and the connection went away.  I changed the code to allow me to flip over:

while (reader.Read())
{
    Thread.Sleep(TimeSpan.FromSeconds(3));
    regionDictionary.Add(reader[0].ToString(), reader[1].ToString());
}

Sure enough, when I run the test, I see the active connection

image

I then decided to see what would happen with two connections.  I went and added a second connection in serial:

[TestMethod()]
public void GetTwoRegionsInSequenceTest()
{
    NorthwindFactory target = new NorthwindFactory();
    string connectionString = @"Data Source=Dixon12;database=Northwind;Uid=NorthwindUser;Pwd=password";
    Dictionary<string, string> regionDictionaryOne = target.GetRegions(connectionString);
    Dictionary<string, string> regionDictionaryTwo = target.GetRegions(connectionString);

    Int32 expected = 8;
    Int32 actual = regionDictionaryOne.Count + regionDictionaryTwo.Count;
    Assert.AreEqual(expected, actual);
}

There is only 1 active connection at a time – the connectionPoolManager in action.

image

My next though was that if the connection is explicitly closed by the SqlConnection objects is not out of its using scope, would the connection stay open?

using(SqlCommand command = new SqlCommand(commandText, connection))
{
    connection.Open();
    SqlDataReader reader = command.ExecuteReader();
    while (reader.Read())
    {
        regionDictionary.Add(reader[0].ToString(), reader[1].ToString());
    }
    connection.Close();
    Thread.Sleep(TimeSpan.FromSeconds(10));
}

Sure enough, when I run this, the connection is “closed” on the client but on the Sql Server it is still active:

image

That is the connection pool manager keeping the connection alive.

So my next thought – does the connection pool manager work cross threads?  I created a new test like so:

[TestMethod()]
public void GetRegions_ParallelTest()
{
    NorthwindFactory target = new NorthwindFactory();
    string connectionString = @"Data Source=Dixon12;database=Northwind;Uid=NorthwindUser;Pwd=password";
    ConcurrentBag<KeyValuePair<String, String>> regionBag = new ConcurrentBag<KeyValuePair<String, String>>();

    Parallel.For(0, 2, i =>
    {
        Dictionary<string, string> regionDictionary = target.GetRegions(connectionString);
        foreach (KeyValuePair<String,String> keyValuePair in regionDictionary)
        {
            regionBag.Add(keyValuePair);
        }

    });

    Int32 expected = 8;
    Int32 actual = regionBag.Count;
    Assert.AreEqual(expected, actual);
}

And the GetRegions() has a 10 second delay built in.  I got this back on my dual-processor machine:

image

And to corroborate, I passed in two different times that the thread stays awake:

public Dictionary<String, String> GetRegions(String connectionString)
{
    return GetRegions(connectionString, 10);
}

public Dictionary<String, String> GetRegions(String connectionString, Int32 lengthOfSleep)
{
    Dictionary<String, String> regionDictionary = new Dictionary<string, string>();

    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        String commandText = "Select * from Region";
        using (SqlCommand command = new SqlCommand(commandText, connection))
        {
            connection.Open();
            SqlDataReader reader = command.ExecuteReader();
            while (reader.Read())
            {
                regionDictionary.Add(reader[0].ToString(), reader[1].ToString());
            }
            connection.Close();
            Thread.Sleep(TimeSpan.FromSeconds(lengthOfSleep));
        }
    }

    return regionDictionary;
}

Add the test that checks:

[TestMethod()]
public void GetRegions_Parallel_DifferentSleepTimes_Test()
{
    NorthwindFactory target = new NorthwindFactory();
    string connectionString = @"Data Source=Dixon12;database=Northwind;Uid=NorthwindUser;Pwd=password";
    ConcurrentBag<KeyValuePair<String, String>> regionBag = new ConcurrentBag<KeyValuePair<String, String>>();

    Parallel.For(0, 2, i =>
    {
        Dictionary<string, string> regionDictionary = target.GetRegions(connectionString, 5+(i*5));
        foreach (KeyValuePair<String, String> keyValuePair in regionDictionary)
        {
            regionBag.Add(keyValuePair);
        }

    });

    Int32 expected = 8;
    Int32 actual = regionBag.Count;
    Assert.AreEqual(expected, actual);
}

Sure enough, in the 1st five seconds:

image

And in the last 5 seconds (or so):

image

Next, I added a new test that uses two different types of connection strings – 1 for integrated security, 1 for Sql Server security:

[TestMethod()]
public void GetRegions_DifferentConnectionStringsTest()
{
    NorthwindFactory target = new NorthwindFactory();
    string sqlServerSecurityConnectionString = @"Data Source=Dixon12;database=Northwind;Uid=NorthwindUser;Pwd=password";
    string integratedSecurityConnectionString = @"Data Source=Dixon12;database=Northwind;Integrated Security=true";

    Dictionary<string, string> sqlServerSecurityRegionDictionary = target.GetRegions(sqlServerSecurityConnectionString);
    Dictionary<string, string> integratedSecurityRegionDictionary = target.GetRegions(integratedSecurityConnectionString);

    Int32 expected = 8;
    Int32 actual = sqlServerSecurityRegionDictionary.Count + integratedSecurityRegionDictionary.Count;
    Assert.AreEqual(expected, actual);
}

Sure enough, 2 connections

 

image

I then realized that my test so far do not show the number of connection pools in existence, rather it just shows the number of active Sql Connections for each scenario that may or may not be controlled by different connection pools.  What I need to do is to have something on the client that I can use to inspect the number of connection pools and connections within those pools.  I ran across this site which showed how to use reflection to resolve the hidden properties/fields of the SqlConnection class.  To that end, I created the following class that determines the the ConnectionPool for a given connection and then the number of connections in that pool.

public static ConnectionPool GetConnectionPool(SqlConnection sqlConnection)
{
    ConnectionPool connectionPool = new ConnectionPool();
    connectionPool.PoolIdentifier = sqlConnection.ConnectionString;

    Type sqlConnectionType = typeof(SqlConnection);
    FieldInfo _poolGroupFieldInfo =
      sqlConnectionType.GetField("_poolGroup", BindingFlags.NonPublic | BindingFlags.Instance);
    var dbConnectionPoolGroup =
      _poolGroupFieldInfo.GetValue(sqlConnection);

    if (dbConnectionPoolGroup != null)
    {
        
        FieldInfo _poolCollectionFieldInfo =
          dbConnectionPoolGroup.GetType().GetField("_poolCollection",
             BindingFlags.NonPublic | BindingFlags.Instance);
        
        HybridDictionary poolCollection =
          _poolCollectionFieldInfo.GetValue(dbConnectionPoolGroup) as HybridDictionary;

        foreach (DictionaryEntry poolEntry in poolCollection)
        {
            var foundPool = poolEntry.Value;
            FieldInfo _objectListFieldInfo =
               foundPool.GetType().GetField("_objectList",
                  BindingFlags.NonPublic | BindingFlags.Instance);
            var listTDbConnectionInternal =
               _objectListFieldInfo.GetValue(foundPool);
            MethodInfo get_CountMethodInfo =
                listTDbConnectionInternal.GetType().GetMethod("get_Count");
            var numberOfConnections = get_CountMethodInfo.Invoke(listTDbConnectionInternal, null);
            connectionPool.NumberOfConnections = (Int32)numberOfConnections;
        }
    }

    return connectionPool;
}

I also realized that I needed the number of ConnectionPools in total.  That is also available via the SqlConnection.ConnectionFactory property. 

public static List<ConnectionPool> GetConnectionPools(SqlConnection sqlConnection)
{
    List<ConnectionPool> connectionPools = new List<ConnectionPool>();

    Type sqlConnectionType = typeof(SqlConnection);
    PropertyInfo _connectionFactoryPropertyInfo =
        sqlConnectionType.GetProperty("ConnectionFactory", BindingFlags.NonPublic | BindingFlags.Instance);
    var connectionFactory =
      _connectionFactoryPropertyInfo.GetValue(sqlConnection,null);


    if (connectionFactory != null)
    {
        FieldInfo _connectionPoolGroupsInfo =
          connectionFactory.GetType().BaseType.GetField("_connectionPoolGroups",
             BindingFlags.NonPublic | BindingFlags.Instance);
        var dbConnectionPoolGroups =
          _connectionPoolGroupsInfo.GetValue(connectionFactory);

        IEnumerable enumerator = dbConnectionPoolGroups as IEnumerable;
        ConnectionPool connectionPool = null;

        foreach (var item in enumerator)
        {
            connectionPool = new ConnectionPool();
            PropertyInfo _valuePropertyInfo =
                item.GetType().GetProperty("Value", BindingFlags.Public | BindingFlags.Instance);
            var _valuePropertyValue = _valuePropertyInfo.GetValue(item,null);

            PropertyInfo _keyPropertyInfo =
                item.GetType().GetProperty("Key", BindingFlags.Public | BindingFlags.Instance);
            var _keyPropertyValue = _keyPropertyInfo.GetValue(item, null);

            if (_valuePropertyValue != null)
            {
                FieldInfo _poolCollectionFieldInfo =
                    _valuePropertyValue.GetType().GetField("_poolCollection",
                     BindingFlags.NonPublic | BindingFlags.Instance);
                HybridDictionary poolCollection =
                  _poolCollectionFieldInfo.GetValue(_valuePropertyValue) as HybridDictionary;

                connectionPool.PoolIdentifier = _keyPropertyValue.ToString();
                connectionPool.NumberOfConnections = poolCollection.Count;
            }
            connectionPools.Add(connectionPool);
        }
    }

    return connectionPools;
}

So my unit(integration) tests show that with the same connection string, you have 1 pool with as many connection.Open() you call that have not been cleaned up but the GC yet. 

[TestMethod()]
public void GetConnectionPool_1OpenConnectionTest()
{
    string connectionString = @"Data Source=Dixon12;database=Northwind;Uid=NorthwindUser;Pwd=password";
    SqlConnection sqlConnection = new SqlConnection(connectionString);
    sqlConnection.Open();
    ConnectionPool connectionPool = ConnectionPoolFactory.GetConnectionPool(sqlConnection);

    Int32 expected = 1;
    Int32 actual = connectionPool.NumberOfConnections;
    Assert.AreEqual(expected, actual);
    sqlConnection.Close();
}

Also, you can see the number of ConnectionPools that are active at any 1 time and the number of connections in those strings. 

[TestMethod()]
public void GetConnectionPools_2OpenConnectionsDifferentConnectionStringsTest()
{
    string connectionString1 = @"Data Source=Dixon12;database=Northwind;Uid=NorthwindUser;Pwd=password";
    SqlConnection sqlConnection1 = new SqlConnection(connectionString1);
    sqlConnection1.Open();
    string connectionString2 = @"Data Source=Dixon12;database=Northwind2;Integrated Security=true";
    SqlConnection sqlConnection2 = new SqlConnection(connectionString2);
    sqlConnection2.Open();
    List<ConnectionPool> connectionPools = ConnectionPoolFactory.GetConnectionPools(sqlConnection1);

    Int32 expected = 2;
    Int32 actual = connectionPools.Count;
    Assert.AreEqual(expected, actual);
}

Armed with that information, I could then confirm if different connection strings open new pools (it does) and that if different threads with the same connection string opens a new pool (it doesn’t). 

[TestMethod()]
public void GetConnectionPools_2OpenConnectionsSameConnectionStringsDifferentThreads_Test()
{
    string connectionString1 = @"Data Source=Dixon12;database=Northwind2;Integrated Security=true";
    SqlConnection sqlConnection1 = new SqlConnection(connectionString1);
    Thread threadOne = new Thread(sqlConnection1.Open);
    threadOne.Start();

    string connectionString2 = @"Data Source=Dixon12;database=Northwind2;Integrated Security=true";
    SqlConnection sqlConnection2 = new SqlConnection(connectionString2);
    Thread threadTwo = new Thread(sqlConnection2.Open);
    threadTwo.Start();

    List<ConnectionPool> connectionPools = ConnectionPoolFactory.GetConnectionPools(sqlConnection2);
    Int32 numberOfConnectionPoolsExpected = 1;
    Int32 numberOfConnectionPoolsActual = connectionPools.Count;
    Assert.AreEqual(numberOfConnectionPoolsExpected, numberOfConnectionPoolsActual);

    Int32 numberOfConnectionsExpected = 0;
    Int32 numberOfConnectionsActual = connectionPools[0].NumberOfConnections;
    Assert.AreEqual(numberOfConnectionsExpected, numberOfConnectionsActual);

}

This means that the connection pool manager is thread safe.  Note that Connections are not thread safe, which is why the # of connections are 0 on the main thread.  And yes, I know I could have looked at MSFT source code to figure this out and perhaps there is some documentation on Thread-Safety is available, but this was fun.

So the next question in my mind is what can have an impact on performance?  For example, if you have connection pool fragmentation (via different connection strings), what is the performance gain my combining all of the active connections into 1 pool?  This post has gotten long enough, so I will show that in another one.