Carl Rippon

Building SPAs

Carl Rippon
BlogBooks / CoursesAbout
This site uses cookies. Click here to find out more

Scalable and Performant ASP.NET Core Web APIs: Database Connections

February 22, 2018
dotnetsqlserver

This is another post in a series of posts on creating performant and scalable web APIs using ASP.NET core 2.0. In this post we’ll focus on database connections, making sure the connection pool is used efficiently …

Connections

We’ve chosen Dapper as our ORM in a previous post. So, we are responsible for creating SQL connections for our queries.

Some problematic code

We have a very dirty controller action method below for GET /api/contacts/{contactId}. This code has a serious problem with it - we catch and return the exception with a “200” status code so that we can easily see the problem. Obviously this isn’t production code!

[HttpGet("{contactId}")]
public IActionResult Get(Guid contactId)
{
    try
    {
        SqlConnection connection = new SqlConnection(_connectionString);

        connection.Open();
        string sql = @"SELECT ContactId, Title, FirstName, Surname
                        FROM Contact
                        WHERE Contact.ContactId = @ContactId";

        Contact contact = connection.QueryFirstOrDefault<Contact>(sql, new { ContactId = contactId });
        if (contact == null)
        {
            return NotFound();
        }

        return Ok(contact);
    }
    catch(Exception ex)
    {
        return Ok(ex);
    }
}

If we hit the endpoint in Postman, it responds fine. It we hit it again, it’s still fine … and again … still fine. It’s pretty quick as well.

Connection problem - 1st try

So, what’s the problem? Let’s load test the endpoint …

We’re going to use Web Surge for the load test and hit the endpoint for 30 seconds with 8 threads. If we watch the Output window, - the test starts fine and then grinds to a halt. In fact we start to get timeouts.

Connection load test Connection timeout

If we try to hit the same endpoint again in Postman, we get the following, which gives us more information: Connection timeout in Postman

So, there is a problem getting a connection from the connection pool because all the connections are in use. If we look at our code again, we aren’t closing our connection. So, the connection stays open until the garbage collector closes it. This means that the next request that comes in won’t be able to use that connection from the connection pool - it will have to take a different connection. This process continues until there are no connections left in the pool … which is where we get the timeout error.

The fix

So, we can see that connections are limited resources and should only be open for when it is in use - i.e. we should close the connection straight after the query has finished.

A quick fix attempt is:

[HttpGet("{contactId}")]
public IActionResult Get(Guid contactId)
{
    try
    {
        SqlConnection connection = new SqlConnection(_connectionString);

        connection.Open();
        string sql = @"SELECT ContactId, Title, FirstName, Surname
                        FROM Contact
                        WHERE Contact.ContactId = @ContactId";

        Contact contact = connection.QueryFirstOrDefault<Contact>(sql, new { ContactId = contactId });

        connection.Close();
        if (contact == null)
        {
            return NotFound();
        }

        return Ok(contact);
    }
    catch(Exception ex)
    {
        return Ok(ex);
    }
}

… but what if the query errors? We’ll be left with an open connection again …

A better implementation is to wrap the query in a Using block. This results in our connection automatically being closed and disposed for us - even if an exception occurs.

[HttpGet("{contactId}")]
public IActionResult Getgood(Guid contactId)
{
    Contact contact = null;

    using (SqlConnection connection = new SqlConnection(_connectionString))    {        connection.Open();

        string sql = @"SELECT ContactId, Title, FirstName, Surname
                        FROM Contact
                        WHERE Contact.ContactId = @ContactId";

        contact = connection.QueryFirstOrDefault<Contact>(sql, new { ContactId = contactId });
    }
    if (contact == null)
    {
        return NotFound();
    }

    return Ok(contact);
}

If we run the same load test again, we find that our endpoint continues to operate fine for the full 30 seconds:

Connection load test fix

Conclusion

So, the key point to remember is that connections are precious resources and should only be open when we need them. As a rule of thumb we should create and open the connection immediately before executing the required database query and close the connection immediately after the query has finished and we have captured the results. The Using keyword is a robust way of ensuring our connection is always closed and disposed and returned to the connection pool.

We’ll remain on the topic of data access for our next post - this time looking at database round trips.

Did you find this post useful?

Let me know by sharing it on Twitter.
Click here to share this post on Twitter

If you to learn about using React with ASP.NET Core you might find my book useful:

ASP.NET Core 5 and React

Find out more