2014-02-14 51 views
4

我正在使用Linq to SQL连接到大约2000个数据库之一的ASP.NET MVC应用程序。我们注意到,该应用程序花费大量时间到数据库的连接我们的分析工具,而且我怀疑这是部分原因是由于连接池碎片如下所述:http://msdn.microsoft.com/en-us/library/8xx3tyca(v=vs.110).aspxLINQ to Sql:更改每个连接的数据库

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.

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

我当前的解决方案或多或少像this one - 它将SqlConnection对象封装在继承自DbConnection的类中。这允许我重写Open()方法并在打开连接时更改数据库。它适用于大多数情况下确定的,但在这使得许多更新的请求,我得到这个错误:

System.InvalidOperationException: Transaction does not match connection

我的想法是,我会再以同样的方式裹DbTransaction对象是什么我的SqlConnection一样,并确保其连接属性将指向包装的连接对象。修正了上述错误,但引入了一个新的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()); 
      } 
     } 
    } 
} 

}

回答

0

我想我找到了适合我的情况的解决方案。我没有包装SqlConnection并重写Open()来更改数据库,而是将DBContext传递给一个新的SqlConnection并订阅连接的StateChanged事件。当状态改变时,我检查连接是否刚刚打开。如果是这样,我调用SqlConnection.ChangeDatabase()将其指向正确的数据库。我测试了这个解决方案,它似乎工作 - 我看到所有数据库只有一个连接池,而不是每个已访问数据库的池。我认为这不是理想应用中的理想解决方案,但是对于这个应用程序的结构,我认为它应该在相对较少的成本下做出体面的改进。

2

我不认为这会工作过一个单一的共享连接。

LINQ to SQL最适合于工作单元类型连接 - 创建连接,完成原子分组工作并尽快关闭连接并重新打开下一个任务。如果你这样做,然后传递一个连接字符串(或使用只传递表名的自定义构造函数)是非常简单的。

如果考虑到您的应用程序存在问题,您可以使用getter来操纵缓存的DataContext'实例',而是每次请求时创建一个新实例,而不是使用缓存/共享实例,并将连接字符串插入Getter。

但 - 我敢肯定,这不会帮助您的池问题。 SQL Server驱动程序基于不同的连接字符串值来缓存连接 - 由于值仍然在变化,因此您很快就会在连接字符串缓存中激活大量连接,这可能会导致大量缓存未命中,从而导致连接速度变慢。