在.net 3.5推出之后,微软的数据库访问技术出现了两个ORM映射。LINQ to SQL,主要针对SQL Server数据库,和ADO.net Entity Framework,支持多种数据库。很多时候,我们要考虑数据库平台的可移植性,即现在程序可能运行在SQL Server上,但根据需要,也许有一天,我们要将它一直到Oracle上,在考虑到这种问题的时候,我们首选ADO.net Entity Framework。当然选择LINQ to SQL,还是ADO.net Entity Framework的依据不止这一个:
LINQ to SQL针对SQL Server用户,期望能有一个快速开发、简单的ORM技术的群体。开发的系统多半是中小型应用程序,采用轻量化开发模式,不需要繁复的开发过程。还有一种情况:如果开发的系统中,不需要将同一个Entity Class对应到多个表,也就是说程序中写入一条数据,而该数据不会写入一个以上的表,那么可以选择LINQ to SQL
ADO.net Entity Framework则针对使用SQL Server开发大型应用程序的用户,需要一整套数据库软件来管理。以及非SQL Server数据库的用户。在开发的系统中,如果需要将同一个Entity Class对应至多个表,那么ADO.net Entity Framework是目前唯一的选择。
LINQ to SQL 有着自身的优势,由于LINQ是一个轻量化的ORM产品,性能在其设计之初是考虑重点之一,所以LINQ to SQL的性能是很让人满意的,而和其相对的ADO.net Entity Framework由于架构的复杂,在性能方面就会略逊一筹。
在LINQ to SQL中,通过Visual Studio2008自带的设计器,IDE会根据用户添加的数据表,自动生成对应的数据库类和对应表的类。应用程序将DataContext(与一个实体数据库对应)中的Table<Entity class>(与实体表的一行对应)作为数据源,通过LINQ Expression进行访问,LINQ to SQL Provider会利用内建的Query Convert机制,将LINQ Expression转换为SQL指令封装于回传的DataQuery对象中,当第一次对DataQuery对象调用MoveNext是,这个SQL指令会经由ADO.net送到数据库执行后取回回传的结果,并将结果集中的元素一一转为Entity Object.
在之前已经介绍了LINQ(包括LINQ to Entity),由于LINQ to SQL使用LINQ Expression,所以LINQ to SQL的操作和LINQ to Entity的操作相似,不同的是两者的数据源分别为数据库和对象集合。除此之外,LINQ to SQL 和 LINQ to Entity还有些地方不同:
受Query Convert机制(将LINQ Expression转换为SQL指令)的限制,原本可以使用的Extension Method或是自定义函数不能运用于LINQ to SQL中,如:
程序 4-1
static void Main(string[] args)
{
NorthwindDataContext context = new NorthwindDataContext();
var result1 = from s in context.Customers
where Check(s)
select s;
foreach (var item in result1)
{
Console.WriteLine(item.CustomerID);
}
Console.ReadLine();
}
static bool Check(Customer item)
{
return true;
}
程序4-1虽然能编译通过,但在执行时将会引发NotSupportException异常,这是因为运行是Query Convert不知道如何将Check转换为SQL指令的缘故,要让程序能正常运行,需要先调用ToArray将数据库中数据转化为对象进行操作。
程序4-2
static void Main(string[] args)
{
NorthwindDataContext context = new NorthwindDataContext();
var result1 = from s in
(from s in context.Customers select s).ToList()
where Check(s)
select s;
foreach (var item in result1)
{
Console.WriteLine(item.CustomerID);
}
Console.ReadLine();
}
下面,我主要介绍一些比较容易忽视的问题:
1、连接模式
由于LINQ to SQL底层采用ADO.net实现,所以,LINQ to SQL的查询有两种模式。连线模式和离线模式。在查询过程中,LINQ to SQL默认采用连接模式。当调用ToList(), ToArray()等的时候,程序会自动切换到离线模式。但下面的示例看起来是应该有错误的,实际上却可以正确执行,为什么呢?
程序4-3
NorthwindDataContext context = new NorthwindDataContext();
var result1 = from s in context.Customers
select s;
var result2 = from s in context.Orders
select s;
foreach (var item in result1)
{
foreach (var item2 in result2)
{
break;
}
}
我们知道,两个查询使用的是同一个DataContext,也是相同的Connection。如果采用连线模式,当第一个查询连接时,第二个查询执行将会抛出异常,而这里却可以正确执行为什么?
原来在LINQ to SQL中有一种Connection管理机制。在DataContext的管理机制中,每一个foreach都是一个User,当一个User正在执行时,另一个User(嵌套的foreach)尝试执行,就会激发Buffer机制。Buffer机制激活时,第一个User所关联的DataReader会将所有数据放入Buffer中,此时Buffer中就有了一个DataTable,然后DataReader被关闭,对应的User被移除。此时第二个User再执行连接,就不会出错了。
Buffer机制只对同一个DataContext有效,对于不同的DataContext,不会激活Buffer机制。所以我们建议尽量通过ToArray等方法将连接模式转换为离线模式进行操作。
当LINQ to SQL执行查询时,由于默认采用连线模式,所以,当回传程序集的Enumerator调用MoveNext时,连接打开,直至Enumerator执行结束,连接才会关闭。以foreach为例,当foreach取出集合的第一个元素时,连接打开,当foreach执行结束时,连接关闭。
2、 查询执行方式
程序4-4
static void Query()
{
NorthwindDataContext context = new NorthwindDataContext();
var first = FirstQuery(context);
var result = SecondQuery(context, first);
foreach ( var item in result)
{
Console.WriteLine(item.CustomerID);
}
}
static IEnumerable<Customer> FirstQuery(NorthwindDataContext context)
{
IEnumerable<Customer> result = from s in context.Customers
select s;
return result;
}
static IEnumerable<Customer> SecondQuery(NorthwindDataContext context,
IEnumerable<Customer> first);
{
var result = from s in first
where s.CustomerID == "VINET";
return result;
}
当调用Query()方法进行断点调试时,可以看到,程序在执行FirstQuery时,会将LINQ Expression转化为标准SQL语句,而当程序执行SecondQuery时,却没有看到SQL语句,为什么呢?
原因很简单,当SecondQuery执行时,参数first的类型为IEnumerable,程序执行到where处时,会调用IEnumerable的Where方法,这是程序是将IEnumerable对象作为数据源,所以编译器将LINQ Expression转化成LINQ to Objects进行调用了。
如果将程序改成:
程序4-5
static IQueryable<Customer> FirstQuery(NorthwindDataContext context)
{
IQueryable<Customer> result = from s in context.Customers
select s;
return result;
}
static IQueryable<Customer> SecondQuery(NorthwindDataContext context,
IQueryable<Customer> first);
{
var result = from s in first
where s.CustomerID == "VINET";
return result;
}
此时的情况就不同了。这样在调试时,就可以看到两个方法中LINQ Expression都转成了SQL语句。
也就是说,当要将LINQ的回传值进行回传,或者作为参数传递给另一个函数时,类型的选择会直接影响编译器的Extension Method的调用,如果不注意,可能会造成难以察觉的错误。
3、Translate 由DataReader加载Entity Objects
程序4-6
using (SqlCommand cmd = new SqlCommand("SELECT * FROM Customers", conn))
{
conn.Open();
using (SqlDataReader reader = cmd.ExecuteReader(System.Data.CommandBehavior,CloseConnection))
{
var result = context.Translate<Customers>(reader);
foreach (var item in result)
{
Console.WriteLine(item.CustomerID);
}
}
}
4、Deferring Load
当一次性的将Order和OrderDetail(两个之间存在Association)中的行取回,当访问Order时,它的明细信息并没有加载,只有在访问OrderDetail的属性时,明细才会被加载,这时每访问一个OrderDetail属性,程序就会发送一次SQL语句到数据库,这种方式成为Deffering Load。
这种方式好不好呢?这样根据情况来定,如果数据很多的话,这样可以很大的节省内存空间,而如果数据很少,这种方式的效率就相对较低了。所以要根据实际情况进行选择。
如何关闭Deferring Load功能:
程序4-7
NorthwindDataContext context = new NorthwindDataContext();
context.DeferredLoadingEnable = false;
System.Data.Linq.DataLoadOptions loadOptions =
new System.Data.Linq.DataLoadOptions();
loadOptions.LoadWith<Orders>(o => o.Order_Details);
context.LoadOptions = loadOptions;
var result = from s1 in context.Orders
where s1.OrderDate > DateTime.Parse("2009/03/04")
select s1;
先设置DataContext的DeferredLoadingEnable属性为false,关闭DeferringLoad功能
然后创建DataLoadOptions对象,通过调用此对象的LoadWith或AssociateWith来指定哪些Entity Object要一次性加载那一个关联相关的Entity Object。
注意:1.不能在指定为DataContext的LoadOptions属性后再调用LoadWith函数:
如context.LoadOptions.LoadWith<Orders>(o => o.Order_Details); 是错误的
2.除了LoadWith函数外,之前也提过LoadOptions还有另外一个方法AssociateWith,此方法可以指定关联Entity Object时的过滤,可以和LoadWith搭配使用:
loadOptions.LoadWith<Orders>(o => o.Order_Details);
loadOptions.AssociateWith<Orders>(o => o.Order_Details.Where( p => p.productID == 60);
context.LoadOptions = loadOptions;
3.使用LoadWith时,不能出现循环的情况,如
loadOptions.LoadWith<Orders>( o => o.Order_Details);
loadOptions.LoadWith<Order_Details>(o => o.Orders); //这种做法是错误的
5.继承
Entity Oject 继承是ORM不可或缺的技术之一。具体的解释如下:
有三个类,People、Male、Female,数据库中有一张Persons表中有Name,Sex、Age字段,这三个类可以都和Persons表关联,以Sex属性进行区分,当sex = 1时,对应的实体为male,sex=2时,对应实体为female,sex = 0 则为people。这时,我们可以设置Male和Female继承自People,而People有Name、Sex和Age属性。其中Sex属性值就被成为“Discriminator Property”,而区分实体为People的Sex属性值成为“Basic class DiscriminatorProperty Value”,而区分实体为Male和Female的Sex属性值成为“Derived Class DiscriminatorProperty Value”。当实体的Sex属性值既不属于Male,也不属于Female和People时,那么该属性值就视“Inheritance Default”属性值所设置的实体而定。
.net 3.5 数据库开发 之 ADO.net Entity Framework