实质上,本文的中心还是在讨论C#对SQL Server的增删改查,只是这次创新一点,配合Treeview制作SQL Server数据库查看器。
具体如下图:
根据SQL Server,Test数据库中的表的结构与内容:
编写一个对Test数据库的数据库查看器,点击相应的表则读取该表的内容:
其中上述的程序,点击根节点“Test数据库”是无效的,点击其子节点,则右方的组框改成相应的表名,
同时读出此表的内容。
在制作之前,请根据《【SQL Server】用户的设置与授权、sa用户登录、查询一个数据库中有多少张表》(点击打开链接)确保你的SQL Server以提供SQL身份验证,同时开一个对test拥有数据库操作权限的用户名pc与密码为admin的账户,否则下述不通过数据库路径连接SQL Server的方式可能会无效。
1、首先,解决方案含有的目录示意图如下所示:
其中DB.cs是我自己编写的数据库连接类,相当于M层的角色,采用了《【Java】利用单例模式、可变参数优化Java操作Mysql数据库、JDBC代码的写作》(点击打开链接)中单例的思想编写,确保数据库不会被多次连接占用系统的资源。具体代码如下:
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;//DataTable用到
using System.Data.SqlClient;//一系列的数据库操作类用到
namespace SQLServerConnection
{
class DB : IDisposable
{
private SqlConnection sqlConnection;
// 以下代码,保证该类只能有一个实例
// 在自己内部定义自己的一个实例,只供内部调用
private static DB db = null;
// 这个类必须自动向整个系统提供这个实例对象
// 这里提供了一个供外部访问本class的静态方法,可以直接访问
public static DB getInstance()
{
if (db == null)
{
db = new DB();
}
return db;
}
private DB()// 私有无参构造函数
{
sqlConnection = new SqlConnection("server=.\\SQLEXPRESS;uid=pc;pwd=admin;database=test");
sqlConnection.Open();
}
//单例化结束
public DataTable getBySql(string sql, Object[] param)
{//查询
sql = String.Format(sql, param);//用字符串参数替换的形式防止注入
SqlDataAdapter sqlDataAdapter = new SqlDataAdapter(new SqlCommand(sql, sqlConnection));
DataTable dataTable = new DataTable();
sqlDataAdapter.Fill(dataTable);
return dataTable;
}
public DataTable getBySql(string sql)
{//无参数的查询
SqlDataAdapter sqlDataAdapter = new SqlDataAdapter(new SqlCommand(sql, sqlConnection));
DataTable dataTable = new DataTable();
sqlDataAdapter.Fill(dataTable);
return dataTable;
}
public void setBySql(string sql, Object[] param)
{ //无查询结果的修改
sql = String.Format(sql, param);//用字符串参数替换的形式防止注入
new SqlCommand(sql, sqlConnection).ExecuteNonQuery();
}
public void setBySql(string sql)
{ //无参数,无查询结果的修改
new SqlCommand(sql, sqlConnection).ExecuteNonQuery();
}
public void Dispose()
{//相当于析构函数
sqlConnection.Close();
//在C#中关闭数据库连接不能在类的析构函数中关,否则会抛“内部 .Net Framework 数据提供程序错误 1”的异常
//通过实现C#中IDisposable接口中的Dispose()方法主要用途是释放非托管资源。
}
}
}
C#对SQL Server的查询很简单。与其它编程语言类似,先打开数据库,查完再关闭数据库连接。
只是,查出来的结果,C#是直接放到SqlDataAdapter中,再直接Fill方法填充到DataTable里面。
这里主要提供两个方法,一个是getBysql提供查询使用,也就是从SQL Server查数据使用,对应的则是setBySql,用作写数据的,虽然这个程序在后续没有任何要向数据库写的地方,也就是没有用到方法,但你感兴趣可以继续按照《【C#】ListView的使用,对Access数据库的增删改查》(点击打开链接)的思路做下去,进一步提供增删改的功能。
上述两个方法提供不同参数的重载,主要是用于应对带参数与不带参数的查询。
这里采用string.format("{0}{1}",new Object[]{"a","b"});将a,b分别替换{0}与{1}这个位置,防止任何形式的SQL注入。比如你补个and 1 or 1在后面是没叼用的,最终会形成where xx='and 1 or 1'。
同时由于C#规定不能在构造函数中关连接,这里只好实现IDisposable的接口了。
之后就是主窗体Form1了,布局及其所要修改的属性如下,相当于V层了:
最后是Form1.cs,相当于C层的代码,具体请看注释,承担将数据库的查询结果与其它组件连接起来的角色。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Data.SqlClient;
namespace SQLServerConnection
{
public partial class Form1 : Form
{
DB db;
public Form1()
{
InitializeComponent();
}
//选择树节点触发
private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
{
if (e.Node.Parent != null)//剔除根节点,点击根结点是没有任何动作的!
{
string table_name = e.Node.Text;
groupBox2.Text = "表" + table_name;
listView1.Clear();
//查表的信息
DataTable table_info = db.getBySql("select name from syscolumns where id=object_id('{0}')", new Object[] { table_name });
for (int i = 0; i < table_info.Rows.Count; i++)
{
for (int j = 0; j < table_info.Columns.Count; j++)
{//生成表头
listView1.Columns.Add(table_info.Rows[i][j] + "", listView1.Width / table_info.Rows.Count - 1, HorizontalAlignment.Left);
}
}
//查表的内容
DataTable table = db.getBySql("select * from [{0}]", new Object[] { table_name });
listView1.BeginUpdate();//数据更新,UI暂时挂起,直到EndUpdate绘制控件,可以有效避免闪烁并大大提高加载速度
for (int i = 0; i < table.Rows.Count; i++)
{
ListViewItem listViewItem = new ListViewItem();//生成每一列
for (int j = 0; j < table.Columns.Count; j++)
{
if (j <= 0)
{
listViewItem.Text = table.Rows[i][j] + "";
}
else
{
listViewItem.SubItems.Add(table.Rows[i][j] + "");
}
}
listView1.Items.Add(listViewItem);
}
listView1.EndUpdate();//结束数据处理,UI界面一次性绘制。
}
}
private void Form1_Load(object sender, EventArgs e)
{
try
{
TreeNode root_node = new TreeNode();//建立节点
root_node.Text = "Test数据库";
treeView1.Nodes.Add(root_node);
db = DB.getInstance();//初始化数据库查询单例DB.cs
DataTable table_name = db.getBySql("SELECT name FROM sysobjects WHERE (xtype = 'U')");//查询test数据库表有多少张表
for (int i = 0; i < table_name.Rows.Count; i++)//遍历查询出来的结果表(视图)
{
for (int j = 0; j < table_name.Columns.Count; j++)
{
TreeNode treeNode = new TreeNode();
treeNode.Text = table_name.Rows[i][j] + "";
root_node.Nodes.Add(treeNode);//一一将查询结果,也就是表名添加到树节点
}
}
}
catch
{
MessageBox.Show(this.Text, "数据库出错!");
Environment.Exit(1);
}
}
}
}
上述展示了TreeView的使用。主要是对其节点TreeNode的操作,节点名称同样是Text,在节点下添加节点用父节点.Nodes.Add(子节点);
而ListView的具体使用,在《【C#】ListView的使用,对Access数据库的增删改查》(点击打开链接)中讲过了,这里不再赘述了。