Linq to Sql:更改每个连接的数据库

Jar*_*lps 5 c# linq asp.net asp.net-mvc connection-pooling

我正在开发一个 ASP.NET MVC 应用程序,它使用 Linq to SQL 连接到大约 2000 个数据库之一。我们在分析工具中注意到应用程序花费了大量时间来建立与数据库的连接,我怀疑这部分是由于连接池碎片所致,如下所述: http: //msdn.microsoft.com/en-us /library/8xx3tyca(v=vs.110).aspx

许多 Internet 服务提供商在一台服务器上托管多个网站。他们可以使用单个数据库来确认表单身份验证登录,然后为该用户或用户组打开与特定数据库的连接。与身份验证数据库的连接被集中并由每个人使用。但是,每个数据库都有一个单独的连接池,这会增加与服务器的连接数量。

连接到 SQL Server 时,有一种相对简单的方法可以避免这种副作用,同时又不会影响安全性。不要为每个用户或组连接到单独的数据库,而是连接到服务器上的同一数据库,然后执行 Transact-SQL USE 语句以更改为所需的数据库。

我正在尝试在 Linq to Sql 中实现此解决方案,以便我们拥有更少的打开连接,因此当我们需要连接时,池中更有可能有可用连接。为此,我需要在每次 Linq to Sql 尝试运行查询时更改数据库。有没有什么方法可以在不重构整个应用程序的情况下完成此任务?目前,我们只是为每个请求创建一个数据上下文,并且该数据上下文可以打开和关闭许多连接。每次打开连接时,我都需要告诉它要使用哪个数据库。

我当前的解决方案或多或少类似于这个- 它将 SqlConnection 对象包装在继承自 DbConnection 的类中。这允许我重写 Open() 方法并在打开连接时更改数据库。它适用于大多数情况,但在进行多次更新的请求中,我收到此错误:

System.InvalidOperationException:事务与连接不匹配

我的想法是,然后以与 SqlConnection 类似的方式包装 DbTransaction 对象,并确保其连接属性将指向回包装的连接对象。这修复了上面的错误,但引入了一个新错误,其中 DbCommand 无法将我的包装连接转换为 SqlConnection 对象。然后我也包装了 DbCommand,现在我收到了有关 DbCommand 对象的事务未初始化的新的令人兴奋的错误。

简而言之,我觉得我正在追寻特定的错误,而不是真正深入了解正在发生的事情。我的这种包装策略是否走在正确的轨道上,还是我缺少更好的解决方案?

以下是我的三个包装类中更有趣的部分:

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());
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

}

Jar*_*lps 1

我想我找到了适合我的情况的解决方案。我不是包装 SqlConnection 并重写 Open() 来更改数据库,而是向 DBContext 传递一个新的 SqlConnection 并订阅该连接的 StateChanged 事件。当状态发生变化时,我检查连接是否刚刚打开。如果是这样,我调用 SqlConnection.ChangeDatabase() 将其指向正确的数据库。我测试了这个解决方案,它似乎有效 - 我看到所有数据库只有一个连接池,而不是每个已访问的数据库都有一个连接池。

我意识到这不是理想应用程序中的理想解决方案,但对于该应用程序的结构,我认为它应该以相对较低的成本做出不错的改进。