我希望有人可以就在不同线程中处理SQL连接时的最佳做法提供一些建议。
我现在遇到的问题让我问这个问题(但我已经好好想了一会儿),如下所示。
我有一个对话框需要一些时间来初始化,因为它包含一个从SQL数据库填充的控件。这不是一个大规模的查询,但它需要几秒钟,我认为没有理由用户每次显示对话框或每次包含对话框的表单实例化时都应该等待几秒钟。因此,我将填充对话框的代码放在BackgroundWorker
中,以便在用户甚至点击可显示对话框的内容之前,它可以在后台进行。
到目前为止一切顺利。工作可爱。唯一的问题,我的一个表单(使用这个对话框)本身就是一个SQL查询填充,而且,它涉及一个稍微复杂的查询(截断表并重新填充它),我想保持原子性,所以我把它全部放在交易中。
但现在我们遇到了问题;到启动此事务时,对话框已经实例化,其BackgroundWorker
已经忙于填充它。所以我得到一个读取
“不允许新事务,因为还有其他线程 在会话中运行“
我能理解为什么我会得到这样的例外,但我仍然不确定如何最好地解决它。
但可以肯定的是,这种在不同线程中针对相同数据库运行不同查询的概念,其中一些可能涉及事务,这种情况并不常见。我认为此前肯定有人遇到过这样的人,所以我希望有人可以提供一些建议。
*编辑* 好的,这里有一些代码来说明问题。提供完整的代码会过于复杂,我的问题实际上是关于SQL连接和多线程的原理,而不是我面临的这个特殊问题,但如果它有助于理解问题,我将添加此代码
我有两种形式,我们称之为Form_Main
和Form_Dialog
。当前一个按钮被点击时,后者显示为模态对话框。所以他们的胆量看起来像这样:
Form_Main.cs
public partial class Form_Main : Form
{
public Form_Main()
{
InitializeComponent();
// The above function is automatically created by the IDE and is
// defined in Form_Main.Designer.cs. It is inside this function
// that the class of the dialog form is defined and instantiated
// (and therefore its BackgroundWorker thread is started)
Populate();
}
private void Populate()
{
using (SqlTransaction sqlTransaction = ClassGlobal.sqlConnection.BeginTransaction())
{
using (SqlCommand sqlCommand = new SqlCommand(".....", ClassGlobal.sqlConnection, sqlTransaction))
{
// Here goes some code that populates the current
// form, and it's encapsulated inside a Transaction
}
sqlTransaction.Commit();
}
}
private void Button_Click(object sender, EventArgs e)
{
if (formDialog.ShowDialog() != DialogResult.OK)
return;
// Do some stuff with the return value from the dialog
}
}
和Form_Dialog.cs
public partial class Form_Dialog : Form
{
public Form_Dialog()
{
InitializeComponent();
BackgroundWorker _populateWorker = new BackgroundWorker();
_populateWorker.DoWork += new DoWorkEventHandler(_populateWorker_DoWork);
_populateWorker.RunWorkerAsync();
}
void _populateWorker_DoWork(object sender, DoWorkEventArgs e)
{
using (SqlCommand sqlCommand = new SqlCommand(".....", ClassGlobal.sqlConnection, sqlTransaction))
{
// Here goes some code that populate the current form
}
}
}
这是ClassGlobal.cs的片段
public sealed class ClassGlobal
{
public static SqlConnection sqlConnection;
static ClassGlobal()
{
}
public static void DBInit(string server, string database, string uid, string password)
{
string connectionstring = "SERVER=" + server + ";" + "DATABASE=" + database + ";" + "UID=" + uid + ";" + "PASSWORD=" + password + ";";
sqlConnection = new SqlConnection(connectionstring);
}
}
ClassGlobal
在应用程序的早期实例化,并使用所需的连接参数调用DBInit
。所有表单都可以访问其中定义的静态SQLConnection
。
从上面的内容可以看出,在调用Form_Main.cs
函数时查看Populate()
,InitializeComponent()
函数已被调用,因此{{1}的构造函数}}也已被调用,因此Form_Dialog
的{{1}}已经被淘汰了。因此,BackgroundWorker
的{{1}}函数内的交易无法启动。
答案 0 :(得分:3)
是的,好的,我们可以看到问题。不要尝试共享SqlConnection
个对象。唯一应该共享和传递的是连接 string 。
当您需要连接对象时,请创建一个新对象,Open
,然后使用它,然后Dispose
。让连接池(在幕后工作)负责实际使用与服务器的实际物理连接数。
所以例如:
void _populateWorker_DoWork(object sender, DoWorkEventArgs e)
{
using(var conn = new SqlConnection(ClassGlobal.ConnectionString
using (SqlCommand sqlCommand = new SqlCommand(".....", conn))
{
conn.Open();
// Here goes some code that populate the current form
}
}
和
public sealed class ClassGlobal
{
public static string ConnectionString;
static ClassGlobal()
{
}
public static void DBInit(string server, string database, string uid, string password)
{
ConnectionString = "SERVER=" + server + ";" + "DATABASE=" + database + ";" + "UID=" + uid + ";" + "PASSWORD=" + password + ";";
}
}
答案 1 :(得分:1)
您应该在每个请求上创建一个新连接,而不是静态连接。连接池将负责资源共享(这是错误的代码,但应该意味着您不需要在其他地方进行任何更改)。
public sealed class ClassGlobal
{
private static readonly string connectionstring;
public static SqlConnection sqlConnection
{
get
{
return new SqlConnection(connectionstring);
}
}
static ClassGlobal()
{
}
public static void DBInit(string server, string database, string uid, string password)
{
connectionstring = "SERVER=" + server + ";" + "DATABASE=" + database + ";" + "UID=" + uid + ";" + "PASSWORD=" + password + ";";
}
}