我正在开发一个ASP.NET MVC应用程序,它使用Linq to SQL连接到大约2000个数据库之一。我们在分析工具中注意到应用程序花费了大量时间建立与数据库的连接,我怀疑这部分是由于连接池碎片造成的,如下所述:http://msdn.microsoft.com/en-us/library/8xx3tyca(v=vs.110).aspx
许多互联网服务提供商在一个网站上托管多个网站 服务器。他们可能使用单个数据库来确认表单 身份验证登录然后打开与特定数据库的连接 对于该用户或用户组。与身份验证的连接 数据库汇集并由每个人使用。但是,有一个单独的 每个数据库的连接池,增加了数量 与服务器的连接。
有一种相对简单的方法可以避免这种情况 连接到SQL时不会影响安全性的副作用 服务器。而不是为每个用户或连接到单独的数据库 组,连接到服务器上的同一数据库,然后执行 Transact-SQL USE语句更改为所需的数据库。
我正在尝试在Linq to Sql中实现此解决方案,因此我们拥有更少的打开连接,因此当我们需要时,更有可能在池中提供连接。为此,我需要在每次Linq to Sql尝试运行查询时更改数据库。有没有办法在不重构整个应用程序的情况下实现这一目标?目前,我们只是为每个请求创建一个数据上下文,并且该数据上下文可以打开和关闭许多连接。每次打开连接时,我都需要告诉它使用哪个数据库。
我当前的解决方案或多或少像this one - 它将一个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());
}
}
}
}
}
答案 0 :(得分:2)
我认为这不会影响单个共享连接。
LINQ to SQL最适合工作单元类型连接 - 创建连接,执行原子分组工作并尽快关闭连接并重新打开以执行下一个任务。如果你这样做,那么传入连接字符串(或使用只传递表名的自定义构造函数)非常简单。
如果将应用程序分解是一个问题,您可以使用getter来操作缓存的DataContext'实例',而是在每次请求它时创建一个新实例,而不是使用缓存/共享实例并在其中注入连接字符串吸气剂。
但是 - 我很确定这对你的汇集问题没有帮助。 SQL Server驱动程序根据不同的连接字符串值缓存连接 - 由于值仍在变化,您可以直接在连接字符串缓存中激活大量连接,这可能会导致大量缓存未命中,从而导致连接速度变慢。
答案 1 :(得分:0)
我想我找到了适合我情况的解决方案。我没有包装SqlConnection并重写Open()来更改数据库,而是将DBContext传递给新的SqlConnection并订阅连接的StateChanged事件。当状态改变时,我检查是否刚刚打开了连接。如果是这样,我调用SqlConnection.ChangeDatabase()将其指向正确的数据库。我测试了这个解决方案,它似乎工作 - 我看到所有数据库只有一个连接池,而不是每个已访问的数据库的一个池。
我意识到这不是理想应用程序中的理想解决方案,但是对于这个应用程序的结构,我认为应该以相对较低的成本进行适当的改进。
答案 2 :(得分:0)
我认为,最好的方法是使用Repository模式制作UnitOfWork模式以使用Entity Framework。实体框架有FirstAsync,FirstOrDefaultAsync,这帮助我解决了同样的错误。