Question

I'm working on an ASP.NET MVC application which uses Linq to SQL to connect to one of about 2000 databases. We've noticed in our profiling tools that the application spends a lot of time making connections to the databases, and I suspect this is partly due to connection pool fragmentation as described here: http://msdn.microsoft.com/en-us/library/8xx3tyca(v=vs.110).aspx

Many Internet service providers host several Web sites on a single server. They may use a single database to confirm a Forms authentication login and then open a connection to a specific database for that user or group of users. The connection to the authentication database is pooled and used by everyone. However, there is a separate pool of connections to each database, which increase the number of connections to the server.

There is a relatively simple way to avoid this side effect without compromising security when you connect to SQL Server. Instead of connecting to a separate database for each user or group, connect to the same database on the server and then execute the Transact-SQL USE statement to change to the desired database.

I am trying to implement this solution in Linq to Sql so we have fewer open connections, and so there is more likely to be a connection available in the pool when we need one. To do that I need to change the database each time Linq to Sql attempts to run a query. Is there any way to accomplish this without refactoring the entire application? Currently we just create a single data context per request, and that data context may open and close many connections. Each time it opens the connection, I'd need to tell it which database to use.

My current solution is more or less like this one - it wraps a SqlConnection object inside a class that inherits from DbConnection. This allows me to override the Open() method and change the database whenever a connection is opened. It works OK for most scenarios, but in a request that makes many updates, I get this error:

System.InvalidOperationException: Transaction does not match connection

My thought was that I would then wrap a DbTransaction object in a similar way to what I did with SqlConnection, and ensure that its connection property would point back to the wrapped connection object. That fixed the error above, but introduced a new one where a DbCommand was unable to cast my wrapped connection to a SqlConnection object. So then I wrapped DbCommand too, and now I get new and exciting errors about the transaction of the DbCommand object being uninitialized.

In short, I feel like I'm chasing specific errors rather than really understanding what's going on in-depth. Am I on the right track with this wrapping strategy, or is there a better solution I'm missing?

Here are the more interesting parts of my three wrapper classes:

public class ScaledSqlConnection : DbConnection
{
    private string _dbName;
    private SqlConnection _sc;
    public override void Open()
    {
        //open the connection, change the database to the one that was passed in
        _sc.Open();
        if (this._dbName != null)
            this.ChangeDatabase(this._dbName);
    }
    protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel)
    {
        return new ScaledSqlTransaction(this, _sc.BeginTransaction(isolationLevel));
    }

    protected override DbCommand CreateDbCommand()
    {
        return new ScaledSqlCommand(_sc.CreateCommand(), this);
    }
}

public class ScaledSqlTransaction : DbTransaction
{
    private SqlTransaction _sqlTransaction = null;
    private ScaledSqlConnection _conn = null;

    protected override DbConnection DbConnection
    {
        get { return _conn; }
    }
}

public class ScaledSqlCommand : DbCommand
{
    private SqlCommand _cmd;
    private ScaledSqlConnection _conn;
    private ScaledSqlTransaction _transaction;
    public ScaledSqlCommand(SqlCommand cmd, ScaledSqlConnection conn)
    {
        this._cmd = cmd;
        this._conn = conn;
    }
    protected override DbConnection DbConnection
    {
        get
        {
            return _conn;
        }
        set
        {
            if (value is ScaledSqlConnection)
                _conn = (ScaledSqlConnection)value;
            else
                throw new Exception("Only ScaledSqlConnections can be connections here.");
        }
    }

    protected override DbTransaction DbTransaction
    {
        get
        {
            if (_transaction == null && _cmd.Transaction != null)
                _transaction = new ScaledSqlTransaction(this._conn, _cmd.Transaction);
            return _transaction;
        }
        set
        {
            if (value == null)
            {
                _transaction = null;
                _cmd.Transaction = null;
            }
            else
            {
                if (value is ScaledSqlTransaction)
                    _transaction = (ScaledSqlTransaction)value;
                else
                    throw new Exception("Don't set the transaction of a ScaledDbCommand with " + value.ToString());
            }
        }
    }
}

}

Was it helpful?

Solution 2

I think I figured out a solution that works for my situation. Rather than wrapping SqlConnection and overriding Open() to change databases, I'm passing the DBContext a new SqlConnection and subscribing to the connection's StateChanged event. When the state changes, I check to see if the connection has just been opened. If so, I call SqlConnection.ChangeDatabase() to point it to the correct database. I tested this solution and it seems to work - I see only one connection pool for all the databases rather than one pool for each db that has been accessed.

I realize this isn't the ideal solution in an ideal application, but for how this application is structured I think it should make a decent improvement for relatively little cost.

OTHER TIPS

I don't think that's going to work off a single shared connection.

LINQ to SQL works best with Unit of Work type connections - create your connection, do your atomically grouped work and close the connection as quickly as possible and reopen for the next task. If you do that then passing in a connection string (or using custom constructor that only passes a tablename) is pretty straight forward.

If factoring your application is a problem, you could use a getter to manipulate the cached DataContext 'instance' and instead create a new instance each time you request it instead of using the cached/shared instance and inject the connection string in the Getter.

But - I'm pretty sure this will not help with your pooling issue though. The SQL Server driver caches connections based on different connection string values - since the values are still changing you're right back to having lots of connections active in the connection string cache, which likely will result in lots of cache misses and therefore slow connections.

I think, that the best way is making UnitOfWork pattern with Repository pattern to work with Entity Framework. Entity Framework has FirstAsync, FirstOrDefaultAsync, this helped me to fix the same bug.

https://msdn.microsoft.com/en-us/data/jj819165.aspx

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top