前几天刚学了一点LINQ To Sql,看的是ScottGu's Blog 上的Posts,觉得很是不错,有一种编程模式要发生一次大革命的感觉(呵呵,不知道这种感觉对不对),由于最近项目一直项目比较忙,所以学习的机会很少,今天周六,有了一点时间,可是自己并没有什么新的感受,所以就想试着翻译一下我读的这几篇posts,跟大家分享。有兴趣的朋友可以直接到下面链接上去看e文:
http://weblogs.asp.net/scottgu/archive/2007/07/16/linq-to-sql-part-5-binding-ui-using-the-asp-linqdatasource-control.aspx
我开始吧:
在上几周我写了一系列关于LINQ To SQL的博文。LINQ To SQL 是一个内置的O/RM(object relational mapper),它存在于.NET Framework3.5的release 版中,而且Linq To Sql让你在.NET 类中设计关系型数据库非常方便。可以用LINQ的表达式来查询数据库,当然,更新/插入/删除数据也是没问题的。
以下是我的LINQ To Sql 系统的前四部分的链接:
Part 1: Introduction to LINQ to SQL
Part 2: Defining our Data Model Classes
在前几篇博文中,我重点讲的是怎么在编程时用LINQ to SQL来对数据库进行方便地查询和更新数据。
今天的博文我将涉及到的是在.NET 3.5 release版中的作为asp.net的一部分的新增的<asp:LinqDataSource>控件。它是一个为ASP.NET 新增的数据源控件(像在ASP.NET 2.0中新增的ObjectDataSource和SQLDataSource一样),通过该控件你可以方便地将ASP.NET UI控件绑定到Linq To Sql 数据模型上。
我们要建立的事例程序:
在这篇教案(或者叫博文我认为也可以_韩现龙)中我用到的这个简单地能进行数据编辑的Web应用程序是为数据库是的products进行前-后的数据录入和操作。
这个应用程序支持如下的终端用户特点:
1.允许用户通过类别对产品进行筛选
2.允许用户通过点击列头(Name,Price,Units In Stock等)对产品进行排序
3.允许用户分布,在不同的页面之间进行跳转(每页显示10条产品记录)
4.允许用户在页面上对在产品列表中列出来的产品详细信息进行编辑。
5.允许用户从产品列表中删除产品记录。
这个Web应用程序用一个用LINQ To SQL ORM建立的面向对象的数据模型。(原文说clean object-oriented data model,这个clean该做何讲?)
所有的业务规则和业务验证逻辑将会在我们在数据模型是验证,而不是在UI或者是在任何的UI页面上。这将能保证:1)一个可以在整个应用程序中应用的一致的规则2), 我们可以减少代码的书写,不重复地去写那些代码 3),以后我们可以便捷地修改或配置我们的业务逻辑,而不需要在我们整个应用程序中去修改数个地方。
我们也利用了LINQ To SQL的内置的分页/排序功能来保证产品列表的分页和排序不在中间层进行,而直接在数据库中查出我们需要的记录(也就是在给定的时间内只从数据库中查出10条记录--不会去查出上千条记录之后再在Web服务器上去完成分页/排序的功能)
什么是<asp:LinqDataSource>控件,它是怎么帮我们工作的?
<asp:LinqDataSource>控件是一个实现了在ASP.NET 2.0中介绍的DataSourceControl模式。它和ObjectDataSource和SqlDataSource控件很类似,可以显示将页面上的其他的ASP.NET 控件绑定到一个数据源。不同的是它不是直接绑定到数据库(像SQL DataSource)或者到一个类(像ObjectDataSource),它绑定的一个使用了LINQ的数据模型。
使用<asp:LinqdataSource>的一个好处就是增加了基于ORMs支持的LINQ的灵活性。你可以不去定义自己的让数据源来调用的query/insert/update/delete方法,取而代之的是你将该控件指向你的数据模型,指明你想操作的哪张表,然后将任何的ASP.NET UI控件来绑定到它,然后让它们和<asp:LinqDataSource>一起进行工作。
例如:为了从一个存在于LINQ to SQL 数据模型中的产品实体中找出一条基本的产品 数据来显示在页面上,我可以很简单地在页面上放置一个<asp:linqdatasource>,将它指定到我的LINQ To Sql datacontext类,然后指明我想绑定的实体(例如产品实体)。然后我可以将一个GridView指向它(设置一下GridView的DataSourceID)来获取一个产品的信息列表:
不用做其他的任何事情,我就可以运行此页,并在页面上显示一个支持分页和排序的数据列表。我也可以向GridView中增加修改/删除按钮,让它支持自动更新。我不需要添加任何的方法、视图、参数,或者为<asp:LinqDataSource>书写任何代码来支持这些查询和更新的事情,它可以自动地操作我们指定的Linq To SQL 数据模型。当进行更新时,LINQ To SQL ORM将在对数据库进行任何的操作之前,自动来验证我们写到LinqToSql数据模型中的业务逻辑的规则和验证逻辑是否正确。
重要:LINQ 和LINQ To SQL的美是它明显地不是仅仅能用来绑定UI控件,或者特定的像LinqDataSource这样的绑定控件。在我这个系列的博客中你已经了解,写LINQ to SQL ORM写代码是非常的简洁。如果你乐意,你可以直接写代码来控制你的UI,让UI上的控件来对你的Linq To Sql数据模型进行操作,或者当你发现某个UI场景不适合用<asp:linqdatasource>使用时,你也可以直接书写代码。
下面的部分讲述了用<asp:LinqDataSource>来构建我上述定义的一个Web应用程序。
步骤1:定义数据模型
我们首先要定义用来描述我们的数据库的数据模型。 在该系列的Part 2 中我讲解了如何在VS2008的Linq To SQl设计器中生成一个LINQ to SQL数据模型。下面是一个我可以用LINQ to SQL设计器可以很快设计出来的一个"Northwind"数据库的模型:
我们会在第5步的时候再来看这个数据模型,届时我们将会向该模型中添加一些业务验证规则。开始时我们只是用它来建立我们的UI。
步骤 2: 建立基础的产品列表
我们用<asp:gridview>控件来开始整个页面的设计,向girdview中添加一些样式:
我们可以通过编码的方式来绑定我们原来定义的数据模型(像我在该系列的Part 3中做的那样),或者使用<asp:linqdatasource> 这个新增控件来将我们的数据模型绑定到gridview中。
VS2008内置了一个使得将LinqData绑定到gridview(或其他的asp.net 服务器控件)更加方便的设计器。我们可以切换到"设计"视图,选定gridview,然后选择"New Data Source"选择,然后选择其中的Choose Data Source项,通过以上几步,将LinqDataSource绑定到gridview上。
点击了"<New data source>后会弹出一个对话框,该对话框列表了所有你可以生成的可用的数据源选项。选择"Linq"选项来生成<asp:Linqdatasource>控件,并且对它进行命名:
<asp:linqdatasource>设计器将将会列出应用程序中可用的 LINQ to SQL DataContext 类 (包括你引用的那些类字典):
我们选择刚才用LINQ to SQL设计器生成的数据模型。 然后选择我们存在于数据模型中的某数据表来<asp:linqdatasource>中绑定使用的的表。此处我们选择我们建立的"Product"实体。然后点击"Advanced"按钮来为数据源启用更新和删除::
当点击"Finish"按钮时,VS2008将会在我们的.aspx页面上生成一个<asp:linqdatasource>, 并且将gridview指向它(通过gridview的DataSourceID属性),它也根据我们选择绑定到的Product实体来在gridview上自动生成列。
然后在gridview中将"smart task"菜单展开,指定启用分页、启用排序、启用删除:
然后按下F5键来运行该程序,页面上就会显示出一个支持分页和排序的产品列表(每页的索引在grid的下边):
我们也可以点击在每行上的"edi"或"delete"按钮来更新数据:
如果我们切换到页面上的源视图,我们就会看到页面上显示的如下图的数据内容。<asp:linqdatasource>控件指向刚才创建的LINQ to SQL DataContext,同时也指向了我们想绑定到的表 GridView通过DataSourceID这个属性指向了<asp:linqdatasource>,并且设置了哪一列应该显示在表格中,每个列的列头应该是什么,在单击列头时的排序表达式。
现在我们已经有了基于LINQ to SQL数据模型的基础页面, 接着往下走,更深一步地设置UI和行为:
步骤 3: 删除某些我们不用的列
上面的gridview已经定义了许多的列, 而且其中两个列值 (SupplierID 和CategoryID) 是外键—如果将它们显示给用户当然会是一个非常理想的途径。
移除不需要的列
从删除一些我们不需要的列开始整理我们的UI. 我可以在源模式中删除,(只需要删除某些<asp:boundfield>声明即可) 或者在设计模式 (在设计视图中单击列选择"Remove"即可。). 比如,我们可以移除"QuantityPerUnit" 列,然后重新运行程序,就得到了一些清洁一些的UI:
如果你用过<asp:ObjectDataSource> 控件,而且声明了更新的参数和更新的方法 (是使用基于TableAdapters的DataSet是默认的),其中一点比较痛苦的是在你页面上的参数被更改时,你必须更改你的TableAdapter的updatemethods的方法签名,例如::如果我们在gridview中删除了某一列(就像上边那样),我们就必须修改TableAdapter来让它支持除去那个参数之后的Update Methods.
<asp:LinqDataSource> 的其中一个真正比较好的东西是你不需要做一些如上所述的更改。在界面上简单地删除或添加某列,然后重新运行――不需要做任何的其他改变。这让使用<asp:LinqDataSource> 来创建的界面修改起来更加简单,使得应用程序中更改变得更加快速高效。
清除SupplierID 和CategoryID 列
当前在GridView中每个产品记录上都显示了两个外键的integer值:
从数据模型的观点来看,它对用户并不是十分友好。我真正想显示给用户的是CategoryName和 SupplierName,而并非两个数字,并且当用户选择"编辑"按钮时,显示给用户一个和SupplierID和CategoryID相关联的dropdownlist。
我可以按照如下方式更改:将GridView中的默认的<asp:BoundField>替换为 <asp:TemplateField>. 在TemplateField我可以添加自定义的一些内容。
在如下的源码中我想用到的一个优点是,在每个LINQ To SQL数据模型中的Product类中,每条记录都有Supplier 和Category的属性.这就是说,我可以方便地将它们在gridview中绑定到Supplier.CompanyName 和 Category.CategoryName 的子属性上:
现在运行程序,得到了人性化的Category和Supplier:
为了向页面中加上在GridView的编辑模式下可用的DropDownList, 首先需要在页面上加两个<asp:LinqDataSource> 控件. 将它们分别配置为绑定到刚才创建的LINQ to SQL 数据模型的Categories 和 Suppliers上:
然后回到添加到GridView上的<asp:TemplateField>,然后自定义它们在编辑模式下的显示方式(通过设置EditItemTemplate)。我们定义每个列在编辑模式下显示一个dropdownlist控件, 在DropDownList中的可用的值是从刚才的categories和suppliers数据源控件中取来的,用两个各方式将selected value 绑定到Product's SupplierID 和 CategoryID foreign keys:
现在,当用户点击"Edi"按钮时,它就会一个关联到产品的合法的Suppliers下拉列表框,如下图:
当点击"Save",该产品记录将会被更新。(GridView 会用当前DropDownList's 的当前的 selectedvalue来绑定SupplierID).
步骤 4: 根据某些条件查询产品列表
不仅仅是显示所有在数据库中的产品列表,我们还可以将界面修改为一个可以通过产品类型的下拉列表来查询产品的界面。
因为刚才我们已经添加了和我们的LINQ TO SQL数据模型相关联的的<asp:LinqDataSource> 控件,所以我此处需要做的只是在页面首部添加一个绑定到这个控件的drop-downlist 控件。比如:
运行此页面,现在在页面上方有一个可以过滤各个类别的产品的dropdownlist :
最后一步是将GridView设置为只显示用户从DropDownList中选择的类别的产品。最简便的方法是在GridView上通过选择"Configure DataSource"来设置此功能:
这就要回到在本节开始时我们提到的<asp:LinqDataSource>控件设计阶段了。我可以选择在其中"Where"按钮来添加一个绑定的过滤条件到这个数据源控件。我可以添加任意个过滤表达式,并且显示的从多处将值和过滤条件结合起来(比如: 从查询语句, 从值, 从页面上的其他控件等):
上边我想通过它们的CategoryID 的值来选择产品,该CategoryID从刚才在页面上创建的DropDownList 控件中检索该而得:
点击"finish"按钮,在页面上的 <asp:linqdatasource> 控件将会被更新为含有你下边这个的过滤条件:
现在,运行这个页面,用户就可以选择在DropDownList的里的那一类型的产品,并对其进行分页、排序、编辑、删除的操作。
<asp:LinqDataSource> 控件将自动从数据库中检索出基于LINQ to SQL 数据模型的过滤条件 ,并会自动进行查询优化(比如:在上边Grid中,只有第2页的3条记录从数据库中检索了出来)。
如果你想写自定义的查询条件的话,你可以处理<asp:LinqDataSource> 的Selecting事件。
步骤 5:添加业务验证规则
在该系列的 Part 4中我已经讲了,当 定义LINQ to SQL 数据模型时,将会有自动地有一个基于验证关系添加到我们的的数据模型中。这意味着,若试图将一个null值插入到一个不允许为null的列中,试图将一个string变量插入到一个interger类型的列,或者指定一个外键不存在的记录,LINQ to SQL数据模型将会抛出一个错误,保证我我们的数据实体被维护。
然后,基本的模式验证仅仅是第一步,并且在许多真实的程序中是远远不够的。一般来讲,我们想,而且应该向数据模型类中添加上额外的业务验证规则和应用程序级别的验证。感谢LINQ to SQL 将添加这些业务规则做得很方便 (更详细的各种各样的验证规则请阅读本系统列的Part 4).
一个业务验证规则的例子:
例如,让我们假定一个我们规定的基本的业务逻辑规则。具体来说, 我们保证当用户订购的产品数量大于我们库存的数量时,应该无法继续进行订货:
如果用户保存了上边的行,我们想阻止此类的更改,并且抛出一个恰当的错误来告诉用户该怎么解决它。
添加数据模型验证规则
在我们应用程序的UI层来添加这类验证规则是错误的!添加到UI层的就意思着这个规则只在该处适用,当在我们的应用程序中添加其他的也需要更新产品的页时它不会自动被触发。 UI层的分布式的业务规则/逻辑将会将随着程序的不断发展,在整个应用程序的各个代码中到处来修改这个规则将是一个非常痛苦的事情。
正确的添加数据模型验证规则的地方是在我们刚才已定义的LINQ to DATA 数据模型中。在本系统列的Part 4中我已经谈到,所有的用LINQ to SQL模型设计器生成的类都被定义为了"partial"――也就是说我们可以方便地向其中添加附加的方法/事件/属性。LINQ to SQL 数据模型类会自动地调用我们添加的增加增强了验证逻辑的验证方法。
例如,我可以向项目中添加一个局部类(partial Product),该类中声明LINQ to SQL的方法在更新Product实体之后先调用的OnValidate()的局部方法。在OnValidate()方法中,我可以添加如下的业务逻辑来增强以上提到的验证规则:
一旦我在LINQ to SQL工程中添加了以上的那个类,上边的业务逻辑验证规则将会在任何使用该数据模型的人试图更新数据库时触发。这对于更新已有的产品和添加新产品都是同样地会生效。
因为刚才我们在页面上定义的<asp:LinqDataSource> 控件是据我们的这个LINQ to SQL数据模型类的,它的所有update/insert/delete逻辑现在都会先经过以上的验证。我们不必在UI层做任何事情来保证这个验证来触发――它会在使用此数据模型的的任何及任意的地方被触发。
在UI层添加优秀的错误处理
默认地,现在用户用我们现在的这个GridView来输入一个非法的UnitsOnOrder/Discontinued combination,我们的LINQ to SQL 数据模型将会抛出一个异常。<asp:LinqDataSource> 将会捕获此异常,并且提供一个用户可以处理此异常的事件。如果在Global.asax文件中没有Application_Error()事件的话,那么绑定到<asp:LinqDataSource>GridView(或其他的控件)将会将错误传送到页面上,交给页面来处理,开发者可以选择在任何的地方来恰当地处理这个错误,从而提供给用户一个正确地用户体验。
对于我们刚才定义的应用程序而言,可能最好的处理处理异常的地方就是在GridView的 RowUpdated事件上。 这个事件将会在我们试图更新数据源的时候被触发,并且在更新事件失败时我们可以接收到此异常的信息。我们可以添加如下的代码来判断是否抛出了异常,是否给终端用户显示错误信息:
注意上边我们没有将验证具体逻辑加到UI上,取而代之的是,我检索在业务逻辑验证时设置的验证失败的错误信息字符串,并用该字符串来显示给终端用户一个恰当的信息(我将显示更多各类失败时的错误信息)。
注意我是如何做到当抛出错误信息时我是怎么让GridView保持在编辑状态的――那样的话用户就可以避免丢失他们的数据,而且可以修改他们录入的值再次点击"Update"按钮来保存数据。我们可以在页面的在显示错误信息的任何地方添加一个ID为ErrorMessage <asp:literal> 控件:
此时,当用非法的数据更新数据时,我们就会看到这条指明了我们该如何处理的错误信息:
现在的好处是,我现在可以在不修改任何UI层的代码的情况下来添加或者修改我的数据模型的业务规则,验证规则和相应的错误信息,可以在我的数据模型中写在一个地方,并且在异常抛出时自动地被触发。
总结
<asp:LinqDataSource>控件提供了一种将ASP.NET UI 控件绑定到LINQ to SQL 数据模型的简单方法。它允许UI控件从一个LINQ to SQL 数据模型中检索数据,也允许高效地更新/插入/删除。
在以上的应用程序中,我们用LINQ to SQL ORM 设计器设计出了一个干净的,面向对象的数据模型。 然后我们向页面中添加了三个ASP.NET UI 控 (一个 GridView, 一个DropDownList, 一个 ErrorMessage Literal), 还添加了三个<asp:LinqDataSource> 控件来绑定数据模型中的 Product, Category, and Supplier
然后我们写了5行业务验证逻辑代码,11行UI错误处理逻辑。
这个简单的处理程序的结构实现了用户可以通过所属类别来检索数据,对产品列表高效排序和分类,在GridView内部来进行编辑/更新产品(还提供了我们的业务验证规则),从系统中删除产品记录(也提供了我们的业务验证规则)。
在下篇博文中,我将讲LINQ to SQL 对于并发冲突,lazy and eager加载,table mapping 的继承和自定义的sql server的存储过程的使用。
下周我还想开始一个新的博客系列:<asp:ListView>控件,它也是在.NET 3.5中新增的一个控件。它提供的是对数据的全部控制(没有tables,没有spans,没有inline styles..),而且还支持分布,排序,修改和插入。 比如,我们选择用它来的自定义的显示模式来替换我们的Grid的默认的显示格式。最好的是,我可以无需修改我的数据模型而在页面中将grid来替换。
Hope this helps
Scott
到这儿就翻译完了,不管怎么样,它毕竟是我花了自己的时间来翻译的,希望它能对您有所帮助。不过我还是希望e文好的朋友直接看原文。
嘿嘿,马马虎虎。