创建包含SQL Server数据库的安装程序

在开发需要在客户端服务器上部署SQL Server数据库的应用程序时,可能会遇到一些挑战。虽然Visual Studio .NET的安装项目提供了一些帮助,但它们可能不足以满足所有需求。此外,使用如InstallShield这样的商业产品虽然方便,但会增加成本。因此,寻找一个免费的解决方案是很有必要的。

在MSDN上,发现了一篇关于使用Installer类和自定义动作来实现这一功能的文章。原始代码是用VB.NET编写的,但本文将其移植到C#,并添加了一些发现有用的新特性。

首先,需要创建一个从System.Configuration.Install派生的类,并在解决方案中添加两个名为install.txt和uninstall.txt的嵌入式资源。install.txt将包含数据库的SQL脚本,而uninstall.txt将包含删除脚本。

对于数据库脚本,使用了Microsoft团队为ASP.NET InSQL会话状态制作的ASPstate脚本:

<sessionState mode="SQLServer" stateConnectionString="tcpip=127.0.0.1:42424" sqlConnectionString="data source=aleph;User ID=ASPsession;Password=ASPsession;" cookieless="false" timeout="60" />

为了使Web应用程序能够在客户端服务器上工作,需要创建一个包含应用程序和SQL脚本的MSI。为了在安装时运行脚本,创建了一个名为ScriptInstall的.dll,其代码如下:

安装器类代码

首先,声明一个string,它将有一个默认值,并且可以通过Install方法被覆盖:

string conStr = "packet size=4096;integrated security=SSPI;" + "data source=\"(local)\";persist security info=False;" + "initial catalog=master";

使用两个函数来返回连接字符串和脚本内容:

private static string GetScript(string name) { Assembly asm = Assembly.GetExecutingAssembly(); Stream str = asm.GetManifestResourceStream(asm.GetName().Name + "." + name); StreamReader reader = new StreamReader(str); return reader.ReadToEnd(); } private static string GetLogin(string databaseServer, string userName, string userPass, string database) { return "server=" + databaseServer + ";database=" + database + ";User ID=" + userName + ";Password=" + userPass; }

然后,使用两个函数来在SQL服务器上运行install.txt和uninstall.txt。ExecuteSQL有一个正则表达式,它在GO之后分割脚本,以便可以使用SQLCommand逐个执行,因为如果SQL脚本包含"GO",ADO.NET会抛出异常。

private static void ExecuteSql(SqlConnection sqlCon) { string[] SqlLine; Regex regex = new Regex("^GO", RegexOptions.IgnoreCase | RegexOptions.Multiline); string txtSQL = GetScript("install.txt"); SqlLine = regex.Split(txtSQL); SqlCommand cmd = sqlCon.CreateCommand(); cmd.Connection = sqlCon; foreach (string line in SqlLine) { if (line.Length > 0) { cmd.CommandText = line; cmd.CommandType = CommandType.Text; try { cmd.ExecuteNonQuery(); } catch (SqlException) { // rollback ExecuteDrop(sqlCon); break; } } } } private static void ExecuteDrop(SqlConnection sqlCon) { if (sqlCon.State != ConnectionState.Closed) sqlCon.Close(); sqlCon.Open(); SqlCommand cmd = sqlCon.CreateCommand(); cmd.Connection = sqlCon; cmd.CommandText = GetScript("uninstall.txt"); cmd.CommandType = CommandType.Text; cmd.ExecuteNonQuery(); sqlCon.Close(); }

现在有了这些函数,可以覆盖Install(IDictionary stateSaver)和Uninstall(IDictionary savedState)。在Install方法中,除了在服务器上运行SQL脚本外,还保存了用户提交的连接数据。保存明文连接字符串是危险的,所以使用RijndaelManaged来加密它。可以在源代码中找到这个类。保存连接字符串是因为需要在卸载时删除数据库ASPstate。

public override void Install(IDictionary stateSaver) { base.Install(stateSaver); if (Context.Parameters["databaseServer"].Length > 0 && Context.Parameters["userName"].Length > 0 && Context.Parameters["userPass"].Length > 0) { conStr = GetLogin(Context.Parameters["databaseServer"], Context.Parameters["userName"], Context.Parameters["userPass"], "master"); RijndaelCryptography rijndael = new RijndaelCryptography(); rijndael.GenKey(); rijndael.Encrypt(conStr); // save information in the state-saver IDictionary to be used in the Uninstall method stateSaver.Add("key", rijndael.Key); stateSaver.Add("IV", rijndael.IV); stateSaver.Add("conStr", rijndael.Encrypted); } SqlConnection sqlCon = new SqlConnection(conStr); sqlCon.Open(); ExecuteSql(sqlCon); if (sqlCon.State != ConnectionState.Closed) sqlCon.Close(); }

在Uninstall方法中,如果savedState包含conStr,使用RijndaelCryptography来解密它:

public override void Uninstall(IDictionary savedState) { base.Uninstall(savedState); if (savedState.Contains("conStr")) { RijndaelCryptography rijndael = new RijndaelCryptography(); rijndael.Key = (byte[])savedState["key"]; rijndael.IV = (byte[])savedState["IV"]; conStr = rijndael.Decrypt((byte[])savedState["conStr"]); } SqlConnection sqlCon = new SqlConnection(conStr); ExecuteDrop(sqlCon); }

安装项目

现在安装器类已经完成,可以创建一个安装项目并添加主要输出。在用户界面编辑器中,选择安装下的起始节点。在操作菜单中,选择添加对话框。在添加对话框对话框中,选择文本框(A)对话框,然后点击确定关闭对话框。在操作菜单中,选择向上移动。重复直到文本框(A)对话框位于安装文件夹节点之上。编辑文本框(A)表单的属性如下:

转到自定义动作编辑器,并将主要输出添加到安装和卸载节点。点击安装节点上的主要输出并编辑其属性。在CustomActionData中输入以下内容:

/databaseServer=[EDITA1] /userName=[EDITA2] /userPass=[EDITA3]

在安装器类中使用Context.Parameters获取文本框的值:

conStr = GetLogin(Context.Parameters["databaseServer"], Context.Parameters["userName"], Context.Parameters["userPass"], "master");
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485