在ASP.NET对于SQL Server数据库的增删改查自然是便利的,ASP.NET提供了大量封装好的数据库表格,例如GridView什么的,但这些组件只能通过VS自带的属性进行样式的修改,远不如类似与其它语言的循环结构foreach的Repeater,能够自己在“源”代码中的修改遍历,当然这个组件并不是这么好驾驭是真的。
下面有一个例子说明这个组件的运用。
在数据库test中有一张用烂的user_info表,结构如下:
建表语句如下:
CREATE TABLE [dbo].[user_info](
[id] [int] IDENTITY(1,1) NOT NULL,
[username] [varchar](50) NULL,
[password] [varchar](50) NULL,
PRIMARY KEY CLUSTERED
(
[id] ASC
)WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
在Default.aspx页面,完成对这个表的增删改查操作。
要求不能出现相同的用户名。
解决方案的目录结构如下所示:
其中DB.cs是《【C#】利用C#窗体与SQL Server的连接、Treeview制作SQL Server数据库查看器》(点击打开链接)中介绍过的数据库查询类,窗体程序与ASP.NET是通用的,代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data;//DataTable用到
using System.Data.SqlClient;//一系列的数据库操作类用到
using System.Configuration;
namespace Repater
{
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(ConfigurationManager.ConnectionStrings["myCon"].ToString());
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()方法主要用途是释放非托管资源。
}
}
}
唯一的不同,是这个数据库连接字符串与《【ASP.NET】将数据库连接字符串写在Web.config》( 点击打开链接)同样在Web.Config中配置过,再调用:
<connectionStrings>
<add name="myCon"
connectionString="server=.\SQLEXPRESS;database=test;Trusted_Connection=SSPI;"
providerName="using System.Data.SqlClient;"/>
</connectionStrings>
在Defalut.aspx的界面布置如下,双击Repeater添加一个OnItemCommand="Repeater1_ItemCommand"事件这一点很关键,是整个工程的灵魂。同时双击下方的“添加”按钮,添加一个button,onclick事件。
之后,先不急着在Default.aspx.cs中编程,先修改源视图中的代码,也就是Default.aspx,具体如下:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Repater._Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Repeater ID="Repeater1" runat="server" OnItemCommand="Repeater1_ItemCommand">
<HeaderTemplate><%--不参与循环的头部--%>
<table border="1">
<tr>
<th>
用户名
</th>
<th>
密码
</th>
<th>
操作
</th>
</tr>
</HeaderTemplate><%--循环产生的部分,被绑定的数据也就是DataTable有多少行,这里就会循环多少次--%>
<ItemTemplate>
<tr>
<td><%--输出数据,利用到<%#Eval("username")%>,里面的任何一个符号,主要是双引号是不能改变的--%>
<asp:TextBox ID="TextBox1" runat="server" Text='<%#Eval("username")%>' />
</td><%--因此如果出现如上的引号嵌套,仅能将外面的引号改成单引号--%>
<td>
<asp:TextBox ID="TextBox2" runat="server" Text='<%#Eval("password")%>' />
</td>
<td><%--这里,CommandName,CommandArgument都是传给OnItemCommand="Repeater1_ItemCommand"的参数--%>
<asp:Button runat="server" CommandName="update" CommandArgument='<%#Eval("id")+","+(Container as RepeaterItem).ItemIndex%>'
Text="修改" /><%--修改按钮比较复杂,除了在CommandName传递一个update给后台,标识自己是修改,还要同时传递两个参数一个打印的id,与该行的行数用于发现TextBox--%>
<asp:Button runat="server" CommandName="del" CommandArgument='<%#Eval("id")%>' Text="删除"
OnClientClick='return confirm("确定此操作吗?")' />
</td>
</tr>
</ItemTemplate>
<FooterTemplate><%--不参与循环的尾部--%>
</table>
</FooterTemplate>
</asp:Repeater>
<b>插入用户:</b><br /><%--这里没啥好说的,就一个asp.net中常见的表单--%>
用户名:<asp:TextBox ID="TextBox1" runat="server" />密码:<asp:TextBox ID="TextBox2" runat="server" />
<asp:Button runat="server" ID="Button1" Text="添加" OnClick="Button1_Click" />
</div>
</form>
</body>
</html>
具体见相应的注释,这里,其实主要的难点就出现在,输出绑定数据的引号的问题,它<%#Eval("DataTable的字段(列)名")%>一定要双引号,非常奇怪,出现引号嵌套,你还不能用《【JavaScript】引号嵌套问题与Javascript中多行HTML写作方案》( 点击打开链接)那套规则来搞,它一定要双引号,外面值能改成单引号咯,否则会出现如下的错误:
同时,如果你要输出绑定数据的同时,同时带上其它数据的输出,那你仅能类似这种形式搞了:CommandArgument='<%#Eval("id")+","+(Container as RepeaterItem).ItemIndex%>'这也没办法,ASP.NET是这样规定的。
也可以看到Repeater的代码类似其它语言的foreach,你可以自由地修改里面的输出形式,可以是table,可以是div等等
之后在后端Default.aspx.cs你就这样写,配合Default.aspx完成SQL Server数据库的增删改查:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace Repater
{
public partial class _Default : System.Web.UI.Page
{
DB db;
protected void Page_Load(object sender, EventArgs e)
{
db = DB.getInstance();//必须放在!Page.IsPostBack外面,因为每次离开~加载这个页面,伴随这数据库实例被销毁,你必须将其重新初始化
if (!Page.IsPostBack)//必须有,规定数据不能多次被绑定。
{
Repeater1.DataSource = db.getBySql("select * from [user_info]");
Repeater1.DataBind();//直接通过绑定来完成Repeater的输出
}
}
protected void Repeater1_ItemCommand(object source, RepeaterCommandEventArgs e)
{
if (e.CommandName == "update")//如果点击的是被标记为CommandName="update"的按钮,也就是修改按钮
{
int id = int.Parse(e.CommandArgument.ToString().Split(',')[0]);//这里还真必须用单引号来表示字符,而不是""的字符串~,C#的Split就一个以字符,而不是字符串参数的代码
int itemIndex = int.Parse(e.CommandArgument.ToString().Split(',')[1]);//藏在CommandArgument='<%#Eval("id")+","+(Container as RepeaterItem).ItemIndex%>'逗号后面的参数就是该行行号
TextBox TextBox1 = Repeater1.Items[itemIndex].FindControl("TextBox1") as TextBox;//获得改行的TextBox1
TextBox TextBox2 = Repeater1.Items[itemIndex].FindControl("TextBox2") as TextBox;//获得改行的TextBox2
//这里是修改数据库表的一般逻辑,不赘述了
if (TextBox1.Text.Trim().Equals("") || TextBox2.Text.Trim().Equals(""))
{
Response.Write("<b>用户名,密码不得为空!</b>");
}
else
{
if (db.getBySql("select * from [user_info] where [username]='{0}'", new Object[] { TextBox1.Text }).Rows.Count == 0)//如果没有这个用户名才能修改
{
db.setBySql("update [user_info] set [username]='{0}' where [id]={1}", new Object[] { TextBox1.Text, id });
db.setBySql("update [user_info] set [password]='{0}' where [id]={1}", new Object[] { TextBox2.Text, id });
//数据绑定并不意味着会自动刷新Repeater1,必须自己再用代码,刷新一下Repeater1
Repeater1.DataSource = db.getBySql("select * from [user_info]");
Repeater1.DataBind();
Response.Write("<b>已修改!</b>");
}
else
{
Response.Write("<b>已有该用户名!</b>");
}
}
}
if (e.CommandName == "del")//删除按钮
{
db.setBySql("delete from [user_info] where [id]=" + e.CommandArgument);//e.CommandArgument就是藏着该项的数据库id
//刷新Repeater1
Repeater1.DataSource = db.getBySql("select * from [user_info]");
Repeater1.DataBind();
Response.Write("<b>已删除!</b>");
}
}
protected void Button1_Click(object sender, EventArgs e)
{
//添加按钮
if (TextBox1.Text.Trim().Equals("") || TextBox2.Text.Trim().Equals(""))
{
Response.Write("<b>用户名,密码不得为空!</b>");
}
else
{
if (db.getBySql("select * from [user_info] where [username]='{0}'", new Object[] { TextBox1.Text }).Rows.Count == 0)
{
db.setBySql("insert into [user_info]([username],[password]) values('{0}','{1}')", new Object[] { TextBox1.Text, TextBox2.Text });
//刷新Repeater1
Repeater1.DataSource = db.getBySql("select * from [user_info]");
Repeater1.DataBind();
Response.Write("<b>添加成功!</b>");
}
else
{
Response.Write("<b>已有该用户名!</b>");
}
}
}
}
}
可以看到,这里怎么通过CommandName与CommandArgument操纵在Repeater的按钮。
需要注意的是,在Page_Load方法中,Repeater的数据绑定操作,必须放在if (!Page.IsPostBack){}之中,否则会出现如下的错误: