I had a scenario where I wanted to query several database tables at once and process the data as fast as possible using a single database connection.
After some though I decided to look at MARS ( multiple active result set) which was available from SQL2005 and up. This gives the option to open multiple datareaders under the one SQL database connection and allow the data to be read in an interleaved fashion. Although not true parallel reading it does have the advantage of having only one database connection open.
Also I had been reading about the new parallel framework that is available in .Net 4.0. Because I have several datareaders open, I could process each reader in parallel (using multi-core processors) and take advantage of the yeild points. This is because statements which return rows to the client, are allowed to interleave execution before completion while rows are being sent to the client.
Below is the sample prototype code to test my theory.
1: public void GetUserDetailsSharedConn()
2: {
3: List<mdAddress> addresses;
4: List<mdUser> users;
5:
6: string dbCon = "Server= .;Database=adventureworks;Trusted_Connection=yes;MultipleActiveResultSets=true;";
7: using (SqlConnection conn = new SqlConnection(dbCon))
8: {
9:
10: string sql1 = "SELECT * FROM [Person].[Address]";
11: string sql2 = "SELECT * FROM [Person].[Contact]";
12:
13: SqlCommand cmd1 = new SqlCommand(sql1, conn);
14: SqlCommand cmd2 = new SqlCommand(sql2, conn);
15: cmd1.CommandTimeout = 500;
16: cmd2.CommandTimeout = 500;
17: conn.Open();
18: SqlDataReader dr1 = cmd1.ExecuteReader();
19: SqlDataReader dr2 = cmd2.ExecuteReader();
20:
21:
22: Parallel.Invoke
23: (
24: () => PopulateAddressSharedConn(dr1, out addresses),
25: () => PopulateUsersSharedConn(dr2, out users)
26: );
27: }
28: }
29:
30:
31: private void PopulateUsersSharedConn( SqlDataReader rd, out List<mdUser> users)
32: {
33: users = new List<mdUser>();
34: // Call Read before accessing data.
35: while (rd.Read())
36: {
37: mdUser user = new mdUser();
38: user.Title = rd["Title"] == DBNull.Value ? string.Empty : (string)rd["Title"];
39: user.FirstName = (string)rd["FirstName"];
40: user.LastName = (string)rd["LastName"];
41: users.Add(user);
42: }
43: }
44:
45: private void PopulateAddressSharedConn(SqlDataReader rd, out List<mdAddress> addresses)
46: {
47: addresses = new List<mdAddress>();
48: // Call Read before accessing data.
49: while (rd.Read())
50: {
51: mdAddress address = new mdAddress();
52: address.Address1 = (string)rd["AddressLine1"];
53: address.Address2 = rd["AddressLine2"] == DBNull.Value ? string.Empty :(string)rd["AddressLine2"];
54: address.City = (string)rd["City"];
55: addresses.Add(address);
56: }
57:
58: }
I used the Parallel.Invoke method because I need to wait until I received all the data back form all the threads.
Below is the SQL Profiler trace after executing the code in quick succession. On the first query set, the ‘Address’ batch yielded until the ‘Contact’ batch completed. However on the highlighted second query the ‘Contact’ batch waited until the ‘Address’ batch completed.