在ASP.NET 2.0中操作数据之五十七:在分层架构中缓存数据

导言:

  正如前面章节所言,缓存ObjectDataSource的数据只需要简单的设置一些属性。然而,它是在表现层对数据缓存,这就与ASP.NET page页面缓存策略(caching policies)紧密的耦合(tightly couples)起来。我们对体系机构分层的原因之一便是打破这种耦合。拿业务逻辑层为例,将业务逻辑从ASP.NET页面脱离出来;而数据访问层将数据访问的细节ASP.NET页面脱离出来。从某种意义来说,将业务逻辑和数据访问细节脱离出来是首先,这样的话使系统更易读、易维护、易修改,便于按模块分工—比如,表现层的开发者对数据库的细节不甚了解也不妨碍其开发工作。当然,将缓存策略从表现层脱离出来也有类似的好处。

  本文我们将对层次机构进行扩充,新添一个缓存层(Caching Layer,简称CL)以实施缓存策略。该缓存层包括一个ProductsCL类,该类用类似 GetProducts(), GetProductsByCategoryID(categoryID)等方法来访问产品信息。调用这些方法时先从内存检索数据,如果内存为空则调用业务逻辑层BLL里的ProductsBLL类的相应方法,再从数据访问层DAL返回获取的数据。该ProductsCL类的方法从业务逻辑层BLL获取数据后先对数据缓存后再返回。

如图1所示,缓存层CL位于表现层和业务逻辑层。


图1:在我们的体系结构中缓存层(CL)是单独的一层

第一步:创建缓存层的类

  在本文,我们创建的缓存层仅仅包含一个ProductsCL类,它只有几个方法。
  完整的缓存层还应该包含CategoriesCL, EmployeesCL, 和SuppliersCL类。有了业务逻辑层BLL和数据访问层DAL,缓存层完全可以当成一个单独的类库工程(Class Library project),不过我们将它作为App_Code文件夹里的一个类来处理。

  为了更好的将缓存层类和DAL类、BLL类区分开,我们在App_Code文件夹里创建一个新的子文件夹。在资源管理器里右击App_Code文件夹,选择“新文件夹”,命名为CL,在里面添加新类ProductsCL.cs


图2:添加名为CL的文件夹和名为ProductsCL.cs的类

  跟BLL里的ProductsBLL类一样,ProductsCL类应该包含相同的数据访问和修改方法。不过在本文,我们只创建GetProducts()方法(在第3步)和GetProductsByCategoryID(categoryID)方法(在第4步)。你可以在空闲的时候对ProductsCL类进行完善,并创建相应的CategoriesCL, EmployeesCL和 SuppliersCL类

第二步:对Data Cache进行读和写

  ObjectDataSource的缓存属性使用ASP.NET data cache来存储从BLL获取的数据。要访问data cache,可以从ASP.NET页面的code-behind classes类或体系结构层(architecture)的类来访问。要通过ASP.NET页面的code-behind classes类对data cache进行读写,可使用如下模式:

// Read from the cache(读)
object value = Cache["key"];
// Add a new item to the cache(写)
Cache["key"] = value;
Cache.Insert(key, value);
Cache.Insert(key, value, CacheDependency);
Cache.Insert(key, value, CacheDependency, DateTime, TimeSpan);

  Cache class类的Insert方法可以有很多的重载。Cache["key"] = value 和 Cache.Insert(key, value)是相同的,都是向cache添加一个条目(item),不过没有指定expiry(可以理解为缓存持续时间)。更具代表性的是,在我们向cache添加条目的时候指定一个expiry,它要么是dependency(从属体),要么是time-based expiry,又或者两者兼而有之,比如上面的最后2个表达式。

  如果所需的数据存储在内存的话,首先调用缓存层的方法返回数据。如果不在内存的话就调用BLL里相应的方法。数据先缓存再返回。就像下面的流程表解析的一样:


图3:如果数据存在于内存的话就调用缓存层的方法。

上图的流程可用如下的模式:

Type instance = Cache["key"] as Type;
if (instance == null)
{
 instance = BllMethodToGetInstance();
 Cache.Insert(key, instance, ...);
}
return instance;

  其中,Type是缓存在内存中的数据的类型——具体到本文,也就是Northwind.ProductsDataTable;此外,key用于唯一地标识缓存的每一个条目。如果指定了key值的那个条目不在内存中,那么instance就为null,然后用BLL类的某恰当的方法来检索数据,将获得的数据缓存到内存。将instance返回后,它将包含一个对数据的引用(reference to the data),数据要么来自内存,要么是BLL类的返回数据。

  当访问内存时,请务必使用上述模式。下面的这个模式,咋一看好像和上面的模式一模一样,但是有一个细微的区别,它存在一个race condition(可以理解为不易察觉的隐式缺陷)。race condition很难调试,因为它只是偶尔发生,而且再次发生的可能性也小。如下:

if (Cache["key"] == null)
{
 Cache.Insert(key, BllMethodToGetInstance(), ...);
}
return Cache["key"];

  再一个就是,上述模式不是在局部变量里存储缓存条目的引用,而是在条件语句里直接访问数据,在return语句里直接返回数据。设想这种情况,开始运行代码时Cache["key"]是non-null的,但在运行return语句前,系统将其从内存里清除掉,那么代码就会返回一个null值,而不是我们期望的某种类型的对象。

  注意:如果仅仅是对data cache进行读或写访问,你没有必要进行同步访问(synchronize thread access);当然,如果你需要对内存里的数据进行多重操作(multiple operations),你还是应该实施锁定(lock),或其它的机制。

如果要从data cache里清除某个条目,可以用Remove方法,比如:

Cache.Remove(key);

第三步:从ProductsCL类返回产品信息

  在本文,我们要在ProductsCL类里用2个方法来返回产品信息: GetProducts()和 GetProductsByCategoryID(categoryID). 和业务逻辑层里的ProductsBL类相似,缓存层里的GetProducts()方法返回一个Northwind.ProductsDataTable对象,来获取所有产品的信息;而GetProductsByCategoryID(categoryID)方法返回的是某个特定类别的所有产品。

如下的代码是ProductsCL类里的部分方法:

[System.ComponentModel.DataObject]
public class ProductsCL
{
 private ProductsBLL _productsAPI = null;
 protected ProductsBLL API
 {
 get
 {
 if (_productsAPI == null)
 _productsAPI = new ProductsBLL();

 return _productsAPI;
 }
 }

 [System.ComponentModel.DataObjectMethodAttribute(DataObjectMethodType.Select, true)]
 public Northwind.ProductsDataTable GetProducts()
 {
 const string rawKey = "Products";

 // See if the item is in the cache
 Northwind.ProductsDataTable products = _
 GetCacheItem(rawKey) as Northwind.ProductsDataTable;
 if (products == null)
 {
 // Item not found in cache - retrieve it and insert it into the cache
 products = API.GetProducts();
 AddCacheItem(rawKey, products);
 }

 return products;
 }

 [System.ComponentModel.DataObjectMethodAttribute(DataObjectMethodType.Select, false)]
 public Northwind.ProductsDataTable GetProductsByCategoryID(int categoryID)
 {
 if (categoryID < 0)
 return GetProducts();
 else
 {
 string rawKey = string.Concat("ProductsByCategory-", categoryID);

 // See if the item is in the cache
 Northwind.ProductsDataTable products = _
 GetCacheItem(rawKey) as Northwind.ProductsDataTable;
 if (products == null)
 {
 // Item not found in cache - retrieve it and insert it into the cache
 products = API.GetProductsByCategoryID(categoryID);
 AddCacheItem(rawKey, products);
 }

 return products;
 }
 }
}

  首先,注意运用到类(class)和方法(methods)上的属性 DataObject和 DataObjectMethodAttribute ;这些属性服务于ObjectDataSource的设置向导,指出那些类和方法应该出现在向导的设置步骤里。因为ObjectDataSource控件要在表现层访问这些类和方法,所以我添加了这些属性,方便向导设置。关于这些属性及其作用,请参阅本教程第2章《创建一个业务逻辑层》。

  在GetProducts() 和 GetProductsByCategoryID(categoryID)方法里,GetCacheItem(key)返回的数据赋值给一个局部变量。GetCacheItem(key)方法根据指定的key值在内存查找对应的缓存条目;如果没找到,则用ProductsBLL类里相应的方法来检索数据,并用AddCacheItem(key, value)方法将获取的数据缓存到内存。

GetCacheItem(key) 和 AddCacheItem(key, value)方法分别对data cache进行读、写操作。GetCacheItem(key)相对简单,它根据传入的key值,从Cache类返回数据,如下:

private object GetCacheItem(string rawKey)
{
 return HttpRuntime.Cache[GetCacheKey(rawKey)];
}

private readonly string[] MasterCacheKeyArray = {"ProductsCache"};
private string GetCacheKey(string cacheKey)
{
 return string.Concat(MasterCacheKeyArray[0], "-", cacheKey);
}

  GetCacheItem(key)并没有直接使用我们提供的key值,而是调用GetCacheKey(key)方法,因为该方法根据“ProductsCache-”返回key;在上述代码中,MasterCacheKeyArray用于存储字符串“ProductsCache”。当然,AddCacheItem(key, value)方法也会用到MasterCacheKeyArray,我们稍后会看到。

  在ASP.NET页面后台代码类(code-behind class),我们可以使用Page类的Cache属性来访问data cache ,就像我们在第2步里的表达式:Cache["key"] = value一样;而在体系结构的类(注:具体到本文,就是缓存层类(ProductsCL),我们可以通过2种方式来访问:HttpRuntime.Cache 或 HttpContext.Current.Cache ;在Peter Johnson的博客里有一篇文章《HttpRuntime.Cache vs. HttpContext.Current.Cache》(http://weblogs.asp.net/pjohnson/archive/2006/02/06/437559.aspx),探讨了HttpRuntim与相对于HttpContext.Current的优点;在此,我们的ProductsCL类将使用HttpRuntime.
注意:如果你是使用的类库工程(Class Library projects),一定要记得引用System.Web才能使用HttpRuntime 和 HttpContext类。

  如果没有在内存找到数据,ProductsCL类将从业务逻辑层BLL获取数据,并使用AddCacheItem(key, value)对数据进行缓存,可以用下面的代码向内存添加缓存数据,其缓存时间为60秒:

const double CacheDuration = 60.0;

private void AddCacheItem(string rawKey, object value)
{
 HttpRuntime.Cache.Insert(GetCacheKey(rawKey), value, null,
 DateTime.Now.AddSeconds(CacheDuration), Caching.Cache.NoSlidingExpiration);
}

  其中,DateTime.Now.AddSeconds(CacheDuration)指定了缓存时间—60秒;而 System.Web.Caching.Cache.NoSlidingExpiration指明了不存在可变缓存时间(no sliding expiration).虽然Insert()方法可以包含绝对时间和可变时间(absolute and sliding expiry)2种定义缓存时间的输入参数,但是你只能指定其中一个,如果你同时指定绝对时间和可变时间2个参数的话,Insert()方法会抛出一ArgumentException 异常。
注意:直接执行AddCacheItem(key, value)方法会有一些弊端,我们将在第4步解释并修正。

第4步:当数据被修改时使缓存失效

  除了数据检索方法外,缓存层还应该包含插入、更新、删除数据的方法。缓存层的数据修改方法并不是修改缓存的数据,而是调用业务逻辑层的相应方法,然后使缓存数据失效。就像前面章节探讨的那样,当激活ObjectDataSource的缓存属性时,便可调用它的Insert, Update或Delete方法。

下面的UpdateProduct方法,说明了如何在缓存层CL执行数据修改方法:

[System.ComponentModel.DataObjectMethodAttribute(DataObjectMethodType.Update, false)]
public bool UpdateProduct(string productName, decimal? unitPrice, int productID)
{
 bool result = API.UpdateProduct(productName, unitPrice, productID);

 // TODO: Invalidate the cache

 return result;
}

  在业务逻辑层的方法返回数据以前,我们需要将缓存的数据失效。不过,这并非易事,无论ProductsCL class's GetProducts()还是GetProductsByCategoryID(categoryID)都会向内存添加条目,并且GetProductsByCategoryID(categoryID)方法会为每种类别添加几个条目(因为每种类别有几种甚至更多的产品)。

  要使缓存数据失效,我们需要将ProductsCL类添加的所有条目删除。为此,在AddCacheItem(key, value)方法里,当添加条目时为其指定一个缓存从属体(cache dependency)。一般来说,缓存从属体可以是内存里的另一个条目;文件系统里的一个文件;又或者是Microsoft SQLServer database数据库里的数据。当从属体发生改变,或者从内存里移除时,其对应的缓存条目会自动的从内存删除。在本教程,当ProductsCL类向内存添加条目时,我们创建一个额外的条目作为其从属体。由此,要删除缓存条目,仅仅移除这些从属体即可。

  我们来更改AddCacheItem(key, value)方法,当用该方法向内存添加缓存数据时,使每个条目与一个从属体(cache dependency)对应起来。

private void AddCacheItem(string rawKey, object value)
{
 System.Web.Caching.Cache DataCache = HttpRuntime.Cache;

 // Make sure MasterCacheKeyArray[0] is in the cache - if not, add it
 if (DataCache[MasterCacheKeyArray[0]] == null)
 DataCache[MasterCacheKeyArray[0]] = DateTime.Now;

 // Add a CacheDependency
 System.Web.Caching.CacheDependency dependency =
 new CacheDependency(null, MasterCacheKeyArray);
 DataCache.Insert(GetCacheKey(rawKey), value, dependency,
 DateTime.Now.AddSeconds(CacheDuration),
 System.Web.Caching.Cache.NoSlidingExpiration);
}

  MasterCacheKeyArray是一个字符串数组,用来存储“ProductsCache”. 首先检查MasterCacheKeyArray,如果其为null,用当前date和time对其赋值。然后,创建一个从属体。CacheDependency类的构造器(constructor)可以有很多重载(overloads),本文使用的重载接受2个字符串数组作为输入参数。第一个参数指定文件作为从属体,但本文我们不大算用文件来做从属体,所以我们将第一个输入参数设为null;第二个参数指定cache keys作为从属体,本文我们指定为MasterCacheKeyArray。然后将该CacheDependency传递给Insert方法。

对AddCacheItem(key, value)方法做了上述修改后,要使缓存失效,很简单,将从属体移除即可:

[System.ComponentModel.DataObjectMethodAttribute(DataObjectMethodType.Update, false)]
public bool UpdateProduct(string productName, decimal? unitPrice, int productID)
{
 bool result = API.UpdateProduct(productName, unitPrice, productID);

 // Invalidate the cache
 InvalidateCache();

 return result;
}

public void InvalidateCache()
{
 // Remove the cache dependency
 HttpRuntime.Cache.Remove(MasterCacheKeyArray[0]);
}

第五步:在表现层调用缓存层

  保存对ProductsCL类的修改,打开Caching文件夹里的FromTheArchitecture.aspx页面,并添加一个GridView控件。从GridView控件的智能标签里创建一个新的ObjectDataSource,在向导的第一步,从下拉列表里选择ProductsCL,如下图:


图4:类ProductsCL包含在下拉列表里

  选定ProductsCL类后,点Next。我们可以看到在SELECT标签里有2个选项:GetProducts() 和 GetProductsByCategoryID(categoryID)方法;而在UPDATE标签里只有唯一的一个UpdateProduct()方法。在SELECT标签里选择GetProducts()方法;而在UPDATE标签里选择那个唯一的UpdateProduct()方法,最后点Finish。


图5:ProductsCL类的方法包含在下拉列表里。

  完成向导后,Visual Studio会将ObjectDataSource的OldValuesParameterFormatString属性设置为original_{0},并向GridView添加相应的列。将OldValuesParameterFormatString该为默认值{0}, 并启用GridView控件的分页、排序、编辑功能。由于缓存层CL的UploadProducts()方法只对产品的name 和 price进行编辑,由此需要对GridView做相应的修改以限制其只能编辑这2列。

  在前面的教程,我们指定GridView控件包含 ProductName, CategoryName,和UnitPrice3列。放心大胆的将其复制过来,这样,GridView 和 ObjectDataSource的声明代码看起来应该像下面的这样:

<asp:GridView ID="Products" runat="server" AutoGenerateColumns="False"
 DataKeyNames="ProductID" DataSourceID="ProductsDataSource"
 AllowPaging="True" AllowSorting="True">
 <Columns>
 <asp:CommandField ShowEditButton="True" />
 <asp:TemplateField HeaderText="Product" SortExpression="ProductName">
 <EditItemTemplate>
 <asp:TextBox ID="ProductName" runat="server"
  Text='<%# Bind("ProductName") %>' />
 <asp:RequiredFieldValidator ID="RequiredFieldValidator1"
  ControlToValidate="ProductName" Display="Dynamic"
  ErrorMessage="You must provide a name for the product."
  SetFocusOnError="True"
  runat="server">*</asp:RequiredFieldValidator>
 </EditItemTemplate>
 <ItemTemplate>
 <asp:Label ID="Label2" runat="server"
  Text='<%# Bind("ProductName") %>'></asp:Label>
 </ItemTemplate>
 </asp:TemplateField>
 <asp:BoundField DataField="CategoryName" HeaderText="Category"
 ReadOnly="True" SortExpression="CategoryName" />
 <asp:TemplateField HeaderText="Price" SortExpression="UnitPrice">
 <EditItemTemplate>
 $<asp:TextBox ID="UnitPrice" runat="server" Columns="8"
  Text='<%# Bind("UnitPrice", "{0:N2}") %>'></asp:TextBox>
 <asp:CompareValidator ID="CompareValidator1" runat="server"
  ControlToValidate="UnitPrice" Display="Dynamic"
  ErrorMessage="You must enter a valid currency value with
  no currency symbols. Also, the value must be greater than
  or equal to zero."
  Operator="GreaterThanEqual" SetFocusOnError="True"
  Type="Currency" ValueToCompare="0">*</asp:CompareValidator>
 </EditItemTemplate>
 <ItemStyle HorizontalAlign="Right" />
 <ItemTemplate>
 <asp:Label ID="Label1" runat="server"
  Text='<%# Bind("UnitPrice", "{0:c}") %>' />
 </ItemTemplate>
 </asp:TemplateField>
 </Columns>
</asp:GridView>

<asp:ObjectDataSource ID="ProductsDataSource" runat="server"
 OldValuesParameterFormatString="{0}" SelectMethod="GetProducts"
 TypeName="ProductsCL" UpdateMethod="UpdateProduct">
 <UpdateParameters>
 <asp:Parameter Name="productName" Type="String" />
 <asp:Parameter Name="unitPrice" Type="Decimal" />
 <asp:Parameter Name="productID" Type="Int32" />
 </UpdateParameters>
</asp:ObjectDataSource>

  这样,我们该页面就使用了缓存层。为实地演示缓存,在ProductsCL类的GetProducts() 和 UpdateProduct()方法里设置断点(breakpoints),在浏览器里访问该页面,当排序或分页时就会执行这些代码,从内存获取数据。然后更新一条记录,注意由于缓存失效,将从业务逻辑层BLL获取数据并绑定到GridView。
注意:从本文download链接下载的缓存层并不完善。它只包含了一个ProductsCL类,它只包含几个方法。此外,只有一个ASP.NET页面(~/Caching/FromTheArchitecture.aspx)使用了缓存层CL,而其它的页面都是直接调用业务逻辑层BLL。如果打算在你的应用程序里使用缓存层CL,那么页面层的所有调用都应该先访问缓存层CL。

总结:

  虽然可以在ASP.NET 2.0的表现层对SqlDataSource 和 ObjectDataSource控件实施缓存,但更理想的做法是在体系单独分层来达到缓存的目的。在本文,我们在表现层和业务逻辑层之间创建了一个缓存层,该缓存层包含的类和方法与现有的业务逻辑层所包含的类和方法类似。当然,也是在表现层调用。

  本示例及前面教程处理的是“触发装载”(reactive loading)—也就是说当发现请求的数据没在内存后将数据装载进内存。其实数据也可以“预装载”(proactively loaded)进内存—也就是说在数据实际请求之前将其预先装载进内存。在下一篇文章我们将看到预装载的情形——在应用程序启动的时候如何将静态值(static values)装载进内存。

  祝编程快乐!

作者简介

  本系列教程作者 Scott Mitchell,著有六本ASP/ASP.NET方面的书,是4GuysFromRolla.com的创始人,自1998年以来一直应用 微软Web技术。大家可以点击查看全部教程《[翻译]Scott Mitchell 的ASP.NET 2.0数据教程》,希望对大家的学习ASP.NET有所帮助。

(0)

相关推荐

  • 在ASP.NET 2.0中操作数据之六十:创建一个自定义的Database-Driven Site Map Provider

    导言: ASP.NET 2.0的网站地图(site map)功能允许页面开发者在一些持久介质(persistent medium),比如一个XML文件里,自己定义一个web程序的site map.一旦定义了之后,我们可以通过System.Web命名空间的SiteMap class类或某个Web导航控件,比如SiteMapPath, Menu, 或TreeView来对其进行访问.site map系统使用的是provider model模式,所以可以创建不同的site map,并将其应用到一个web

  • 在ASP.NET 2.0中操作数据之五十八:在程序启动阶段缓存数据

    导言: 前面2章考察了在表现层和缓存层缓存数据.在第56章,我们探讨了在表现层设置ObjectDataSource的相关cache属性来缓存数据.在第57章,我们探讨了创建一个单独的分开的缓存层.这2章都是采用"应激装载"(reactive loading)的模式来缓存数据.该模式下,每次请求数据时,系统先检查其是否在内存,如果没有,则从数据源--比如数据库,来获取数据,然后将其存储在内存里.该模式的优势在于执行起来很容易:而缺点之一在于应"请求"(requests

  • 在ASP.NET 2.0中操作数据之六十六:在TableAdapters中使用现有的存储过程

    导言: 在前面的文章里我们考察了如何让TableAdapters向导自动的创建存储过程.而在本文,我们将考察如何让TableAdapter使用现有的存储过程.由于Northwind数据库现有的存储过程很少,我们也需要考察如何在Visual Studio环境里手动向数据库添加新的存储过程. 注意:在第61章<在事务里对数据库修改进行封装>里我们向TableAdapter添加了一些方法以支持事务(比如 (BeginTransaction, CommitTransaction等).我们可以在不修改数

  • 在ASP.NET 2.0中操作数据之五十九:使用SQL缓存依赖项SqlCacheDependency

    导言: 在56和57章探讨的缓存技术使用的是基于时间的缓存周期,当过了某段时间后便将缓存数据从内存清除.当设置缓存时间为x秒时,数据在x秒内都是"新"的.当然,就像在60章谈到的那样,对静态数据来说,x可延伸到web应用程序的整个生命周期(lifetime). 当缓存数据时,基于时间周期的技术因为其易用性而常常被采用,不过又常常不那么完美.理想的状态是这样的:数据库数据还是应缓存在内存,直到源数据(underlying data)发生改变时才从内存清除.这样的话可以最大化的获取缓存带来

  • 在ASP.NET 2.0中操作数据之六十二:GridView批量更新数据

    导言: 在前面的教程,我们对数据访问层进行扩展以支持数据库事务.数据库事务确保一系列的操作要么都成功,要么都失败.本文我们将注意力转到创建一个批更新数据界面. 在本文,我们将创建一个GridView控件,里面的每一行记录都可以进行编辑(见图1),因此我们没有必要多添加一列来包含Edit, Update,和Cancel按钮,而是在页面包含2个"Update Products"按钮,被点击时,遍历所有的产品并对数据库进行更新.让我们开始吧. 图1:GridView控件里的每一行记录都可以编

  • 在ASP.NET 2.0中操作数据之六十五:在TableAdapters中创建新的存储过程

    导言: 本教程的Data Access Layer (DAL)使用的是类型化的数据集(Typed DataSets).就像我们在第一章<创建一个数据访问层>里探讨的一样,该类型化的数据集由强类型的DataTable和TableAdapter构成.DataTable描绘的是系统里的逻辑实体而TableAdapter引用相关数据库执行数据访问,包括对DataTable填充数据.执行返回标量数据(scalar data)的请求.添加,更新,删除数据库里的记录等. TableAdapter执行的SQL

  • 在ASP.NET 2.0中操作数据之六十三:GridView实现批量删除数据

    导言: 在前面的教程,我们用GridView创建了一个批编辑界面.在用户需要一次性编辑多条记录的情况下,批编辑界面很有用.同理,当用户需要同时删除多条记录时,该技术也很有用. 如果你使用过邮件系统的话,你应该对这种最常见的批删除界面很熟悉:界面里每一行都包含一个checkbox,此外,还有一个"Delete All Checked Items"按钮(如图1).本教程比较短,因为我们在前面的教程已经完成大体的框架,在前面的第50章<为GridView控件添加Checkbox>

  • 在ASP.NET 2.0中操作数据之六十四:GridView批量添加数据

    导言: 在前面的第62章<GridView批量更新数据>里,我们用GridView控件里定制了一个批编辑界面,同样的我们也可以定制一个批添加界面.假设有这种情况,我们接受一批从Tokyo(东京)发过来的货物:6种不同的tea 和 coffee,如果用户在一个DetailsView控件里一次输入一个产品,他将会重复的输入很多相同的值,比如相同的种类(Beverages),相同的供应商(Tokyo Traders),相同的discontinued值(False),以及相同的order值(0).重复

  • 在ASP.NET 2.0中操作数据之六十一:在事务里对数据库修改进行封装

    导言: 正如我们在第16章<概述插入.更新和删除数据>里探讨的那样,GridView控件内建的功能支持对每行数据的编辑和删除功能,你只需要稍稍动一下鼠标就可以创建丰富的数据修改界面而不用写一行代码.但是,在某些情况下,这还不够,我们需要让用户能够成批地处理数据. 比如,很多基于web(web-based)的电子邮件客户端,将所有邮件出来,每条邮件除了包含邮件信息(主题.发送者等)外,还包含一个checkbox控件.这些界面允许用户同时删除多个邮件,用户只需要选中邮件,再点"删除所选邮

  • 在ASP.NET 2.0中操作数据之六十七:在TableAdapters中使用JOINs

    导言: 在关系数据库里,我们处理的数据通常跨越了几个数据表.举例:当展示产品信息时我们很可能想列出每个产品相应的category以及供应商的名称等.诚然,Products表里包含有CategoryID 和SupplierID值,但是事实上的category以及supplier names分别定义在Categories表和Suppliers表里. 要从其它的相关表里获取信息,我们可以使用correlated subqueries或JOINs.一条correlated subquerie就是一个镶套

随机推荐