LINQ数据映射.docx
- 文档编号:9349626
- 上传时间:2023-05-18
- 格式:DOCX
- 页数:29
- 大小:638.87KB
LINQ数据映射.docx
《LINQ数据映射.docx》由会员分享,可在线阅读,更多相关《LINQ数据映射.docx(29页珍藏版)》请在冰点文库上搜索。
LINQ数据映射
第八章LINQ进阶
8.1LINQ数据映射
在第四章对LINQToSQL的学习中,我们用到了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-1
usingSystem.Data.Linq.Mapping;//
//与数据库中Category表相互映射的实体类
[Table(Name="Category")]
publicclassCategory
{
}
代码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")]
publicclassCategory
{
//CategoryID属性
[Column(IsPrimaryKey=true,Name="CategoryID",DbType="IntNOTNULL")]
publicintCategoryID{get;set;}
//CategoryName属性
[Column(Name="Name",DbType="VarChar(100)NOTNULL",CanBeNull=false)]
publicstringName{get;set;}
}
代码8-2
从代码8-2中我们可以看到,将类的属性添加一个[Column]特性,用来表示该数据与数据表中的列字段相匹配。
在CategoryID属性的声明时我们使用了C#3.0中的自动属性新特性,并为[Column]特性添加了IsPrimaryKey属性,用来表明CategoryID属性在数据表中是一个主键,因此Category对象将CategoryID属性作为其身份的唯一标识,接着为[Column]添加了Name属性表示数据字段的名称,DbType属性用于标识字段在数据库中的数据类型,在这里的“IntNOTNULL”表明CategoryID字段是一个INT类型的非空字段。
在Category类型的Name属性的[Column]特性中我们可以看到一个CanBeNull属性,它用来表明这个字段在数据库表中是否允许为空,在这里的设置为false,即表示该字段不允许为空。
在LINQToSQLWebSite网站项目的DataClasses.designer.cs文件中,我们可以看到这里的Category类并没有使用自动属性特性,而是定义了一些私有字段,并用对应的公共属性去访问,在代码中我们可以看到VisualStudio自动生成的代码与我们刚刚编写的代码基本没有区别,只是更加的复杂,自然地使得自动生成的LINQToSQL实体类具有非常强大的功能。
采用同样的类属性与数据库字段映射方法,我们可以同样地为Product添加映射属性,我们也将它作为练习留给读者。
8.1.3实体类关联
在数据库设计时,设计人员常常应用“三个范式”将数据表用外键的形式关联起来。
OnLineStore数据库中的Category表的CategoryID主键就作为Product表的外键使这两个数据表相互关联,并且它们是一个一对多(one:
many)的关系,即一条Category数据可以对应多个Product数据,因此可以通过CategoryID找到多个同一种商品类型的产品。
在LINQToSQL的实体类之间也可以实现这样的关联。
根据数据库表之间的关联,我们可以在Category类中设计EntitySet类型的泛型集合,用于存放与之对应的Product类型对象,如代码8-3,在Product属性前用[Associtation]特性表明这是一个将Product类型与Category类型关联起来的属性集合,其中的OtherKey属性表明是按照“CategoryID”字段来关联,这样的方式与数据库表中的外键相同,在这里我们用到的是一项叫做对象树(ObjectTrees)的方法。
usingSystem.Data.Linq.Mapping;
usingSystem.Data.Linq;//需要添加
//将Category表与Product表关联起来
[Association(Name="Category_Product",OtherKey="CategoryID")]
publicEntitySet
{
get;
set;
}
代码8-3
在声明了EntitySet
上述这些映射工作虽然可以依靠手工编写代码来完成,就像我们刚刚所做的这些工作这样,但是这无疑给开发人员带来了额外的工作,相反,使用LINQToSQL实体类,即正如第四章中所介绍的DataClasses.dbml文件中所做的工作,可以自动地生成关系型数据到实体类映射的代码,而且还提供了更为丰富的功能,为我们提供了最大限度的便利。
8.1.4数据访问
在做好了所有的数据实体类代码编写之后,我们来了解一下LINQToSQL是如何通过DataContext类型实现数据访问的。
我们可以把DataContext看作是LINQToSQL的入口点和连接面向对象世界与关系型数据世界的一座桥梁,DataContext类型对象可以为我们做许多的工作,包括将LINQ查询表达式翻译为SQL语句、将数据从数据库中取出并返回以及将数据实体的更新写入数据库等等。
下面我们还是结合一个小功能的需求来对数据访问进行讲解。
假设我们现在需要从数据库中得到商品价格大于或等于999块的所有商品类别信息。
因为商品的价格是存放在Product表中,因此,这里需要用到刚刚我们说的到表之间关联,如代码8-4。
usingSystem.Data.Linq;//需要添加
usingSystem.IO;//需要添加
protectedvoidPage_Load(objectsender,EventArgse)
{
//使用DataContext类型对象建立与数据库的连接
DataContextdb=newDataContext("DataSource=localhost;
InitialCatalog=OnLineStore;IntegratedSecurity=True");
//使用IO输出流到DBLog.txt文件
StreamWritersw=newStreamWriter(Server.MapPath("DBLog.txt"),true);
//DataContext类型对象的日志功能
db.Log=sw;
Table
varquery=fromcategoryincategoryList
//获取关联集合,并设置过滤条件
wherecategory.Product.Any(p=>p.Price>=999)
selectcategory;
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,VisualStudio会非常智能地发现在Product集合中存放的将会是Product类型对象,因此我们可以用p.Price属性来控制商品的价格条件。
最后我们用select返回满足条件的Category对象集。
我们在Default.aspx页面中放入一个GridView控件用以显示数据,并将它的数据源设置为query。
编译并运行这段小程序,如图8-2,可以看到页面成功地显示了价格大于或等于999块的商品类别信息。
图8-2
DataContext类型对象会为我们自动地将LINQ查询表达式翻译为SQL语句,并将数据从数据库中取出并返回,我们可以通过查看刚才程序中输出的日志文件,查看这些生成的SQL语句,如图8-3,该文件位与当前项目文件夹中。
图8-3
虽然现在我们也能手工编写这样的数据映射和数据访问代码了,但是,使用如第四章中所用的方法来创建LINQToSQL实体类会更加的方便和准确,我们需要做的仅仅是设计并实现好数据库。
在本小节中通过手工方法编写代码是为了让读者更加清楚的认识到这些数据映射特性。
8.2LINQ与存储过程
在前面的内容中,我们都是使用LINQ在服务器端生成SQL语句再发送给数据库,然后由数据库系统执行这些SQL操作并返回结果。
但在实际的开发中,数据库管理员会为我们提供预定义好的存储过程(Storedprocedure)来实现数据操作。
使用存储过程可以增进数据库端的执行效率,并提高数据访问的安全性。
与其他的数据库访问技术一样,通过LINQ也能调用存储过程,本小节中我们就一起来学习一下这些方法。
我们还是以LINQToSQLWebSite网站项目作为背景来讲解如何调用存储过程。
现在,我们需要完成的工作是使用存储过程查找价格大于某一用户输入值的商品信息。
下面我们首先给数据库中添加这个存储过程。
打开SQLServerManagementStudio,在对象资源管理器中,为OnLineStore数据添加一个存储过程,如代码8-5。
CREATEPROCEDUREMyProcedure
@pricedecimal(10,2)
AS
BEGIN
select[ProductID],[Name],[Description],[Price]from[Product]
where[Price]>=@price
orderby[Price]desc
END
GO
代码8-5
接下来,打开LINQToSQLWebSite网站项目,在服务器资源管理器中刷新之前我们建立的与OnLineStore数据连接,我们就可以看到新建立的存储过程,如图8-4,打开DataClasses.dbml,并将MyProcedure存储过程拖入设计视图,如图8-5,打开DataClasses.designer.cs,可以找到如代码8-6。
图8-4
图8-5
[Function(Name="dbo.MyProcedure")]
publicISingleResult
{
IExecuteResultresult=this.ExecuteMethodCall(this,((MethodInfo)(MethodInfo.GetCurrentMethod())),price);
return((ISingleResult
}
代码8-6
在代码8-6中可以发现,MyProcedure()方法返回的类型是ISingleResult
对于我们在数据库中定义的MyProcedure存储过程,我们仅仅需要返回的是Product类型或该类型中属性的一部分,因此,没有必要再声明一个新的MyProcedureResult实体类,我们可以在设计视图中,通过编辑MyProcedure存储过程的属性来指定其返回类型,如图8-6,我们指定其返回类型为Product。
图8-6
好,现在我们需要再新添加一个Web窗体,命名为StoredProcedurePage.aspx,并向里面添加一个GridView控件。
对于存储过程的调用,是用到的LINQToObjects,这是因为当程序运行时,是返回存储过程执行后产生的结果集到内存中,我们再访问内存中的数据集,因此,我们可以使用在第四章中介绍的任何LINQ方法,如代码8-7,我们将MyProcedure()方法作为数据源,并设置价格参数为1000,运行效果如图8-7。
protectedvoidPage_Load(objectsender,EventArgse)
{
DataClassesDataContextdb=newDataClassesDataContext();
decimalprice=1000.00M;//设置参数
varquery=frompindb.MyProcedure(price)//调用存储过程
selectp;//返回结果
GridView1.DataSource=query;//数据绑定
GridView1.DataBind();
}
代码8-7
图8-7
读者也许已经发现了其中的问题,我们定义的存储过程中并没有包含ImageUrl属性和CategoryID属性,在运行结果中怎么会出现呢?
原因是刚刚我们已经设置调用存储过程方法返回的类型为Product,而存储过程返回的结果集中没有包含这两个属性的值,因此,LINQ为我们自动填充了默认数据,正如CategoryID属性,因为它是int类型,而int类型的默认值是0,所以在运行结果中显示所有的CategoryID都变为了0。
下面我们更改程序,以显示正确的结果,如代码8-8。
protectedvoidPage_Load(objectsender,EventArgse)
{
DataClassesDataContextdb=newDataClassesDataContext();
decimalprice=1000.00M;//设置参数
varquery=frompindb.MyProcedure(price)//调用存储过程
selectnew{p.ProductID,p.Name,p.Price};//使用匿名类型返回结果
GridView1.DataSource=query;//数据绑定
GridView1.DataBind();
}
代码8-8
就是这样的方便,我们便完成了存储过程的调用。
8.3LINQ并发与事务处理
对于那些针对单个用户开发的单机版桌面应用程序而言,不会存在对数据的并发访问问题,因为只会有一个用户来访问或者修改后台数据,而我们所设计开发的ASP.NETWeb应用程序面向的是网络用户,系统用户的人数往往是随着时间不断地增加,如何控制用户并发访问数据、修改数据成为了影响系统高效性和数据安全性的一个重要问题。
在很多的系统应用中,要求多条数据处理必须具有完整性,比如要一次性地对多个表中相关联数据记录进行更改,要么都成功执行,如果其中任何一项操作,所关联的一系列数据操作均失效,并回滚操作到执行前,这就是数据库的事务处理,因此我们可以认为事务处理是将一组数据库操作合并为了一个逻辑上的工作单元。
在本小节中,我们就一起来学习一下LINQ是如何帮助开发人员进行数据并发处理以及如何进行事务处理。
8.3.1并发问题
首先我们来了解一下使用LINQToSQL如何实现数据的并发处理,这里我们依然以LINQToSQLWebSite网站和OnLineStore数据库为背景。
假设现在我们需要实现的一个功能就是更改数据库中用户的基本信息。
在LINQToSQLWebSite网站项目中,我们新添加一个Web窗体,命名为ConcurrencyPage.aspx,并向窗体添加一个GridView控件、两个Label控件、一个DropDownList下拉菜单控件、一个TextBox控件和一个Button控件,如代码8-9。
center"> GridViewID="GridView1"runat="server"> GridView> LabelID="Label1"runat="server"Text="请选择用户: "> Label> DropDownListID="DropDownList1"runat="server"> DropDownList> LabelID="Label2"runat="server"Text="新密码: "> Label> TextBoxID="TextBox1"runat="server"TextMode="Password"> TextBox> ButtonID="Button1"runat="server"Text="提交更改"onclick="Button1_Click"/>
代码8-9
接下来我们可以在该窗体后台代码文件中编写页面初始时的数据绑定代码,如代码8-10,
将ConcurrencyPage窗体设置为起始页,编译并运行该页面就可以得到当前数据库中所有的用户信息。
usingSystem.Data.Linq;//需要添加
protectedvoidPage_Load(objectsender,EventArgse)
{
//页面首次调用
if(!
Page.IsPostBack)
{
Bind();
}
}
//数据绑定
voidBind()
{
//声明DataContext类型对象
DataClassesDataContextdb=newDataClassesDataContext();
//返回用户表中所有数据
Table
varquery=fromuinuserList
selectu;
//GridView控件数据绑定
GridView1.DataSource=query;
GridView1.DataBind();
//DropDownList下拉菜单控件数据绑定
DropDownList1.DataSource=query;
DropDownList1.DataTextField="UserID";
DropDownList1.DataValueField="UserID";
DropDownList1.Data
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- LINQ 数据 映射