DLinq高级主题
因为实体类拥有描述数据库表格和列的结构的特性,所以我们可以利用这些信息来创建一个新的数据库实例。您可以通过调用DataContext的CreateDatabase()来创建一个数据库,DLinq将根据已经定义的实体类的结构来生成数据库。您在很多地方需要用到这个功能。您可能需要构建一个在客户系统中自动安装的应用程序,或者需要实现一个离线保存数据的本地数据库的客户端应用程序。对于这些应用,CreateDatabase()提供了一个理想的处理方式,特别是对于那些DLinq认识的数据提供者,如SQL Server Express 2005。
尽管如此,单纯的数据特性并不能体现出一个已有的数据库结构的所有方面。特性就无法体现用户自定义函数、存储过程、触发器以及检测约束。CreateDatabase()方法只能根据已知的信息创建一个数据库。但是,在很多情况下这些已经足够了。
下面的例子告诉我们怎样创建一个数据库MyDVDs.mdf。
[Table(Name="DVDTable")]
publicclassDVD
{
[Column(Id = true)]
publicstring Title;
[Column]
publicstring Rating;
}
publicclassMyDVDs : DataContext
{
publicTable<DVD> DVDs;
public MyDVDs(string connection) : base(connection) {}
}
根据我们的对象模型,使用SQL Server Express 2005作为数据提供者,我们使用下面的方法来创建数据库:
MyDVDs db = new MyDVDs("c:""mydvds.mdf");
db.CreateDatabase();
DLinq同样也提供了在创建新的数据库的之前删除掉一个已存在的数据库的方法。我们可以修改上面的创建数据库的代码,调用DatabaseExists()方法检测是否存在一个已有的数据库,如果有则调用DeleteDatabase()删除这个数据库。
MyDVDs db = newMyDVDs("c:""mydvds.mdf");
if (db.DatabaseExists()) {
Console.WriteLine("Deleting old database...");
db.DeleteDatabase();
}
db.CreateDatabase();
调用CreateDatabase()创建了一个新的数据库,我们就能够对它进行查询和调用它的SubmitChanges()将新对象添加倒这个MDF文件中。
您也可以使用MDF 文件或者一个Catalog的名称调用CreateDatabase()来作用于SQL server。这取决于您使用的连接字符串。连接字符串描述了所要访问的数据库,但这个数据库并不是必须存在的。DLinq会从中提取有用的信息来决定要在什么数据库服务器上创建什么数据库。当然,您需要有数据库服务器的管理员或管理员等同的权限
6.2 和ADO.NET的互操作
DLinq是ADO.NET技术的一部分。它以ADO.NET模型为基础,所以您可以在您的ADO.NET程序中混合使用DLinq。
您可以通过一个已有的ADO.NET连接来创建一个DLinq DataContext对象。DataContext将使用您提供的连接来进行所有的操作,包括查询。如果这个连接已经被打开了,DLinq将使用这个连接,当操作完成的时候就关闭这个连接。在一般情况下,DLinq 在某个操作执行完之后会自动关闭这个连接,除非这个操作存在于某个事务当中。
SqlConnection con =new SqlConnection( ... );
con.Open();
...
// DataContext takes a connection
Northwind db = new Northwind(con);
...
var q =
from c in db.Customers
where c.City == “London”
select c;
您也可以调用DataContext的Connection属性来返回这个连接,然后关闭它。
db.Connection.Close();
当您的应用程序已经拥有了某个事务而您需要DataContext也使用这个已有的事务进行操作时,那么您可以将这个事务对象赋给DataContext的LocalTransaction。
IDbTransaction = con.BeginTransaction();
...
db.LocalTransaction = myTransaction;
db.SubmitChanges();
db.LocalTransaction = null;
设置DataContext的LocalTransaction为某个事务对象,DLinq在执行任何查询或者命令时都将使用这个事务。但操作完成后您需要将DataContext的LocalTransaction设置为null。
但在.NET framework 2.0中,使用事务的更好的方法是使用TransactionScope对象。它可以处理跨数据库或跨其他内存资源管理器访问的分布式事务。对于某个事务范围内需要同时访问多个数据库或者多个连接情况,使用TransactionScope对象可以很好地满足分布式事务的要求。
using(TransactionScope ts = newTransactionScope()) {
db.SubmitChanges();
ts.Complete();
}
如果您根本不需要使用已存在的事务来进行查询,那么推荐使用Transaction Scope来进行事务处理,这样会更容易一点。但这并不是对任何数据库系统都是有效的。对于SQL Server 200,SqlClient连接并不能处理系统分布式事务。而SQL Server 2005服务器却可以很好地处理这种情况。因为,SQL Server 2005会把任何一个Transaction Scope看作是一个完全分布式事务对象。
或许上面提到的这些对您并没什么用处。您的应用程序基本不要用到分布式事务,那么为什么要花费多于的开销?在这种情况下,如果您确实不需要处理分布式事务,那么您可以告诉DataContext的SqlClient 连接不需要处理分布式事务。
using(TransactionScope ts = newTransactionScope()) {
db.UseLocalTransactionsOnly = true;
db.SubmitChanges();
ts.Complete();
}
设置DataContext的UserLocalTransactionsOnly属性为true可以强制TransactionScope只处理本地事务。设置这个属性的时候,确保DataContext的连接对象已经关闭了,否则这个设置是无效的。如果后续请求需要开始一个分布式事务,那么这个分布式事务并不会被禁止。
DLinq不单单只使用事务和连接来和ADO.NET进行互操作。在某些情况下,您会发现您需要使用查询或者提交操作来处理一些特殊的问题,而DataContext根本无法满足需求。这个时候,您就可能需要使用原始的SQL命令直接操作数据库。
您可以直接调用ExecuteQuery()方法来执行原始的SQL查询,查询的结果返回的是一个实体的集合。例如,假设Customer实体类的数据包括customer1和customer2,下面这个查询将返回Customer实体集合。
IEnumerable<Customer> results = db.ExecuteQuery<Customer>(
@"select c1.custid as CustomerID, c2.custName as ContactName
from customer1 as c1, customer2 as c2
where c1.custid = c2.custid"
);
DLinq将会把这些SQL查询的结果的列名称匹配实体类中拥有列特性的属性,然后将这些结果直接转化为实体。
ExecuteQuery()方法还允许使用参数。下面代码执行的是一个参数化查询:
IEnumerable<Customer> results = db.ExecuteQuery<Customer>(
”select contactname from customers where city = {0}”,
“London”
);
查询文本的参数使用类似Console.WriteLine()和String.Format()的方式表示。事实上,您提供的查询字符串将调用String.Format()的方法,所不同的是,您可以使用大括号表示的参数结构来替代参数名称,如@p0, @p1 …, @p(n)。
6.3 实体类自动生成工具
如果您已经拥有了某个数据库,那您就没必要再手动创建这个数据库的实体对象模型了,您可以使用实体类自动生成工具。DLinq安装包中包括了一个叫SQLMetal的命令行工具。它会根据数据库的元数据来推断相应的.NET类型,然后创建出相应的实体类。
您可以使用SQLMetal来创建实体类的源文件。或者您也可以将整个工作分成两部分来做,首先将SQL元数据转化为XML,然后将这个XML文件转化为实体类源文件。分两个步骤来处理的好处是您可以修改生成的XML元数据文件。自动提取实体类的过程只能根据数据库表格和列的元数据进行有限的推断。您也许会发现您需要得到更好的结果或者您需要在您的实体中去掉数据库的某些细节,那您就需要修改中间生产的XML文件了。
使用SQLMetal最简单的解决方案就是直接从已有的数据库生成实体类。下面的例子告诉您怎样使用这个工具:
SqlMetal /server:."SQLExpress /database:Northwind /delayfetch /pluralize /namespace:nwind /code:Northwind.cs
执行上面的例子,我们将创建出Northwind.cs文件,它包含了根据数据库元数据生成的对象模型。如果数据库表格的名称和您要生成的实体类的名称一致,那您只需要使用上面的方法就可以了。如果不是,您就需要分两步来处理。
首先,让SQLMetal产生一个XML文件,如下:
SqlMetal /server:."SQLExpress /database:Northwind /pluralize
/xml:Northwind.xml
生成XML文件之后,您可以修改XML文件中的class和property属性,这两个属性决定了数据库表和列到实体类的映射关系。修改完毕之后,您可以使用下面的命令来生成您的对象模型:
SqlMetal /namespace:nwind /code:Northwind.cs Northwind.xml
SQLMetal使用方法如下:
SqlMetal [options] [filename]
下面的表格显示SQLMetal命令行选项:
选项 |
描述 |
/server:<name> |
数据库服务器的名称 |
/database:<name> |
数据库名称 |
/user:<name> |
登陆数据库服务器的用户名 |
/password:<name> |
登陆数据库服务器的密码 |
/views |
提取数据库视图 |
/functions |
提取数据库自定义方法 |
/sprocs |
提取数据库过程 |
/code[:<filename>] |
生成的实体类以及源文件路径 |
/language:<language> |
生成的代码使用VB 或C# (默认) |
/xml[:<filename>] |
生成的XML文件路径 |
/code[:<filename>] |
生成的实体类源文件路径 |
/map[:<filename>] |
使用外部的映射文件而不是属性来生成目标文件 |
/pluralize |
是否采用英语的复数/单数转换以生成更优雅的实体类和属性的名称, |
/namespace:<name> |
生成的实体类的命名空间 |
/dataAttributes |
自动生成DataObjectField和Precision特性 |
/timeout:<seconds> |
执行数据库命令的时间阈值 |
注意:如果要使用MDF文件的元数据来生成实体类,您需要在这些选项后面指定MDF文件的名称。如果没有指定/server,默认使用localhost/sqlexpress。
6.4 自动生成的XML参考
生成的XML文件是对数据库元数据的最基本的描述。XML文件包含了标识SQL元数据到简单对象的映射属性,如数据库名称以及数据库表格和列。
下面是一个生成的XML文件的原型:
<Database
Name = “database-name”
Access = “public|internal”
Class = ”context-class-name” >
<Schema
Name = “schema-name”
Hidden = “true|false”
Access = “public|internal”
Class = “schema-class-name” >
<Table
Name = “…”
Hidden = “true|false”
Access = “public|internal”
Class = “element-class-name”
Property = “context-or-schema-property-name” >
<PrimaryKey
Name = “primary-key-constraint-name”
Hidden = ”true|false” >
<Column Name = ”key-column-name” />
...
</PrimaryKey>
...
<Unique
Name = “uniqueness-constraint-name”
Hidden = “true|false” >
<Column Name = “column-name” />
...
</Unique>
...
<Index
Name = “index-name”
Hidden = “true|false”
Style = “db-style-info like clustered etc”
IsUnique = “true|false”>
<Column Name = “column-name” />
...
</Index>
...
<Type
Name = “…”
Hidden = “true|false”
Access = “public|private|internal|protected”
InheritanceCode = “…”
IsInheritanceDefault = “true|false” >
<Column
Name = “…”
Hidden = “true|false”
Access = “public|private|internal|protected”
Property = ”property-name”
DBType = ”database-type”
Type = ”CLR-type”
Nullable = ”true|false”
IsIdentity = ”true|false”
IsAutoGen = ”true|false”
IsVersion = ”true|false”
IsReadOnly = ”true|false”
UpdateCheck = ”Always|Never|WhenChanged” />
...
<Association
Name = ”…”
Hidden = ”true|false”
Access = ”public|private|internal|protected”
Property = ”property-name”
Kind = ”relationship-kind”
Target = ”target-table-name”
UpdateRule = ”database-on-update-rule”
DeleteRule = ”database-on-delete-rule”>
<Column Name = “key-column-names” />
...
</Association>
...
<Type /> inheritance sub-types
...
</Type>
</Table>
...
<StoredProcedure
Name = ”…”
Hidden = ”true|false”
Access = ”public|private|internal|protected”
MethodName = “…” >
<Parameter
ParameterName = “parameter-name”
Type = “type-name”
DBType = “database-type”
ParameterDirection = “Input|Output|InputOutput|ReturnValue” />
...
<ResultShape
Name = ”…”
Hidden = ”true|false”
Access = ”public|private|internal|protected”
Class = “type-name” >
<Column /> same as previous
...
</ResultShape>
...
</StoredProcedure>
...
<Function
Name = ”…”
Hidden = ”true|false”
Access = ”public|private|internal|protected”
MethodName = “…”
IsTableValued = “true|false”
Type = “return-type”
DBType = “database-return-type”>
<Parameter
ParameterName = parameter-name”
Type = “type-name”
DBType = “database-type”
ParameterDirection = “Input|Output|InputOutput|ReturnValue” />
...
<ResultShape
Name = ”…”
Hidden = ”true|false”
Access = ”public|private|internal|protected”
Class = “type-name” >
<Column /> same as previous
...
</ResultShape>
...
</Function>
...
</Schema>
...
</Database>
XML文件中的元素和属性描述如下:
6.4.1 XML元素基本属性
下面是所有元素的共有属性:
属性名称 |
描述 |
Name |
与数据库元数据对应的列名称。每个元素都需要有这个属性。 |
Hidden |
标识自动生成工具是否使用这个元素生成目标代码。生成的实体类不包含隐藏的列或关系。设置容器(schemas/tables/etc)的Hidden属性为true,则容器中的所有内容不生成目标代码。设置隐藏的属性的目的就是保留所有的数据库架构,无论它们是否用于生成目标代码。因而,数据库架构的后续变化可以被很好的合并到现有的XML文件中。 |
Access |
标识生成的目标代码的可访问属性。如果没有被指定,默认访问属性为‘public’。 |
6.4.2 Database元素
Database元素描述了一个数据库,它包含了一个或者多个Schema元素。
属性名称 |
描述 |
Class |
指定生成的强类型上DataContext类的名称。 |
6.4.3 Schema元素
Schema元素描述了一个数据库Schema。它包含一个或者多个Table元素。
属性名称 |
描述 |
Class |
指定生成的强类型Schema类的名称。Schema类继承了强类型的Context类,是一个包含多个Table元素的List。名称为dbo的Schema需要被代码生成工具进行特殊处理。组成dbo Schema的所有表格在生成的Context实体中都为Top-Level的表集合。 |
Property |
Context类或父Schema类的属性名称。 |
6.4.4 Table元素
Table元素描述了一个数据库表格。它最多包含一个PrimaryKey元素,0个或者多个Unique元素,0个或者多个Index元素,一个或者多个Column元素以及0个或者多个Association元素。
属性名称 |
描述 |
Class |
生成的实体类名称。 |
Property |
Context类或父Schema类中的表格集合的属性名称 |
6.4.5 PrimaryKey元素
PrimaryKey描述数据库中的主键。它包含组成数据库表格主键的一个或者多个键值列元素。
6.4.6 Unique元素
Unique元素描述了数据库中的唯一性约束。它包含组成数据库表格唯一性约束的一个或者多个键值列元素。
6.4.7 Index 元素
Index元素描述了数据库中的索引定义。索引通常为主键和唯一约束的列集合。SQL server会给这些列自动添加索引定义。尽管如此,我们还可能需要添加自定义索引以及其他形式的隐含索引,所以XML元数据中也包含了这些索引的定义。Index元素包含一个或者多个定义索引的键值列元素。
属性名称 |
描述 |
Style |
指定数据库索引定义类型。对于SQL Server,可以指定为CLUSTERED 或NONCLUSTERED |
IsUnique |
IsUnique属性指定索引定义是否基于唯一约束的值。对于唯一性约束,这可能是重复的。但是,到目前为止SQL都是将这些值分开处理的。 |
6.4.8 Type元素
Type元素描述了数据库表格行对应的CLR类型。
属性名称 |
描述 |
InheritanceCode |
如果存在继承,这个类型标识值表示表格行到特定的CLR类型的映射。 |
IsInheritanceDefault |
在类型标识值没有被指定的情况下,如果这个值为True,则这个类型就为数据库行映射默认的CLR类型。除非没有任何继承关系存在,否则继承体系中的Type的IsInheritanceDefault属性都必须设置为True。 |
6.4.9 Column元素
Column描述了一个数据库列。
Attribute Name |
Description |
Type |
列的CLR类型,即实体类的属性类型。这个属性是必须的。 |
Property |
生成的实体类属性的名称。如果这个属性没有被指定,那么实体类属性的名称同Name属性的值。 |
DbType |
列的数据库类型。如果没有指定,根据CLR类型来推断。 |
IsIdentity |
标识这个列是否为主键的一部分。如果指定了PrimaryKey,这个属性是重复的,可能被移除。 |
IsAutoGen |
描述这个列是否是服务器产生的标识。 |
IsVersion |
描述这个列是否是服务器产生的版本标识或者时间戳。 |
IsReadOnly |
描述这个列是否是只读的,产生的属性是否可以被设置。到目前为止,自动生成的标识和版本时间戳都是只读的,即便这个属性值没有被显示地指定为“true”。 |
UpdateCheck |
指定DLinq的UpdateCheck值 publicenumUpdateCheck { |
6.4.10 Association元素
Association元素描述了表格之间的基于值的关联。SQLMetal 根据数据库中的外键自动生成Association元素。Association元素包含一个或者多个键值列元素。
属性名称 |
描述 |
Property |
实体类的关联属性的名称。如果没有指定,关联属性的名称同Name属性值。 |
Kind |
描述关联类型以及关联的一端。目前这个值没有被直接映射成属性的形式。这个属性的值决定生成的关联成员的类型为EntitySet 或EntityRef。这个属性是必须的。 publicenumRelationshipKind { |
Target |
描述目标表的名称。如果这个连接不是代表儿子端(包含外键的关联端),那么目标表必须包含一个相对应的关联的定义,且关联的名称一样。这个属性是必须的。 |
UpdateRule |
当数据库表格行被修改时,控制数据库的更新规则。目前这个属性仅用于持久化和重新生成 SQL 元数据。 |
DeleteRule |
当数据库表格行被删除时,控制数据库的更新规则。目前这个属性仅用于持久化和重新生成 SQL 元数据。 |
6.4.11 StoredProcedure元素
StoredProcedure描述了数据库存储过程。
属性名称 |
描述 |
MethodName |
DataContext 或Schema类定义的映射为数据库存储过程的函数名称 |
6.4.12 Function元素
Function描述了数据库自定义函数。
属性名称 |
描述 |
MethodName |
DataContext 或Schema类定义的映射为数据库自定义函数的方法名称 |
IsTableValued |
如果这个值为True,则表示用户自定义函数返回的是行集合,而不是标量值。 |
Type |
函数返回的标量值类型。如果这个函数返回的是行集合,则不需要指定这个属性。 |
DBType |
函数返回的数据库类型。如果这个函数返回的是行集合,则不需要指定这个属性。 |
6.4.13 Parameter属性
Parameter描述了数据库存储过程或者自定义函数的参数。
属性名称 |
描述 |
ParameterName |
DataContext 或Schema中定义的方法的参数名称 |
Type |
这个参数的CLR类型 |
DBType |
这个参数的数据库类型 |
ParameterDirection |
参数方向。 public enum ParameterDirection { |
1.4.14 ResultShape元素
ResultShape描述了返回值为行集合的函数或存储过程的返回结果的形式。
属性名称 |
描述 |
Class |
CLR类的名称 |