LINQ数据映射.doc_第1页
LINQ数据映射.doc_第2页
LINQ数据映射.doc_第3页
LINQ数据映射.doc_第4页
LINQ数据映射.doc_第5页
已阅读5页,还剩18页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第八章 LINQ进阶本章导读:在本书的第四章中,我们学习了LINQ技术的基础知识,并通过第六章网上商店系统中对LINQ的使用巩固了对LINQ的认识,在本章中,我们将继续探讨LINQ的进阶知识。在LINQ To SQL中我们曾用到的DataContext类型是连接数据实体和数据库的桥梁,在本章中首先对DataContext类型进行深入分析,让您掌握数据映射的实现机制;然后我们介绍LINQ中调用数据库存储过程的使用方法;在第三步,我们一起来学习在LINQ中的并发和事务处理功能;最后,我们再来探讨一下LINQ的性能问题。通过本章的学习,使您能掌握常用的LINQ高级特性和方法。8.1 LINQ数据映射在第四章对LINQ To SQL 的学习中,我们用到了LINQ的数据映射和DataContext类型对象。在本小节中,我们一起来学习一下LINQ将数据库中关系型数据映射为数据实体类的实现机制,以及DataContext类型的日志功能和自动生成SQL语句功能,只有充分地掌握了这些机制,才能更好地理解和使用LINQ。为了让您充分地理解这些内容,我们还是自己动手编写代码来实现数据的映射,而不是使用如第四章中的自动生成代码的方法,但在本小节中仍然会使用到第四章中的OnLineStore的数据库,然后再将我们自己编写的代码与第四章LINQToSQLWebSite网站项目中的DataContext.dbml代码文件的实现情况做一个比较和分析。8.1.1 表和实体类首先,让我们来了解一下数据表是如何映射为一个实体类的。我们新建一个网站,命名为MoreLINQWebSite,在这个网站项目中我们手动地编写数据映射代码,这样更有助于我们了解其实现机制。我们在解决方案资源管理器中选择网站项目文件并添加两个类,分别命名为Category.cs和Product.cs,我们用这两个类分别作为Category表和Product表的数据实体类。好,下面开始编码工作,我们从Category类开始。因为我们要将Category类与数据库表相互映射,因此需要为项目添加一个对System.Data.Linq.dll的引用,如图8-1,并在代码文件的开头引用System.Data.Linq.Mapping的命名空间。做好一切的准备工作之后,我们在Category类声明的前面,加入一个Table(Name=”Category”)特性,如代码8-1,用以表示该类与数据库表中的Category表相映射,如果没有括号中的Name属性,LINQ会根据数据库表名和类名自动匹配,也就是说此时的数据库表名应该与类名相同。图8-1using System.Data.Linq.Mapping;/ 与数据库中Category表相互映射的实体类Table(Name=Category)public class Category代码8-1现在让我们来看看LINQToSQLWebSite中的表映射属性,打开LINQToSQLWebSite项目,找到App_Code文件夹下DataClasses.designer.cs文件中的Category实体类声明,我们可以同样的发现一个与数据库中表的映射声明,只不过在DataClasses.designer.cs文件中的这些代码都是自动生成的。采用同样的方法,可以将数据库中Product表映射到Product类,我们将它作为练习留给读者。8.1.2 字段和属性声明了数据库表的映射关系后,我们需要将数据库中的数据字段映射到类的属性,如代码8-2,在Category数据表中有两个字段,分别是主键CategoryID和Name,与之对应,我们为Category实体类声明了 CategoryID和Name属性。/ / 与数据库中Category表相互映射的实体类/ Table(Name=Category)public class Category /CategoryID属性 Column(IsPrimaryKey=true,Name=CategoryID,DbType=Int NOT NULL) public int CategoryID get; set; /Category Name属性 Column(Name = Name, DbType = VarChar(100) NOT NULL, CanBeNull = false) public string Name get; set; 代码8-2从代码8-2中我们可以看到,将类的属性添加一个Column特性,用来表示该数据与数据表中的列字段相匹配。在CategoryID属性的声明时我们使用了C#3.0中的自动属性新特性,并为Column特性添加了IsPrimaryKey属性,用来表明CategoryID属性在数据表中是一个主键,因此Category对象将CategoryID属性作为其身份的唯一标识,接着为Column添加了Name属性表示数据字段的名称,DbType属性用于标识字段在数据库中的数据类型,在这里的“Int NOT NULL”表明CategoryID字段是一个INT类型的非空字段。在Category类型的Name属性的Column特性中我们可以看到一个CanBeNull属性,它用来表明这个字段在数据库表中是否允许为空,在这里的设置为false,即表示该字段不允许为空。在LINQToSQLWebSite网站项目的DataClasses.designer.cs文件中,我们可以看到这里的Category类并没有使用自动属性特性,而是定义了一些私有字段,并用对应的公共属性去访问,在代码中我们可以看到Visual Studio自动生成的代码与我们刚刚编写的代码基本没有区别,只是更加的复杂,自然地使得自动生成的LINQ To SQL实体类具有非常强大的功能。采用同样的类属性与数据库字段映射方法,我们可以同样地为Product添加映射属性,我们也将它作为练习留给读者。8.1.3 实体类关联在数据库设计时,设计人员常常应用“三个范式”将数据表用外键的形式关联起来。OnLineStore数据库中的Category表的CategoryID主键就作为Product表的外键使这两个数据表相互关联,并且它们是一个一对多(one:many)的关系,即一条Category数据可以对应多个Product数据,因此可以通过CategoryID找到多个同一种商品类型的产品。在LINQ To SQL的实体类之间也可以实现这样的关联。根据数据库表之间的关联,我们可以在Category类中设计EntitySet类型的泛型集合,用于存放与之对应的Product类型对象,如代码8-3,在Product属性前用Associtation特性表明这是一个将Product类型与Category类型关联起来的属性集合,其中的OtherKey属性表明是按照“CategoryID”字段来关联,这样的方式与数据库表中的外键相同,在这里我们用到的是一项叫做对象树(Object Trees)的方法。using System.Data.Linq.Mapping;using System.Data.Linq;/ 需要添加 /将Category表与Product表关联起来 Association(Name = Category_Product, OtherKey = CategoryID) public EntitySet Product get; set; 代码8-3在声明了EntitySet类型的Product属性后,我们就可以根据Category对象来查找相应的Product对象集合。上述这些映射工作虽然可以依靠手工编写代码来完成,就像我们刚刚所做的这些工作这样,但是这无疑给开发人员带来了额外的工作,相反,使用LINQ To SQL实体类,即正如第四章中所介绍的DataClasses.dbml文件中所做的工作,可以自动地生成关系型数据到实体类映射的代码,而且还提供了更为丰富的功能,为我们提供了最大限度的便利。8.1.4 数据访问在做好了所有的数据实体类代码编写之后,我们来了解一下LINQ To SQL是如何通过DataContext类型实现数据访问的。我们可以把DataContext看作是LINQ To SQL的入口点和连接面向对象世界与关系型数据世界的一座桥梁,DataContext类型对象可以为我们做许多的工作,包括将LINQ查询表达式翻译为SQL语句、将数据从数据库中取出并返回以及将数据实体的更新写入数据库等等。下面我们还是结合一个小功能的需求来对数据访问进行讲解。假设我们现在需要从数据库中得到商品价格大于或等于999块的所有商品类别信息。因为商品的价格是存放在Product表中,因此,这里需要用到刚刚我们说的到表之间关联,如代码8-4。using System.Data.Linq;/需要添加using System.IO;/需要添加 protected void Page_Load(object sender, EventArgs e) /使用DataContext类型对象建立与数据库的连接 DataContext db = new DataContext(Data Source=localhost;Initial Catalog=OnLineStore;Integrated Security=True); /使用IO输出流到DBLog.txt文件 StreamWriter sw = new StreamWriter(Server.MapPath(DBLog.txt), true); /DataContext类型对象的日志功能 db.Log = sw; Table categoryList = db.GetTable(); var query = from category in categoryList/获取关联集合,并设置过滤条件 where category.Product.Any(p = p.Price = 999) select category; GridView1.DataSource = query;/数据绑定 GridView1.DataBind(); sw.Close();/关闭IO流 代码8-4首先我们在代码文件的开头添加了两个命名空间的引用,这是由于我们需要使用DataContext类型和Table泛型集合,它是在System.Data.Linq命名空间中声明的,另外为了说明DataContext类型对象的日志功能,我们用StreamWriter输出流将生成的SQL语句保存到当前网站项目目录下的DBLog.txt文件中。在PageLoad()方法的第一行代码中,我们声明了一个DataContext类型对象db,并用其构造方法将其实例化,注意我们在这里必须向构造方法传入数据库连接字符串。细心的读者也许会回想到,在第四章中我们用DataClassesDataContext 类型对象db时似乎没有提供任何的参数,是的,那是因为第四章中的DataClassesDataContext类继承了DataContext类,在Web.config配置文件中声明了OnLineStoreConnectionString字符串,并用反射的方法为DataClassesDataContext类型传入了该字符串。在接下来的代码中,我们用LINQ查询表达式从categoryList表集合中查询数据,这里需要注意的是,在用where进行数据的过滤时,我们调用了category临时对象的Product属性,这个属性就是我们在代码8-3中所声明的Product类型EntitySet泛型集合,它由于用了Association特性,会自动的与Product实体类型相关联。然后,我们调用Any()扩展方法,并向里传入一个参数p,Visual Studio会非常智能地发现在Product集合中存放的将会是Product类型对象,因此我们可以用p.Price属性来控制商品的价格条件。最后我们用select返回满足条件的Category对象集。我们在Default.aspx页面中放入一个GridView控件用以显示数据,并将它的数据源设置为query。编译并运行这段小程序,如图8-2,可以看到页面成功地显示了价格大于或等于999块的商品类别信息。图8-2DataContext类型对象会为我们自动地将LINQ查询表达式翻译为SQL语句,并将数据从数据库中取出并返回,我们可以通过查看刚才程序中输出的日志文件,查看这些生成的SQL语句,如图8-3,该文件位与当前项目文件夹中。图8-3虽然现在我们也能手工编写这样的数据映射和数据访问代码了,但是,使用如第四章中所用的方法来创建LINQ To SQL实体类会更加的方便和准确,我们需要做的仅仅是设计并实现好数据库。在本小节中通过手工方法编写代码是为了让读者更加清楚的认识到这些数据映射特性。小提示:8.2 LINQ与存储过程在前面的内容中,我们都是使用LINQ在服务器端生成SQL语句再发送给数据库,然后由数据库系统执行这些SQL操作并返回结果。但在实际的开发中,数据库管理员会为我们提供预定义好的存储过程(Stored procedure)来实现数据操作。使用存储过程可以增进数据库端的执行效率,并提高数据访问的安全性。与其他的数据库访问技术一样,通过LINQ也能调用存储过程,本小节中我们就一起来学习一下这些方法。小提示:存储过程(Stored Procedure)是一组为了完成特定功能的SQL语句集,经编译后存储在数据库中。用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。再运行存储过程前,数据库已对其进行了语法和句法分析,并给出了优化执行方案。这种已经编译好的过程可极大地改善SQL语句的性能。 由于执行SQL语句的大部分工作已经完成,所以存储过程能以极快的速度执行。存储过程是数据库中的一个重要对象,任何一个设计良好的数据库应用程序都应该用到存储过程。我们还是以LINQToSQLWebSite网站项目作为背景来讲解如何调用存储过程。现在,我们需要完成的工作是使用存储过程查找价格大于某一用户输入值的商品信息。下面我们首先给数据库中添加这个存储过程。打开SQL Server Management Studio,在对象资源管理器中,为OnLineStore数据添加一个存储过程,如代码8-5。CREATE PROCEDURE MyProcedureprice decimal(10,2)ASBEGIN select ProductID,Name,Description,Price from Productwhere Price = price order by Price descENDGO代码8-5接下来,打开LINQToSQLWebSite网站项目,在服务器资源管理器中刷新之前我们建立的与OnLineStore数据连接,我们就可以看到新建立的存储过程,如图8-4,打开DataClasses.dbml,并将MyProcedure存储过程拖入设计视图,如图8-5,打开DataClasses.designer.cs,可以找到如代码8-6。图8-4图8-5Function(Name=dbo.MyProcedure)public ISingleResult MyProcedure(Parameter(DbType=Decimal) System.Nullable price)IExecuteResult result = this.ExecuteMethodCall(this, (MethodInfo)(MethodInfo.GetCurrentMethod(), price);return (ISingleResult)(result.ReturnValue);代码8-6在代码8-6中可以发现,MyProcedure()方法返回的类型是ISingleResult泛型接口,因此Visual Studio 根据存储过程的返回值为我们自动生成了一个MyProcedureResult类,我们可以在DataClasses.designer.cs文件中找到这个类的声明。对于我们在数据库中定义的MyProcedure存储过程,我们仅仅需要返回的是Product类型或该类型中属性的一部分,因此,没有必要再声明一个新的MyProcedureResult实体类,我们可以在设计视图中,通过编辑MyProcedure存储过程的属性来指定其返回类型,如图8-6,我们指定其返回类型为Product。图8-6好,现在我们需要再新添加一个Web窗体,命名为StoredProcedurePage.aspx,并向里面添加一个GridView控件。对于存储过程的调用,是用到的LINQ To Objects,这是因为当程序运行时,是返回存储过程执行后产生的结果集到内存中,我们再访问内存中的数据集,因此,我们可以使用在第四章中介绍的任何LINQ方法,如代码8-7,我们将MyProcedure()方法作为数据源,并设置价格参数为1000,运行效果如图8-7。protected void Page_Load(object sender, EventArgs e) DataClassesDataContext db = new DataClassesDataContext(); decimal price = 1000.00M; /设置参数 var query = from p in db.MyProcedure(price) /调用存储过程 select p; /返回结果 GridView1.DataSource = query; /数据绑定 GridView1.DataBind(); 代码8-7图8-7读者也许已经发现了其中的问题,我们定义的存储过程中并没有包含ImageUrl属性和CategoryID属性,在运行结果中怎么会出现呢?原因是刚刚我们已经设置调用存储过程方法返回的类型为Product,而存储过程返回的结果集中没有包含这两个属性的值,因此,LINQ为我们自动填充了默认数据,正如CategoryID属性,因为它是int类型,而int类型的默认值是0,所以在运行结果中显示所有的CategoryID都变为了0。下面我们更改程序,以显示正确的结果,如代码8-8。protected void Page_Load(object sender, EventArgs e) DataClassesDataContext db = new DataClassesDataContext(); decimal price = 1000.00M; /设置参数 var query = from p in db.MyProcedure(price) /调用存储过程 select new p.ProductID, p.Name, p.Price ; /使用匿名类型返回结果 GridView1.DataSource = query; /数据绑定 GridView1.DataBind(); 代码8-8就是这样的方便,我们便完成了存储过程的调用。小提示:本小节中更改了存储过程方法的属性,让其返回了Product类型,这是由于我们的存储过程返回结果数据都为Product类型的属性,对于包含复杂SQL查询的存储过程我们还是应该使用Visual Studio为我们自动生成的新结果类型。8.3 LINQ并发与事务处理对于那些针对单个用户开发的单机版桌面应用程序而言,不会存在对数据的并发访问问题,因为只会有一个用户来访问或者修改后台数据,而我们所设计开发的ASP.NET Web应用程序面向的是网络用户,系统用户的人数往往是随着时间不断地增加,如何控制用户并发访问数据、修改数据成为了影响系统高效性和数据安全性的一个重要问题。在很多的系统应用中,要求多条数据处理必须具有完整性,比如要一次性地对多个表中相关联数据记录进行更改,要么都成功执行,如果其中任何一项操作,所关联的一系列数据操作均失效,并回滚操作到执行前,这就是数据库的事务处理,因此我们可以认为事务处理是将一组数据库操作合并为了一个逻辑上的工作单元。在本小节中,我们就一起来学习一下LINQ是如何帮助开发人员进行数据并发处理以及如何进行事务处理。8.3.1 并发问题首先我们来了解一下使用LINQ To SQL如何实现数据的并发处理,这里我们依然以LINQToSQLWebSite网站和OnLineStore数据库为背景。假设现在我们需要实现的一个功能就是更改数据库中用户的基本信息。在LINQToSQLWebSite网站项目中,我们新添加一个Web窗体,命名为ConcurrencyPage.aspx,并向窗体添加一个GridView控件、两个Label控件、一个DropDownList下拉菜单控件、一个TextBox控件和一个Button控件,如代码8-9。 代码8-9接下来我们可以在该窗体后台代码文件中编写页面初始时的数据绑定代码,如代码8-10,将ConcurrencyPage窗体设置为起始页,编译并运行该页面就可以得到当前数据库中所有的用户信息。using System.Data.Linq;/需要添加protected void Page_Load(object sender, EventArgs e) /页面首次调用 if(!Page.IsPostBack) Bind(); /数据绑定 void Bind() /声明DataContext类型对象 DataClassesDataContext db = new DataClassesDataContext(); /返回用户表中所有数据 Table userList = db.GetTable(); var query = from u in userList select u; /GridView控件数据绑定 GridView1.DataSource = query; GridView1.DataBind(); /DropDownList下拉菜单控件数据绑定 DropDownList1.DataSource = query; DropDownList1.DataTextField = UserID; DropDownList1.DataValueField = UserID; DropDownList1.DataBind(); 代码8-10做好了准备工作,在编写处理并发数据访问代码之前,我们来回顾一下之前我们是如何进行数据修改并更新的。我们首先需要使用LINQ从数据库中取回数据,并以对象的形式保存在内存中;然后通过代码修改对象的属性值;最后通过DataContext类型对象的SubmitChanges()方法更新数据库,如代码8-11,这样的代码看起来似乎没有什么问题,但对于允许多用户同时访问并修改的数据,这样的方法有时就会产生非常严重的问题,那就是因为这段代码中没有考虑到数据的并发问题。/声明并初始化DataContext类型对象 DataClassesDataContext db = new DataClassesDataContext(); /从数据库中取出符合用户ID的用户对象信息 var user = (from u in db.User where u.UserID = Convert.ToInt32(DropDownList1.SelectedValue) select u).First(); /修改密码属性值 user.Password = TextBox1.Text;db.SubmitChanges();代码8-11想象一下,如果当我们将用户信息数据从数据库中取出并保存,但在我们修改属性值以及更新数据库之前,另一个用户A已经调用同样的页面更改了用户的数据,此时数据库中的数据与我们保存在内存中的数据就产生了不同,而我们对此却一无所知,我们还是会提交更新,一个数据并发问题在这个时候就发生了。到底是将数据库中数据更新为我们所提交新内容,还是保留A的数据而拒绝我们的更新操作呢,甚至是当我们和用户A修改了同一条记录中的不同字段时,是否同时保留二条更新,这些都是我们在开发并发程序时需要考虑的问题。那么我们应该怎么做来尽量避免这样的问题呢,也就是我们应该以什么样的方式和方法来处理并发问题呢。答案很简单,那就是在我们执行数据更新操作时将我们内存中保存的信息和当前数据库中的信息做一个比较,如果二者的数据一致,则表示在我们进行数据操作期间没有并发问题的发生,我们的更新操作将顺利完成,如果二者的数据不一致则表明并发问题的产生,此时就需要根据实际的业务逻辑来决定处理方法。小提示:在并发问题中存在着两种类型,分别是:悲观并发(Pessimistic Concurrency)和乐观并发(Optimistic Concurrency)。悲观并发是指当一个用户A访问了某一数据时,程序会将该数据上锁(lock),如果此时另外一用户B试图访问同样的数据时,程序会拒绝这样的访问,直到用户A完成了对该数据的访问,取消了对数据的锁定时用户B才能获取该数据的访问权利。乐观并发是指对某一数据允许多个用户同时访问并修改。但是最终保留谁的修改由程序来决定。LINQ也是采用了乐观并发。正是为了处理这样的问题,LINQ为我们提供了强大而非常便捷的并发处理能力。对数据的并发问题实际上在我们使用LINQ To SQL实体进行数据表、字段到实体类、属性映射时已经开始。LINQ默认地为自动生成的实体类属性添加了并发验证的属性UpdateCheck,并设置为Always,即总是进行并发验证。也就是说,对于每一个采用这样设置的类型对象,当我们在进行数据更新时,会将保存在我们内存中的对象属性值与发送给数据库服务器进行验证。当然,将每条属性的UpdateCheck属性设置Always会在性能上造成一定的损失,因此,我们可以根据实际的业务逻辑取消某条属性的并发验证,即设置UpdateCheck为Never,或者设置该属性值改变时才进行验证,即设置UpdateCheck为WhenChanged。在这里,为了更好地说明如何处理并发,我们保持LINQToSQLWebSite项目中DataClasses.designer.cs文件中User实体类的各个属性的UpdateCheck设置为总是进行验证,虽然在代码中我们没有看到显示的UpdateCheck声明,但LINQ默认为其Always。在进行了并发验证设置之后,我们需要做的就是处理一旦并发问题发生时所需要执行的代码。在LINQ中,如果程序检测到了并发问题,会抛出一个ChangeConflictException异常,我们将捕获该异常并处理。下面,我们再来编写用户修改用户密码并提交事件后所执行的代码,并处理如果并发修改异常发生时所执行的代码,如代码8-12。 protected void Button1_Click(object sender, EventArgs e) /声明并初始化DataContext类型对象 DataClassesDataContext db = new DataClassesDataContext(); /从数据库中取出符合用户ID的用户对象信息 var user = (from u in db.User where u.UserID = Convert.ToInt32(DropDownList1.SelectedValue) select u).First(); /修改密码属性值 user.Password = TextBox1.Text; try /更新数据库 db.SubmitChanges(ConflictMode.ContinueOnConflict); /捕获异常 catch (ChangeConflictException) /处理异常 db.ChangeConflicts.ResolveAll(RefreshMode.OverwriteCurrentValues); db.SubmitChanges(); finally /重新绑定数据,显示更新后结果 Bind(); 代码8-12我们使用了try-catch-finally块来捕获异常和处理ChangeConflictException异常。在try代码块中,我们同样调用DataContext类型对象db的SubmitChange()方法,但向方法中传入了一个枚举类型参数ConflictMode.ContinueOnConflict,由于DataContext类型允许一次进行多条数据的更新操作,该参数是指如果在一个更新操作执行过程中一个并发异常产生,那么继续进行后续的更新操作,如果我们想立刻停止更新操作并取消之前的更新,可以将该参数修改并设置为ConflictMode.FailOnFirstConflict。接下来,我们在catch块中捕获ChangeConfictException异常,并编写了相应的处理方法,决定如何处理的关键是RefreshMode参数值。在代码8-12中我们可以看到,在catch代码块中我们设置了RefreshMode.OverwriteCurrentValues参数,对于RefeshMode枚举类型中存在三个值供我们选择,分别是:KeepChanges、KeepCurrentValues和OverWriteCurrentValues,它们分别有着不同的含义,我们还是假设另一用户A在我们进行数据修改提交之前已经修改了数据库中数据,并由此产生了数据的并发修改问题,如表8-1描述。名称描述KeepChanges如果用户A更改了某条记录中的某一个字段值,而这个字段值在我们的更新记录中没有修改,此时将保留用户A的更新值,同时也将我们更新的字段值保存,但如果我们更新的字段与用户A更新的字段冲突,那以我们的最新修改为准。KeepCurrentValues总是以我们最新的更新值为准,完全覆盖用户A的修改值。OverWriteCurrentValues保留用户A所做的修改更新,而放弃我们的修改更新操作。表8-1为了更好地帮助您理解,现在我们来模拟一下并发问题发生时的情况。我们打开并登录进入SQL Server Management Studio,打开OnLineStore数据库中的User表,如图8-8。图8-8在Visual Studio的ConcurrencyPage.aspx.cs代码文件的try代码块中添加一个断点,如图8-9,当程序执行到断点时,我们可以修改数据库中的值来模拟并发的发生。图8-9好,下面编译并运行ConcurrencyPage.aspx窗体,可以得到当前数据库中的数据显示,如图8-10,在下拉菜单中显示的是供用户选择的用户ID值,我们选择默认的ID号为1,并添加新密码“123456”,然后点击“提交更新”按钮,程序会运行至断点并停止。图8-10现在,我们更改OnLineStore数据库User表中UserID为1的数据,如图8-11,我们模拟了另一个用户在我们进行数据修改,但还未将修改更新到数据库之时,已经更新了数据,这就一个典型的并发问题,此时,我们读取在内存中的数据与数据库中的数据因另外个用户的修改而不同。图8-11模拟并发问题完成,我们点击编译的按钮,将停在断点处的程序继续执行,我们会得到什么样的结果呢?答案就在ConcurrencyPage的页面中,我们可以发现,现在在页面上呈现的数据是我们手动在数据库总更改的数据,而我们在页面上修改的数据没有被保存,如图8-12,这全都是因为我们设置了RefreshMode.OverWriteCurrentValues,它会保留并发问题发生时数据库中的现有数据,在我们具体的情况下也就是会保留用户A所做的用户数据修改,从而放弃我们在页面所做的数据修改提交操作,正如表8-1中所述。图8-12让我们来看一看另外的一种情况,在ConcurrencyPage.aspx.cs代码文件的catch代码块中更改RefreshMode属性值,将其设置为RefreshMode.KeepCurrenctValues,如代码8-13,再编译并运行,此时可看到效果如图8-13,我们选择用户ID为2,在新密码文本框中输入“123456”,点击“提交更新”按钮,程序会再一次在断点处暂停。/捕获异常 catch (ChangeConflictException) /处理异常 db.ChangeConflicts.ResolveAll(RefreshMode.KeepCurrentValues); db.SubmitChanges(); 代码8-13图8-13现在我们在SQL Server Management Studio中更改OnLineStore数据库User表中UserID为2的记录的UserName字段为“newUser2”,Password字段为“654321”,如图8-14,同样模拟了另一个用户在我们修改数据并提交数据库之前已经将自己的数据更新到数据库,这也是一个并发修改问题。图8-14完成了并发模拟,我们继续ASP.NET程序的执行,这时显示在页面上的数据如图8-15,我们可以发现,另一用户A在数据库中更新的数据并没有被保留,这是由于KeepCurrentValues的设置,如表8-1中所述,程序总是以我们最新提交的数据更新为主,而覆盖用户A更新的值。图8-15我们现在将RefreshMode属性设置为KeepChanges,并编译运行ConcurrencyPage页面,如代码8-14,选择用户ID为3的用户,设置新密码为“123456”,点击“提交更新”按钮,程序在断点暂停。/捕获异常 catch (ChangeConflictException) /处理异常 db.ChangeConflicts.ResolveAll(RefreshMode.KeepChanges); db.SubmitChanges(); 代码8-14然后再更改数据表中UserID为“3”的记录,将UserName设置为“newUser3”,password设置为“654321”,如图8-16,模拟并发问题的发生。图8-16下面我们让处于暂停状态中的程序继续运行,可以看到最后的结果如图8-17,UserName字段值现在保留为用户A的更新值“newUser3”,而password字段值最终保留的是“123456”,这样的机制正如表8-1中所述,即如果用户A更改了某条记录中的某一个字段值,而这个字段值在我们的更新记录中没有修改,此时将保留用户A的更新值,同时也将我们更新的字段值保存,但如果我们更新的字段与用户A更新的字段冲突,那以我们的最新修改为准。图8-17至此,我们已经向您介绍了所有通过RefreshMode属性的不同设置来处理并发的情况,您在实际的系统开发过程中可根据不同的业务逻辑选择适合的RefreshMode属性。小提示:对于RefreshMode中的三种属性值:KeepChanges、KeepCurrentValues 以及OverKeepCurrenctValues,读者应该充分地掌握各自的含义,也许您此时会对它们感觉到困惑,没关系,正所谓“百闻不如一见”,打开您的电脑,跟随着我们刚才所讲述的内容,实际地动手编写一下程序,相信您会很好的掌握。8.3.2 事务处理在本小节的开头介绍过,我们常常将多个数据库操作定义在一个事务中,以实现这多个数据库操作的完整性,即对于这个事务中的数据库操作要么都成功,要么所有操作都回滚。事务也是处理并发问题的一项重要方法。LINQ为我们提供了非常便捷的事务处理方法,下面就让我们一起来学习一下。使用LINQ来执行事务操作,我们还是需要借助DataContext类型对象的帮助。我们将ConcurrencyPage窗体中后台代码中的按钮事件代码稍微做一个修改,使用事务处理的方式来执行数据的更新问题,如代码8-15。protected void Button1_Click(object sender, EventArgs e) /声明并初始化DataC

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论