简介
在本次学习中,我们将会从最基本的开始,建立一个数据访问层,DAL(Data Access Layer),使用DataSets类,来访问数据库中的信息。
随着互联网的发展,我们的生活总是在带有数据的工作中进行的。我们建立数据库来存储数据,编写代码来取回或者更改它们,并且我们制作页面来收集汇总这些数据。这是我们的漫长学习中的第一课,我们将会来探究在asp2.0中实现这些常用的模式的技术。我们将会从建立一个软件架构开始,该架构包括使用DataSets类的数据访问层(DAL)、执行客户业务规则的业务逻辑层(BLL)和一个由asp.net页构成的表示层,这些表示页将会共享一些常用的界面风格。当这些后台的基础被搭建好后,我们将会来展示如何从一个web应用来显示、汇总、搜集最终使其变为可用的数据。(第一个教程将会很长,但是剩下的将会被分解为更多的容易理解的小模块)
在本次学习中,我们将会使用到MS SQL Server 2005 Express Edition中的Northwind数据库,它存在于App_Data目录中。除了数据库文件,在App_Data文件夹中也包含了建立这个数据库的SQL脚本,该脚本在你想使用不同版本的数据库时或许会有用处。如果你想要的话,你也可以从微软的站点中直接下载。如果你使用的是不同版本的SQL Server中的Northwind数据库的话,你需要更新NORTHWINDConnectionString设置,该设置在web应用中的Web.config文件里。这个web应用是使用Visual Studio 2005 Professional Edition建立的一个system-baseed网站项目文件。尽管如此,所有的教程都将会很好的运行在免费版本的Visual Studio 2005中,那就是Visual Web Developer.
在教程(1)中,我们将会从最基本的开始,建立一个数据访问层(DAL),接下来将会在教程(2)中建立一个业务逻辑层(BLL),在教程(3)中我们将会在页面和导航中让它运行起来。在三次的学习之后,我们将会在之前建立的基础上进行编译。作为探究的开端,我们将会学习到很多,所以赶快打开你的Visual Studio,开始我们的学习吧!
第一步:建立一个web项目并且与数据库相连在我们建立数据访问层(DAL)之前,我们首先需要创建一个站点并且建立数据库。开始建立一个新的system-based的asp.net站点文件。File→New Web Site,在对话框中选择ASP.NET Web Site,将Location设置为“File System”,选择一个文件夹来存放这个站点,并且语言选择“C#”。
图片1:建立新的System-Based站点这将建立一个新的带有名为“Default.aspx”的asp.net页和一个App_Data文件夹的站点。
建立站点后,下一步是添加在Visual Studio的Server Explorer中对于数据库的引用。添加数据库后,你便可以添加表,存储过程,视图,和从Visual Studio中取得的任何内容。你同样可以浏览表中的数据,手写或者利用试图表示来建立自己的查询语句。那么在今后当我们为了建立数据访问层而建立DataSets类的时候,我们就需要将Visual Studio指向这个数据库,通过这种方式我们才能建立DataSets类。当我们能够及时地提供这些连接信息的时候,Visual Studio便会在Server Explorer中自动地创建一个由已注册的数据库组成的下拉列表。
如何向Server Explorer中添加Northwind数据库的步骤取决于你想使用的是App_Data文件夹中的SQL Server 2005 Express Edition版本的数据库还是SQL Server 2000 或 2005版本的数据库服务器。
使用在App_Data文件夹中的数据库如果你没有一个SQL Server 2000/2005的数据库服务器用来连接,或者你只是想避免向数据库服务器中添加数据库的话,那么你就可以使用Northwind数据库的SQL Server 2005 Express Edition版本,它存在于被下载的站点的App_Data文件夹中(NORTHWND.MDF)。
存在于App_Data中的数据库都会自动地被添加到Server Explorer中。假设在你的机器中安装了SQL Server 2005 Express Edition,那么在Server Explorer中你应该能看到一个叫做“NORTHWND.MDF”的节点,你可以展开这个节点,查看他包含的表、视图、存储过程等等(见图片2)。
App_Data文件夹同样可以保存Access数据库的.mdb文件,同样会像SQL Server那样自动地添加到Server Explore中。如果你不想使用任何的SQL Server的选项的话,你也可以下载一个Access版的Northwind数据库文件并且把它放在App_Data目录中。注意,尽管如此,Access数据库没有像SQL Server那样的优势,并且它也不是被设计用来做web站点的。并且,在35+的两个教程中将会利用Access不支持的数据库级别的特点。
连接MS SQL Server 2000/2005数据库服务器中的数据库作为选择,你可能会连接一个建立在数据库服务器中的Northwind数据库。如果这个数据库服务器中还没有建立Northwind数据库的话,你首先必须得执行本教程中的建立语句或者直接从微软的站点下载SQL Server 2000版本的Northwind数据库和执行语句,然后把它加入到你的数据库服务器中。
当你建立了这个数据库后,在Visual Studio的Server Explorer中,右击Data Connections节点,点击"Add Connection"。如果你没有看到Server Explorer的话,那么你可以点击View→Server Explorer,或者点击Ctrl+Alt+S(直到现在我才知道,这里的Server Explorer对应到我所使用的这个Visual Studio中的是Database Explorer)。这将会弹出对话框,通过它你可以制定想要连接的数据库,一些鉴定的信息和数据库的名字。一旦你成功地确认了数据库连接的相关信息并且点击了[OK]按钮后,那么这个数据库将会以节点的形式被添加在Data Connections节点的下边。你可以展开这些节点,浏览它所包含的表、视图、存储过程等等。(此处他讲的不是很详细,我把我的操作过程描述一下。在弹出的对话框中选择“Microsoft SQL Server”,然后点击[Continue],会出现设置数据库连接的东东,按照以前的经验,在Server name中填写“(local)”,在Log on to the server中选择Use SQL Server Authentication选项,然后填写登陆的id和密码,这时在Connect to a database中就会出现让你选择数据库的下拉列表了,很顺利地,我找到了Northwind数据库。如果你想看看是不是连接成功了,你可以点击左下角的[Test Connection]按钮,连接成功后会弹出“Test connection succeeded”的消息框。最后你点击[OK]按钮就OK了)
图片2:建立同Northwind数据库的连接第二步:建立数据访问层
进行数据库编程的一个方法是直接在表示层中插入代码(在一个web应用中,表示层是由asp.net页构成的)。这个或许会采用在asp.net页中编写ADO.NET代码的形势,或者是使用SqlDataSoruce控件来实现。无论使用哪种方法,都能够将数据访问逻辑同表示层紧密地联系到一起。尽管如此,推荐的方法还是将数据访问逻辑同表示层分离开来。那么这个被分离出来的数据访问逻辑就被称为数据访问层(Data Access Layer),简称DAL,并且被建成了一个特别的分离的类库工程。这种分层架构的优点是具有很好的文档性(关于此优点请察看笨教成最后的“更深一层的阅读”部分)并且是我们将在本教程中使用的方法。
所有被使用下划线标记出来的代码都应该被放在数据访问层中,比如建立同数据库的连接,使用SELECT\INSERT\UPDATE\DELETE等命令。表示层中不应该存在任何通数据访问相关的代码,但是应该包含对数据访问层的数据请求的调用。例如,在Northwind数据库中,它包含了Products和Categories表,表中记录了出售商品及其所属的类别。在我们要建立的数据访问层中,我们将会用到以下的方法:
GetCategories(),将会返回关于所有类别的信息
GetProducts(),将会返回所有商品的信息
GetProductsByCategoryID(categoryID),将会返回所有属于某一类别的商品信息
GetProductByProductID(productID),将会返回某一商品的信息
调用这些方法,将会连接数据库,调用适当的方法,并且返回结果。重要的是我们如何返回这些结果。这些方法能够通过执行一些查询简单地返回一个DataSet或者DataReader,但是理想的返回结果应该是以强类别对象(strongly-typed objects)的形式返回。强类别对象是指它的图表在编译的时候就被严格的定义好了,相反的,弱类别对象是指程序在真正地运行之前图表内容还不知道呢。(这句翻译得不好,原文是A strongly-typed object is one whose schema is rigidly defined at compile time, whereas the opposite, a loosely-typed object, is one whose schema is not known until runtime.)
例如,一个弱类别对象的DataReader和DataSet(默认的),他们的图表是对(通过执行查询后返回的)列定义,那么如果我们想要访问一个弱类别定义的DataTable的话,我们需要使用像这样的语句:DataTable.Rows[index]["columnName"]。那么本例中的DataTable的弱定义就显现出来了,事实就是,我们不得不通过使用一个字符串或者序号来访问列的名称。如果是一个强类别的DataTable的话,将会使它的列作为属性被实现,我们就可以使用类似下边这样的代码:DataTable.Rows[index].columnName。
为了返回一个强类别的对象,开发者或者可以创建自己的业务对象,或者可以使用DataSets类。一个业务对象被开发者定义成为一个类,这个类(此处不好翻译,原文:A business object is implemented by the developer as a class whose properties typically reflect the columns of the underlying database table the business object represents)。一个DataSet类是在一个数据库图表的基础上由Visual Studio为你建立的一个类,这个类的成员根据图表被强类别(很好的定义)了。DataSet类本身包括由ADO.NET的DataSet,DataTable和DataRow类扩展出来的类。DataSet类同样包含TableAdapters,它是一个包含能够实现DataSet的DataTables并且能够将对DataTables中的修改传回给数据库的类。
我们将会使用强类别的DataSets来构建本教程的架构。图片3显示了在一个使用DataSets类的应用中的不同层之间的工作流程。
图片3:所有的数据访问代码被传递给DAL
建立一个Dataset和table adapter类
为了开始建立我们的DAL,我们首先向我们的项目中添加一个DataSet类。右击在Solution Explorer中的Project节点,选择“Add New Item”。选择Templates列表中的DataSet选项,并且将其命名为Northwind.xsd。
图片4:为你的项目添加一个新的DataSet
当点击[Add]按钮后,将会询问你是否将DataSet添加到App_Code文件夹中,选择是。这个DataSet类的设计视图就会显示出来了,并且TableAdapter的配置向导也会开始,它允许你可以向你的DataSet类中添加第一个TableAdapter类。
DataSet类是作为强类别的数据集和被提供的;它是由强类别的DataTable实例构成的,每个DataTable实例又是由强类别的DataRow实例构成的。我们将会为下边的数据库的每个表建立一个强类别的DataTable。让我们开始为Products表建立一个DataTable吧。
你要记住的是强类别的DataTables不包含任何的关于如何从他们的基本的数据表中访问数据的信息。为了找到能够填入DataTable中的数据,我们用到了TableAdapter类,这个类将起到数据访问层的作用。为了建立Products的DataTable,TableAdapter将包含GetProducts(),GetProductByCategoryID(categoryID)等方法,这些方法都是我们将要从表示层调用的。这个DataTable的角色就是作为在两层之间传递数据的一个强类别的对象。
TableAdapter配置向导首先会让你选择你将在那个数据库中工作。这个下拉列表中显示了在Server Explor中显示的数据库。如果你没有将Northwind数据库添加到Server Explorer的话,你可以点击[New connection]按钮来添加它。
图片5:从下拉列表中选择Northwind数据库
选择完数据库并点击[Next]按钮后,你将会被询问是否将连接的字符串保存在Web.config文件中。在保存的时候,你应该避免将代码硬性地写入TableAdapter类中,其实在将来连接字符串变动的时候将会简化一些操作。如果你选择了将连接字符串保存在配置文件的<connectionStrings>部分里的话,那么你就可以随意的进行加密,以此来提高安全性,并且在今后你可以通过新的在IIS GUI Admin工具中的asp.net 2.0属性页来进行修改,这种方法对于管理者来说是更加理想的。
图片6:将连接字符串存入Web.config中
接下来,需要为我们的第一个强类别的DataTable定义一个图表,并且为TableAdapter提供第一个可使用的方法来填充这个强类别的DataSet。这两个步骤可以通过建立返回列的查询来同时完成。查询是对对我们要映射到DataTable中的表的查询。在向导的最后我们将会给这次查询建立一个方法名。当完成上述操作后,这个方法就可以在表示层中被调用了。这个方法将会执行定义的查询并且返回一个强类别的DataTable。
在开始定义SQL查询的时候,我们必须首先指出我们想要如何让TableAdapter来发出这个查询。我们可以使用一个ad-hoc SQL 声明,建立一个新的存储过程,或者使用一个已经存在的存储过程。在教程当中,我们将会使用ad-hoc SQL 声明。
图片7:使用Ad-Hoc SQL 声明来查询数据
这个时候我们便可以输入查询的语句了。当在TableAdapter中建立第一个方法的时候,你想要返回在相应的DataTable中想要表达的列。我们可以通过返回Products表中所有列的方式来完成以上的工作。
图片8:在文本框中输入SQL查询语句
另外一种方法可以使用Query Builder和视图来构建一个查询,像图片9所示的那样。
图片9:在查询编辑器中建立查询视图
建立完查询后,在到下一个界面之前,点击[Advance Options]按钮。在Web Site Projects中,“Generate Insert, Update, and Delete statements”是唯一一个默认选中的选项;如果你是从一个类库或者一个Windows项目中运行的该向导的话,“Use optimistic concurrency”同样也会被选中。保持“Use optimistic concurrency”不被选中。
图片10:只选中“Generate Insert, Update, and Delete statements”选项
在确定了这些高级选项后,点击[Next]按钮进入最后一部分。这里我们将会被要求选择向TableAdapter中添加哪种方法。这里有两种模式来填充数据:
Fill a DataTable - 这将会建立一种方法,这种方法会把DataTable作为一个参数来接收,并且以查询的结果作为基础来填充这个DataTable。例如,ADO.NET的DataAdapter类,就是通过使用它的Fill()方法来实现这种模式的。
Return a DataTable - 这种方式会将建立并填充一个DataTable并且将这个DataTable作为返回的值。
你可以让TableAdapter使用上述的一种或两种模式来实现。你也可以重新给这些方法命名。让我们保持两个选项都被选中,尽管我们在这里只是用后边的这种模式。并且让我们重新命名一下这个名字不太特殊的GetData方法,重命名为“GetProducts”。
如果选中了最后那个选项,将会为这个TableAdapter建立Insert()、Update()和Delete()方法。如果你没有选中它的话,所有的更新将需要通过TableAdapter的Update()方法来执行,这种方法接受DataSet类,一个DataTable,一个单独的DataRow或者一组DataRow。(如果你在图片9中就没有选择“Generate Insert, Update, and Delete statements”选项的话,那么这个选项也将不起作用)。保持这个选项被选中。
图片11:将方法重新命名为“GetProducts”
点击[Finish]按钮完成安装向导。当向导关闭后我们将会返回到DataSet的设计视图,在这个视图中显示了我们刚刚建立的那个DataTable。你可以看到在Products的DataTable的列的列表(ProductID,ProductName,等等),还有ProductsTableAdapter的方法(Fill()和GetProducts())。
图片12:Products DataTable和ProductsTableAdapter也被添加到了DataSet类中
现在我们有了一个包含一个单独的DataTable(Northwind.Products)的DataSet类和一个强类别的DataAdapter类(NorthwindTableAdapters.ProductsTableAdapter),这个类还包含一个GetProducts()的方法。通过以下的代码这些对象可以用来访问所有的商品(Product)。
C#
NorthwindTableAdapters.ProductsTableAdapter productsAdapter = new NorthwindTableAdapters.ProductsTableAdapter();
Northwind.ProductsDataTable products;
products = productsAdapter.GetProducts();
foreach (Northwind.ProductsRow productRow in products)
Response.Write("Product: " + productRow.ProductName + "<br />"); 这并不需要我们自己手写这些代码。我们不需要实例化任何ADO.NET类,我们不需要关联任何连接字符串、SQL查询或者存储过程。取而代之的是TableAdapter已经为我们生成了这些简易的数据访问代码。
这个例子中使用的每个对象都是强类别的,允许Visual Studio提供智能和实时编译的类别检查。并且这些TableAdapter返回的DataTables可以被绑定到asp.net的web控件上,就像GridView,DateilsView,DropDownList,CheckBoxList等等。接下来的例子将展示如何把通过GetProducts()方法返回的DataTable绑定到GridView控件上,这只需要在Page_Load事件中编写不到3行的代码。
AllProducts.aspx
<%...@ Page Language="C#" AutoEventWireup="true" CodeFile="AllProducts.aspx.cs" Inherits="AllProducts" %>
<!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>View All Products in a GridView</title>
<link href="Styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
<form id="form1" runat="server">
<div>
<h1>
All Products</h1>
<p>
<asp:GridView ID="GridView1" runat="server"
CssClass="DataWebControlStyle">
<HeaderStyle CssClass="HeaderStyle" />
<AlternatingRowStyle CssClass="AlternatingRowStyle" />
</asp:GridView>
</p>
</div>
</form>
</body>
</html> AllProducts.aspx.cs
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using NorthwindTableAdapters;
public partial class AllProducts : System.Web.UI.Page
...{
protected void Page_Load(object sender, EventArgs e)
...{
ProductsTableAdapter productsAdapter = new
ProductsTableAdapter();
GridView1.DataSource = productsAdapter.GetProducts();
GridView1.DataBind();
}
}
图片13:在GridView中显示的Products列表
这个例子需要我们在asp.net页的Page_Load事件中写3行代码,在将来的学习中,我们将了解如何使用ObjectDataSource从数据访问层显式地取回数据。通过ObjectDataSource我们不再需要写任何代码并且还能很好地获得分页的功能!
第三步:向数据访问层添加带参数的方法
现在我们的ProductsTableAdapter类只包含一个方法,GetProducts(),这个方法返回数据库中的所有商品。但是有时我们只想返回某一特殊商品的信息,或者只属于某种类别的商品信息。我们可以向TableAdapter中添加带参数的方法来实现向数据访问层添加这种功能。
首先我们添加GetProductsByCategoryID(categoryID)方法。要向DAL添加一个新的方法,返回DataSet的设计视图,右击ProductsTableAdapter部分,选择“Add Query”。
图片14:右击TableAdapter并且选择“Add Query”
我们首先会被询问是否想要使用一个*******************或者是一个新的或已经存在的存储过程。我们再次选择使用ad-hoc SQL***。接下来我们会被询问我们将要使用哪种类型的SQL查询。因为我们想返回属于某种类别的商品,所以我们要选择一个返回行的SELECT语句。(We are first prompted about whether we want to access the database using an ad-hoc SQL statement or a new or existing stored procedure. Let's choose to use an ad-hoc SQL statement again. Next, we are asked what type of SQL query we'd like to use. Since we want to return all products that belong to a specified category, we want to write a SELECT
statement which returns rows.)
图片15:选择一个返回行的SELECT语句
下一步是定义一个用来访问数据的SQL语句。因为我们只想返回某一特定类别的商品,我们将会使用跟GetProducts()方法相同的SELECT语句,但是我们填加下边的子句:WHERE CategoryID = @CategoryID。这个@CategoryID参数向TableAdapter向导指示了我们建立的这个方法将需要一个对应类型的输入参数。
图片16:输入返回某一特定类别的商品的查询语句
最后我们需要选择使用哪种数据访问的模式,并且把产生的方法的名字改成我们想要的。在Fill模式中,我们将名字改为“FillByCategoryID”,在Return a DataTable模式中,我们将名字改为“GetProductsByCategoryID”。
图片17:为TableAdapter方法选择名字
当向导结束后,在DataSet的设计视图里包含了一个新的TableAdapter方法。
图片18:现在可以通过类别查询了
通过同样的方法我们再建立一个GetProductByProductID(productID)方法。这些带参数的查询可以在DataSet的设计视图中被直接的测试。右击TableAdapter中的方法,并且选择“Preview Data”。
图片19:属于饮料的商品被显示出来了
接下来,输入一个参数值,点击“Preview”。图片19:属于饮料类别的商品被显示出来有了DAL中的GetProductsByCategoryID(categoryID)方法,我们现在便可以创建一个asp.net页来显示属于某种特殊类别的商品了。下边的这个例子将显示所有属于饮料类别的商品,该CategoryID为1。
Beverages.aspx
<%...@ Page Language="C#" AutoEventWireup="true" CodeFile="Beverages.aspx.cs" Inherits="Beverages" %>
<!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>Untitled Page</title>
<link href="Styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
<form id="form1" runat="server">
<div>
<h1>Beverages</h1>
<p>
<asp:GridView ID="GridView1" runat="server"
CssClass="DataWebControlStyle">
<HeaderStyle CssClass="HeaderStyle" />
<AlternatingRowStyle CssClass="AlternatingRowStyle" />
</asp:GridView>
</p>
</div>
</form>
</body>
</html> Beverages.aspx.cs
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using NorthwindTableAdapters;
public partial class Beverages : System.Web.UI.Page
...{
protected void Page_Load(object sender, EventArgs e)
...{
ProductsTableAdapter productsAdapter = new
ProductsTableAdapter();
GridView1.DataSource =
productsAdapter.GetProductsByCategoryID(1);
GridView1.DataBind();
}
}
图片20:属于饮料类别的商品被显示出来
第四步:插入,更新和删除数据
通常有两种模式可以用来插入、更新和删除数据。第一种模式,我称之为直接操作数据库模式,它包含创建一些方法,当这个方法被执行的时候,将会向数据库发出一个插入、更新或删除的命令,这条命令将会作用于单一的一条记录。这些方法将会传入一系列的值(integers, strings, Booleans, DateTimes等等),这些值对应到要插入、更新或删除的值。例如,运用这种模式,对于Products表,删除的方法将会带有一个integer类型的参数,用它来指出要删除的记录的ProductID,然而,插入的方法中可能会带有一个代表ProductName的string类型的参数,一个代表UnitPrice的decimale类型的参数和一个代表UnitsOnStock的integer类型的变量,等等。
图片21:每一个插入、更新和删除的请求立即被传送给数据库
另一种模式,我称之为批量更新模式,这种模式将会在一个方法调用中更新整个的DataSet,DataTable,或者是一个DataRows集合。通过这种模式,一个开发者将会在一个DataTable中删除、插入或者修改DataRows,然后将这些DataRows或DataTable传入到一个更新的方法中,这个方法将会计算被传入的这些DataRows,判断它们是否被修改了,添加了或删除了(通过利用DataRows的RowState属性值),然后对每一条记录执行适当的数据库请求。
图片22:当Update方法被调用时,所有的变化将会同时作用于数据库
TableAdapter默认将会使用批处理方式,但同时也支持直接操作数据库模式。由于当建立TableAdapter时,在Advanced Properties中,我们选择了“Generate Insert, Update, and Delete statements”选项,ProductsTableAdapter中包含了一个Update()方法,这个方法将会执行批量更新模式。特别的,TableAdapter包含了一个Update()方法,可以给这个方法传递DataSet类,一个强类别的DataTable,或者一个或更多的DataRows。当你第一次建立TableAdapter时选中了GenerateDBDirectMethods的话,直接操作数据库模式也会通过Insert()、Update()、Delete()方法被执行。
两种模式都是通过使用TableAdapter的InsertCommand、UpdateCommand和DeleteCommand的属性来执行他们的插入、更新和删除命令的。点击在DataSet视图中的TableAdapter然后来到属性窗口,你可以测试和修改InsertCommand、UpdateCommand和DeleteCommand的属性。
图片23:TableAdapter包含InsertCommand、UpdateCommand和DeleteCommand属性
为了测试和修改这些数据库命令属性,点击CommandText子属性,将会弹出一个查询生成器
图片24:在查询生成器中生成Insert、Update和Delete语句
接下来的例子说明了如何使用批处理模式来把所有非discontinued并且units in stock 为25或更少的商品的价格成2倍:
NorthwindTableAdapters.ProductsTableAdapter productsAdapter =
new NorthwindTableAdapters.ProductsTableAdapter();
// For each product, double its price if it is not discontinued and
// there are 25 items in stock or less
Northwind.ProductsDataTable products = productsAdapter.GetProducts();
foreach (Northwind.ProductsRow product in products)
if (!product.Discontinued && product.UnitsInStock <= 25)
product.UnitPrice *= 2;
// Update the products
productsAdapter.Update(products); 接下来的代码说明了如何使用直接操作数据库模式来程式化的删除一条指定的记录,然后更新一条,并且添加一条新的记录:
NorthwindTableAdapters.ProductsTableAdapter productsAdapter = new NorthwindTableAdapters.ProductsTableAdapter();
// Delete the product with ProductID 3
productsAdapter.Delete(3);
// Update Chai (ProductID of 1), setting the UnitsOnOrder to 15
productsAdapter.Update("Chai", 1, 1, "10 boxes x 20 bags",
18.0m, 39, 15, 10, false, 1);
// Add a new product
productsAdapter.Insert("New Product", 1, 1,
"12 tins per carton", 14.95m, 15, 0, 10, false); 创建客户化的插入、更新和删除方法
通过数据库直接创建的Insert()、Update()和Delete()方法有的时候会显得有些笨重,特别市在一个表中包含很多的列的时候。看一下前边的那些代码,如果没有IntelliSense的帮助,对于传递给Update()和Insert()方法的每个输入的参数,我们很难知道这个Products表的列的结构。或许有这样的几种情况,我们只想更新一个或两个列,或者想要客户化一个Insert()方法,这个方法将会返回新创建的这条记录的主键的值。
想要创建这样的一个客户化的方法,返回DataSet视图。右击TableAdapter并且选择Add Query,返回到TableAdapter向导。在第二个对话框中我们可以表明想要建立的查询类型。让我们创建一个方法,该方法会添加一条新的商品记录,并且返回这条新插入的记录的ProductID值。因此,选择创建一个INSERT查询。
图片25:创建一个向Products表中添加一条新的记录的方法
在下一个对话框中,InsertCommand的CommandText将会出现。通过在查询的最后添加SELECT SCOPE_IDENTITY()来扩充这条查询语句,这条语句将会返回新插入的记录中最后一条的主键值。要确保你在插入这条SELECT语句之前的INSERT语句要以分号结束。
图片26:添加返回SCOPE_IDENTITY()的值的查询
最后,将新的方法命名为InsertProduct
图片27:将新的方法命名为InsertProduct
当你返回到DataSet视图时,你会发现ProductsTableAdapter包含了一个新的方法,InsertProduct。如果在这个方法中没有为每一个Products表中的列传递一个参数的话,很有可能是你没有以 结束INSERT语句。一定要确保你是以分号结束INSERT语句的。
按照默认,Insert方法是不会执行任何查询的,它只能返回起作用的记录数。然而,我们想要InsertProduct方法返回由查询返回的值,并不是起作用的列数。要实现这种效果,需要调整InsertProduct方法的ExecuteMode属性为Scalar。
图片28:调整InsertProduct方法的ExecuteMode属性为Scalar
下边的代码表示了如何使用新建立的InsertProduct方法:
NorthwindTableAdapters.ProductsTableAdapter productsAdapter = new NorthwindTableAdapters.ProductsTableAdapter();
// Add a new product
int new_productID = Convert.ToInt32(productsAdapter.InsertProduct("New Product", 1, 1, "12 tins per carton", 14.95m, 10, 0, 10, false));
// On second thought, delete the product
productsAdapter.Delete(new_productID); 第五步:完成数据访问层
我们注意到,ProductsTableAdapters类从Products表中返回了CategoryID和SupplierID的值,但是不包括Categories表的CategoryName列和Suppliers表的CompanyName列,即使这些列都是当展示商品的时候我们希望显示出来的。我们可以扩充一下TableAdapter的构造方法,GetProducts(),让它包含CategoryName和CompanyName这两个列的值,这个方法也会更新这个强类别的DataTable,使它包含这些新的列。
这样将会产生一个问题,这些用来插入、更新和删除数据的TableAdapter的方法都是基于这个构造方法的。幸运的是,这个自动生成的用来插入、更新和删除的方法不会被SELECT中的子查询所影响。通过向我们的查询语句中添加Categories和Suppliers子查询,而不使用JOINS,我们可以避免为了修改数据而不得不重新编写这些方法。在ProducsTableAdapter上右击GetProducts()方法并且选择Configure。然后,调整SELECT语句让它变成下边这样:
SELECT ProductID, ProductName, SupplierID, CategoryID,
QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder, ReorderLevel, Discontinued,
(SELECT CategoryName FROM Categories
WHERE Categories.CategoryID = Products.CategoryID) as CategoryName,
(SELECT CompanyName FROM Suppliers
WHERE Suppliers.SupplierID = Products.SupplierID) as SupplierName
FROM Products
图片29:为GetProducts()方法更新SELECT语句
更新了GetProducts()方法后,这个DataTable将会包含两个新的列:CategoryName和SupplierName。
图片30:Products DataTable有两个新的列
用同样的方法也更新一下GetProductsByCategoryID(categoryID)方法中的SELECT语句。
如果你是使用JOINS语句来更新GetProducts()的SELECT语句的话,DataSet视图将不会自动生成采用数据库直接模式来插入、更新和删除数据的方法。你将不得不像前面的教程中我们处理InsertProduct方法那样手动地建立他们。另外,如果你想使用批处理的更新模式的话,你还需要手动地添加InsertCommand,UpdateCommand和DeleteCommand属性值。
添加剩下的tableadapters
直到现在,我们仅仅是处理单一的数据库表的单独的一个TableAdapter。但是,Northwind数据库包含了许多相关连的表,这些表都是在我们web应用程序中需要用到的。一个DataSet类可以包含多个相关连的DataTables。因此,为了完成我们的数据访问层,需要添加我们将要使用的其他的表所对应的DataTables。要向一个DataSet类中添加DataTables,打开DataSet视图,右击视图,选择Add/TableAdapter。这将会建立一个新的DataTable和TableAdapter并且会打开一个向导。
通过以下的查询来建立以下的TableAdapters和方法。注意,在ProductsTableAdapter中的查询包括了获取每个商品的类别(category)和供货者名字(supplier name)的子查询。并且,如果你是跟着教程一步步地做下来的话,你应该已经添加了ProductsTableAdapter类的GetProducts()和GetProductsByCategoryID(categoryID)方法了。
ProductsTableAdapter
GetProducts
SELECT ProductID, ProductName, SupplierID, CategoryID,
QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder,
ReorderLevel, Discontinued , (SELECT CategoryName FROM
Categories WHERE Categories.CategoryID =
Products.CategoryID) as CategoryName, (SELECT CompanyName
FROM Suppliers WHERE Suppliers.SupplierID =
Products.SupplierID) as SupplierName
FROM Products GetProductsByCategoryID
SELECT ProductID, ProductName, SupplierID, CategoryID,
QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder,
ReorderLevel, Discontinued , (SELECT CategoryName FROM
Categories WHERE Categories.CategoryID =
Products.CategoryID) as CategoryName,
(SELECT CompanyName FROM Suppliers WHERE
Suppliers.SupplierID = Products.SupplierID) as SupplierName
FROM Products
WHERE CategoryID = @CategoryID GetProductsBySupplierID
SELECT ProductID, ProductName, SupplierID, CategoryID,
QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder,
ReorderLevel, Discontinued ,
(SELECT CategoryName FROM Categories WHERE
Categories.CategoryID = Products.CategoryID)
as CategoryName, (SELECT CompanyName FROM Suppliers
WHERE Suppliers.SupplierID = Products.SupplierID)
as SupplierName
FROM Products
WHERE SupplierID = @SupplierID GetProductByProductID
SELECT ProductID, ProductName, SupplierID, CategoryID,
QuantityPerUnit, UnitPrice, UnitsInStock, UnitsOnOrder,
ReorderLevel, Discontinued , (SELECT CategoryName
FROM Categories WHERE Categories.CategoryID =
Products.CategoryID) as CategoryName,
(SELECT CompanyName FROM Suppliers
WHERE Suppliers.SupplierID = Products.SupplierID)
as SupplierName
FROM Products
WHERE ProductID = @ProductID CategoriesTableAdapter
GetCategories
SELECT CategoryID, CategoryName, Description
FROM Categories GetCategoryByCategoryID
SELECT CategoryID, CategoryName, Description
FROM Categories
WHERE CategoryID = @CategoryID SuppliersTableAdapter
GetSuppliers
SELECT SupplierID, CompanyName, Address, City,
Country, Phone
FROM Suppliers GetSuppliersByCountry
SELECT SupplierID, CompanyName, Address,
City, Country, Phone
FROM Suppliers
WHERE Country = @Country GetSupplierBySupplierID
SELECT SupplierID, CompanyName, Address,
City, Country, Phone
FROM Suppliers
WHERE SupplierID = @SupplierID EmployeesTableAdapter
GetEmployees
SELECT EmployeeID, LastName, FirstName,
Title, HireDate, ReportsTo, Country
FROM Employees GetEmployeesByManager
SELECT EmployeeID, LastName, FirstName,
Title, HireDate, ReportsTo, Country
FROM Employees
WHERE ReportsTo = @ManagerID GetEmployeeByEmployeeID
SELECT EmployeeID, LastName, FirstName,
Title, HireDate, ReportsTo, Country
FROM Employees
WHERE EmployeeID = @EmployeeID
图片31:在四个TableAdapters后边的DataSet视图已经被添加进来
向数据访问层添加客户化的代码
向DataSet类中添加的TableAdapter和DataTables是作为一个XML摘要定义文件(Schema Definition file(Norghwind.xsd))被表示的。你可以通过右击在Solution Explorer中的Norghwind.xsd文件并选择View Code来查看这个摘要信息。
图片32:Northwinds DataSet类的XML摘要定义文件(XSD)
这个摘要信息在编译或者运行时(如果需要)会被传递给在设计阶段的C#或Visual Basic代码,这个时候你就可以一步步地进行调试了。想要查看这个自动生成的代码,在Class View中点开TableAdapter或DataSet类。如果你看不到Class View的话,可以到View菜单并选种它或者按Ctrl+Shift+C键。在类视图(Class View)中,你可以看到DataSet类和TableAdapter类的属性、方法和事件。想要查看某个特殊的方法,双击在类视图中这个方法的名或者右击并选择Go To Definition。
图片33:通过在类视图中选择Go To Definition来查看自动生成的代码
虽然利用自动生成代码会节省很多时间,但是代码经常是很笼统并且需要我们进行客户化来满足一个应用程序的特殊需求。扩充自动生成代码的风险在于,生成代码的这个工具会不定时地重生成代码并会覆盖掉你所客户化的代码。在.NET2.0的新的部分类思想中,在多个文件中分离出一个类来是很简单的。这就使我们能够向自动生成的类中添加我们自己的方法、属性和事件,而不必担心Visual Studio会覆盖我们的客户化了的代码。
为了表明如何客户化数据访问层,让我们向SuppliersRow类中添加一个GetProducts()方法。这个SuppliersRow类代表了Suppliers表中的一条记录。每个供货商(supplier)可以提供0到多哥商品(products),所以GetProducts()将会返回特定的供货商的商品。为了实现这个,在App_Code文件夹中新建一个名为SuppliersRos.cs的类文件并且添加以下的代码:
using System;
using System.Data;
using NorthwindTableAdapters;
public partial class Northwind
...{
public partial class SuppliersRow
...{
public Northwind.ProductsDataTable GetProducts()
...{
ProductsTableAdapter productsAdapter =
new ProductsTableAdapter();
return
productsAdapter.GetProductsBySupplierID(this.SupplierID);
}
}
} 这个类告诉编辑器当构建Norghwind.SuppliersRow类时,向其中添加我们刚刚定义的那个GetProducts()方法。如果你运行你的工程并返回到类视图的时候,你将会看到GetProducts() 是作为一个Northwind.SuppliersRow方法被列出来的。
图片34:GetProducts()方法现在已经成为Northwind.SuppliersRow类的一部分了
GetProducts()方法现在已经能够被用来列举某一特殊的供货商的商品了,就像下边的代码所表示的那样:
NorthwindTableAdapters.SuppliersTableAdapter suppliersAdapter = new NorthwindTableAdapters.SuppliersTableAdapter();
// Get all of the suppliers
Northwind.SuppliersDataTable suppliers =
suppliersAdapter.GetSuppliers();
// Enumerate the suppliers
foreach (Northwind.SuppliersRow supplier in suppliers)
...{
Response.Write("Supplier: " + supplier.CompanyName);
Response.Write("<ul>");
// List the products for this supplier
Northwind.ProductsDataTable products = supplier.GetProducts();
foreach (Northwind.ProductsRow product in products)
Response.Write("<li>" + product.ProductName + "</li>");
Response.Write("</ul><p> </p>");
} 这个数据同样可以在任何ASP.NET的WEB控件中被显示。下边的页使用了一个包含两个字段的GridView控件:
绑定列,该列显示了每个供伙商的名字。
模板列,该列包含一个BulletedList控件,该控件被绑定到为每个供货商通过GetProducts()方法返回的结果。
在以后的教程中,我们将会测验如何表示详细的报告。现在,这个例子被设计用来展现向Northwind.SuppliersRow类中添加客户化的方法。
SuppliersAndProducts.aspx
<%...@ Page Language="C#" AutoEventWireup="true" CodeFile="SuppliersAndProducts.aspx.cs" Inherits="SuppliersAndProducts" %>
<!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>Untitled Page</title>
<link href="Styles.css" rel="stylesheet" type="text/css" />
</head>
<body>
<form id="form1" runat="server">
<div>
<h1>
Suppliers and Their Products</h1>
<p>
<asp:GridView ID="GridView1" runat="server"
AutoGenerateColumns="False"
CssClass="DataWebControlStyle">
<HeaderStyle CssClass="HeaderStyle" />
<AlternatingRowStyle CssClass="AlternatingRowStyle" />
<Columns>
<asp:BoundField DataField="CompanyName"
HeaderText="Supplier" />
<asp:TemplateField HeaderText="Products">
<ItemTemplate>
<asp:BulletedList ID="BulletedList1"
runat="server" DataSource="<%#
((Northwind.SuppliersRow)((System.Data.DataRowView)
Container.DataItem).Row).GetProducts() %>"
DataTextField="ProductName">
</asp:BulletedList>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
</p>
</div>
</form>
</body>
</html> SuppliersAndProducts.aspx.cs
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using NorthwindTableAdapters;
public partial class SuppliersAndProducts : System.Web.UI.Page
...{
protected void Page_Load(object sender, EventArgs e)
...{
SuppliersTableAdapter suppliersAdapter = new
SuppliersTableAdapter();
GridView1.DataSource = suppliersAdapter.GetSuppliers();
GridView1.DataBind();
}
}
图片35:供货商公司的名字被列在左侧的列中,他们的商品在右侧
posted on 2007-03-19 15:20
曾科 阅读(1564)
评论(0) 编辑 收藏 所属分类:
ASP.NET