XSLT存档  

不及格的程序员-八神

 查看分类:  ASP.NET XML/XSLT JavaScripT   我的MSN空间Blog

LINQ之路 9:LINQ to SQL 和 Entity Framework(上)

 

在上一篇中,我们从理论和概念上详细的了解了LINQ的第二种架构“解释查询”。在这接下来的二个篇章中,我们将使用LINQ to SQL和Entity Framework来实践“解释查询”,学习这些技术的关键特性。在本系列文章中,我不准备事无巨细的讨论LINQ to SQL和Entity Framework的方方面面,毕竟那样需要太多的篇幅,也会让我们从LINQ上面转移注意力,况且,园子里也有不少介绍LINQ to SQL和Entity Framework的好文章。我们在此关注的是LINQ to SQL和Entity Framework中的”LINQ”部分,并会比较这两种技术的相同和不同之处。通过我们之前介绍的LINQ知识还有将来会讨论的更多LINQ Operators,相信阅者能针对LINQ to SQL和Entity Framework写出优雅高效的查询。为了简单清晰,文中有些地方对LINQ to SQL和Entity Framework进行了缩写,分别为:L2S和EF。

LINQ to SQL和Entity Framework之关联

LINQ to SQL和Entity Framework都是一种包含LINQ功能的对象关系映射技术。他们之间的本质区别在于EF对数据库架构和我们查询的类型实行了更好的解耦。使用EF,我们查询的对象不再是完全对应数据库架构的C#类,而是更高层的抽象:Entity Data Model。这为我们提供了额外的灵活性,但是在性能和简单性上面也会有所损失。

LINQ to SQL由C#团队开发并在.NET Framework 3.5中发布,而Entity Framework由ADO.NET团队开发并作为.NET Framework 3.5 Service Pack 1的一部分发布。此后,LINQ to SQL由ADO.NET团队接手,其结果是:在.NET 4.0中,ADO.NET团队更加专注于EF的改进,相对来说,LINQ to SQL的改进要小得多。

LINQ to SQL和Entity Framework各有所长,LINQ to SQL是一个轻量级的ORM框架,旨在为Microsoft SQL Server数据库提供快速的应用程序开发,其优点是易于使用、简单、高性能。而Entity Framework的优点在于:其为创建数据库架构和实体类之间的映射提供了更好的灵活性,它还通过提供程序支持除了SQL Server之外的第三方数据库。

EF 4.0一个非常受欢迎的改进是它现在支持与LINQ to SQL几乎同样的查询功能。这意味着我们在系列文章中的LINQ-to-db查询可以同时适用于EF 4.0和L2S。而且,这也使得L2S成为我们学习使用LINQ查询数据库的理想技术,因为其保持了对象关系方面的简单性,并且我们学习到的查询原则和技术同样适用于EF。

LINQ to SQL实体类

L2S 允许我们使用任何类来表示数据,只要我们为类添加了合适的Attribute(特性)装饰,比如:

复制代码
    [Table]
public class Customer
{
[Column(IsPrimaryKey = true)]
public int ID;

[Column]
public string Name;
}
复制代码

[Table] 特性定义在System.Data.Linq.Mapping名字空间中,它告诉L2S该类型的对象代表了数据库表里的一行数据。默认情况下,它假设表名和类名相同,当他们不同时,我们就可以指定具体的表名,如下:

    [Table (Name="Customers")]

L2S把这种经过[Table]特性装饰的类成为实体类。一个实体类的结构必须匹配它表示的数据库表,这样才能生成可以正确执行的SQL脚本。

[Column] 特性指定一个字段或属性映射到数据库表的一列,如果列名与字段名/属性名不相同,我们可以指定具体的映射列名:

        [Column(Name = "FullName")]
public string Name;

我们可以在[Column]特性中指定IsPrimaryKey属性表示该列为表的主键,这对于保持对象标识、往数据库写入更新是必须的。

除了直接定义public字段,我们也可以定义private字段和public属性,这样我们就能在属性存取时加入验证逻辑。此时,为了性能考虑,我们可以告诉L2S当从数据库存取数据时,绕过属性存取器而直接将值写入private字段。当然,前提是我们认为数据库中的值是正确的,不需要经过属性存取器中的验证逻辑。

复制代码
        private string name = string.Empty;

// Column(Storage = "name") 告诉L2S当从数据库生成实体时直接将数据写入name字段,而不通过set访问器
[Column(Storage = "name")]
public string Name
{
get { return name; }
set { if(value.Length > 5) name = value; }
}
复制代码

可以看到,在使用LINQ to SQL时,我们首先要参照数据库的结构来创建各种必须的实体类,这当然不是一种令人愉快的事情。好在,我们可以通过Visual Studio(新增一个”LINQ to SQL Classes” Item)或SqlMetal命令行工具来自动生成实体类。

Entity Framework实体类

和LINQ to SQL一样,Entity Framework允许我们使用任何类来表示数据(尽管我们必须实现特定的接口来完成诸如导航属性等功能)。比如,下面的EF实体类表示一个customer,它被映射到数据库的customer表:

复制代码
    [EdmEntityType (NamespaceName="EFModel", Name="Customer")]
public partial class Customer
{
[EdmScalarProperty(EntityKeyProperty = true, IsNullable= false )]
public int ID { get; set; }

[EdmScalarProperty(EntityKeyProperty = false, IsNullable = false)]
public string Name { get; set; }
}
复制代码

但和L2S不同的是,这样的一个类并不能独立工作。记住:在使用EF时,我们并不是直接查询数据库,而是查询一个更高层的模型,该模型叫做Entity Data Model(EDM)。所以我们需要某种方法来描述EDM,这通常是由一个以.edmx为扩展名的XML文件来完成的,它包含了一下三个部分:

  • 概念模型,用来描述EDM并且和数据库完全隔离
  • 存储模型,用来描述数据库架构
  • 映射规范,用来描述概念模型如何映射到存储模型

创建一个.edmx文件的最简单方法是在Visual Studio中添加一个”ADO.NET Entity Data Model” 项目,然后按照向导提示来从数据库生成EDM。这种方法不但生成了.edmx文件,还为我们生成了实体类,EF中的实体类对应EDM的概念模型。

设计器为我们生成的EDM初始时包含了表和实体类之间简单的1:1映射关系,当然,我们可以通过设计器或编辑.edmx文件来调整我们的EDM。下面就是我们可以完成的一些工作:

  • 映射多个表到一个实体
  • 映射一个表到多个实体
  • 通过ORM领域流行的三种标准策略来映射继承的类型

这三种继承策略包括:

  • 表到层次类型(Table per hierarchy):单个表映射到一个完整的类继承层次结构。表中的一个类型辨别列用来指示每一行数据应该映射到何种类型。
  • 表到类型(Table per type):单个表映射到单个类型,这意味着继承类型会被映射到多个表。当我们查询一个entity时,EF通过生成SQL JOIN来合并所有的基类型。
  • 表到具体类型(Table per concrete type):单独的表映射到每个具体类型,这意味着一个基类型映射到多个表,当我们查询基类型的entity时,EF会生成SQL UNION来合并数据。

DataContext和ObjectContext

一旦我们定义好了实体类(EF还需定义EDM),我们就可以开始使用LINQ进行查询了。第一步就是通过制定连接字符串来初始化一个DataContext(L2S)或ObjectContext(EF)。

            var l2sContext = new DataContext("database connection string");
var efContext = new ObjectContext("entity connection string");

需要了解的是,直接初始化DataContext/ObjectContext是一种底层的访问方式,但它很好的说明了这些类的工作方式。通常情况下,我们会使用类型化的context(通过继承DataContext/ObjectContext),详细情况稍后就会讨论。

对于L2S,我们传入数据库连接字符串;而对于EF,我们需要传入实体(entity)连接字符串,它同时包含了数据库连接字符串和查找EDM的额外信息。如果你通过Visual Studio创建了EDM,你会在app.config文件中找到针对该EDM的实体连接字符串,比如:

  <connectionStrings>
<add name="testEntities" connectionString="metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=localhost;initial catalog=test;integrated security=True;multipleactiveresultsets=True;App=EntityFramework&quot;" providerName="System.Data.EntityClient"/>
</connectionStrings>

之后我们就可以通过调用GetTable(L2S)或CreateObjectSet(EF)来获得查询对象。下面的示例使用了我们上面创建的Customer实体类:

            var context = new DataContext("Data Source=LUIS-MSFT; Initial Catalog=test; Integrated Security=SSPI;");
Table<Customer> customers = context.GetTable<Customer>();

Console.WriteLine(customers.Count()); // 表中的行数
Customer cust = customers.Single(c => c.ID == 1); // 获取ID为1的Customer

下面是EF中的等价代码,可以看到,除了Context的构建和查询对象的获取有所不同,后面的LINQ查询都是一样的:

复制代码
            var context = new ObjectContext(ConfigurationManager.ConnectionStrings["testEntities"].ConnectionString);
context.DefaultContainerName = "testEntities";
ObjectSet<Customer> customers = context.CreateObjectSet<Customer>();

Console.WriteLine(customers.Count()); // 表中的行数
Customer cust = customers.Single(c => c.ID == 1); // 获取ID为1的Customer
复制代码

一个DataContext/ObjectContext对象有两个作用。其一是工厂作用,我们通过它来创建查询对象,另外,它会跟踪我们对entity所做的任何修改,所以我们可以把修改结果保存到数据库。下面的代码就是更新customer的示例:

复制代码
            // Update Customer with L2S
Customer cust = customers.OrderBy(c => c.Name).First();
cust.Name = "Updated Name";
context.SubmitChanges();

// Update Customer with EF, Calling SaveChanges instead
Customer cust = customers.OrderBy(c => c.Name).First();
cust.Name = "Updated Name";
context.SaveChanges();
复制代码

 

强类型contexts

任何时候都去调用GetTable<>()或CreateObjectSet<>()并不是一件让人愉快的事情,一个更好的方式是为特定的数据库创建DataContext/ObjectContext的子类,在子类中为各个entity添加属性,这就是强类型的context,如下:

复制代码
    class LifePoemContext : DataContext
    {
public LifePoemContext(string connectionString) : base(connectionString) { }

public Table<Customer> Customers
{
get { return GetTable<Customer>(); }
}

//... 为其他table创建相应的属性
}

// Same thing for EF
class LifePoemContext : ObjectContext
    {
public LifePoemContext(EntityConnection connection) : base(connection) { }

public ObjectSet<Customer> Customers
{
get { return CreateObjectSet<Customer>(); }
}

//... 为其他table创建相应的属性
}
复制代码

之后,我们就可以通过使用属性来写出更加简洁优雅的代码了:

            var context = new LifePoemContext("database connection string");
Console.WriteLine(context.Customers.Count());

如果你是使用Visual Studio来创建”LINQ to SQL Classes”或”ADO.NET Entity Data Model”,它会自动为我们生成强类型的context。设计器同时还会完成其他的工作,比如对标识符使用复数形式,在我们的例子中,它是context.Customers而不是context.Customer,即使SQL表名和实体类都叫Customer。

对象跟踪/Object tracking

一个DataContext/ObjectContext实例会跟踪它创建的所有实体,所以当你重复请求表中相同的行时,它可以给你返回之前已经创建的实体。换句话说,一个context在它的生存期内不会为同一行数据生成两个实例。你可以在L2S中通过设置DataContext对象的ObjectTrackingEnabled属性为false来取消这种行为。在EF中,你可以基于每一种类型进行设置,如:context.Customers.MergeOption = MergeOption.NoTracking; 需要注意的是,禁用Object tracking同时也会阻止你想数据库提交更新。

为了说明Object tracking,假设一个Customer的名字按字母排序排在首位,同时它的ID也是最小的。那么,下面的代码,a和b将会指向同一个对象:

            var context = new testEntities(ConfigurationManager.ConnectionStrings["testEntities"].ConnectionString);
Customer a = context.Customers.OrderBy(c => c.Name).First();
Customer b = context.Customers.OrderBy(c => c.ID).First();
Console.WriteLine(object.ReferenceEquals(a, b)); // output: True

这会导致几个有意思的结果。首先,让我们考虑当L2S或EF在遇到第二个query时到底会发生什么。它从查询数据库开始,然后获取ID最小的那一行数据,接着就会从该行读取主键值并在context的实体缓存中查找该主键。如果找到,它会直接返回缓存中的实体而不更新任何值。所以,如果在这之前其他用户更新了该Customer的Name,新的Name也会被忽略。这对于防止意外的副作用和保持一致性至关重要,毕竟,如果你更新了Customer对象但是还没有调用SubmitChanges/SaveChanges,你是不会希望你的更新会被另外一个查询覆盖的吧。

第二个结果是在你不能明确把结果转换到一个实体类形,因为在你只选择一行数据的部分列时会引起不必要的麻烦。例如,如果你只想获取Customer的Name时:

复制代码
            // 下面任何一种方法都是可行的
context.Customers.Select(c => c.Name);
context.Customers.Select(c => new { Name = c.Name } );
context.Customers.Select(c => new MyCustomerType { Name = c.Name } );

// 但下面这种方法会引起麻烦
context.Customers.Select(c => new Customer { Name = c.Name });
复制代码

原因在于Customer实体只是部分属性被获取,这样下一次如果你查询Customer的所有列时,可是context从缓存中返回的的对象只有部分属性被赋值。

关联/Associations

实体生成工具还为我们完成了一项非常有用的工作。对于我们定义在数据库中的每个关联(relationship),它会在关联的两边添加恰当的属性,让我们可以使用关联来进行查询。比如,假设Customer和Order表存在一对多的关系:

复制代码
      Create table Customer
(
ID int not null primary key,
Name varchar(30) not null
)

Create table Orders
(
ID int not null primary key,
CustomerID int references Customer (ID),
OrderDate datetime,
Price decimal not null
)
复制代码

通过自动生成的实体类形,我们可以写出如下的查询:

复制代码
            //获取第一个Custoemr的所有Orders
Customer cust1 = context.Customers.OrderBy(c => c.Name).First();
foreach (Order o in cust1.Orders)
Console.WriteLine(o.Price);

//获取订单额最小的那个Customer
Order lowest = context.Orders.OrderBy(o => o.Price).First();
Customer cust2 = lowest.Customer;
复制代码

并且,如果cust1和cust2正好是同一个Customer时,他们会指向同一对象:cust1 == cust2会返回true。

让我们来进一步查看Customer实体类中自动生成的Orders属性的签名:

复制代码
        // With L2S
[Association(Name="Customer_Order", Storage="_Orders", ThisKey="ID", OtherKey="CustomerID")]
public EntitySet<Order> Orders { get {...} set {...} }

// With EF
[EdmRelationshipNavigationProperty("testModel", "FK__Orders__Customer__45BE5BA9", "Orders")]
public EntityCollection<Order> Orders { get {...} set {...} }
复制代码

一个EntitySet或EntityCollection就如同一个预先定义的query,通过内置的Where来获取相关的entities。[Association]特性给予L2S必要的信息来构建这个SQL query;[EdmRelationshipNavigationProperty]特性告知EF到EDM的何处去查找当前关联的信息。

和其他类型的query一样,这里也会采用延迟执行。对于L2S,一个EntitySet会在你对其进行枚举时生成;而对于EF,一个EntityCollection会在你精确调用其Load方法时生成。

下面是Orders.Customer属性(位于关联的另一边):

        // With L2S
[Association(Name="Customer_Order", Storage="_Customer", ThisKey="CustomerID", OtherKey="ID", IsForeignKey=true)]
public Customer Customer { get {...} set {...} }

尽管属性类型是Customer,但它底层的字段(_Customer)却是EntityRef类型的:private EntityRef<Customer> _Customer;  EntityRef实现了延迟装载(deferred loading),所以直到你真正使用它时Customer才会从数据库中获取出来。

EF以相同的方式工作,不同的是你必需调用EntityReference对象的Load方法来装载Customer属性,这意味着EF必须同时公开真正的Customer对象和它的EntityReference包装者,如下:

        // With EF
[EdmRelationshipNavigationProperty("testModel", "FK__Orders__Customer__45BE5BA9", "Customer")]
public Customer Customer { get {...} set {...} }

public EntityReference<Customer> CustomerReference

我们也可以让EF按照L2S的方式来工作,当我们设置如下属性后,EFEntityCollectionsEntityReference就会自动实现延迟装载,而不需要明确调用其Load方法。

        context.ContextOptions.LazyLoadingEnabled = true;

在下一篇LINQ to SQL和Entity Framework(下)中,我们会讨论学习这两种LINQ-to-db技术的更多细节和值得关注的地方。

在本篇中,我们将接着上一篇“LINQ to SQL 和 Entity Framework(上)”的内容,继续使用LINQ to SQL和Entity Framework来实践“解释查询”,学习这些技术的关键特性。我们在此关注的是LINQ to SQL和Entity Framework中的”LINQ”部分,并会比较这两种技术的相同和不同之处。通过我们之前介绍的LINQ知识还有将来会讨论的更多LINQ Operators,相信阅者能针对LINQ to SQL和Entity Framework写出优雅高效的查询。为了简单清晰,文中有些地方对LINQ to SQL和Entity Framework进行了缩写,分别为:L2S和EF。

LINQ to SQL和Entity Framework的延迟执行

和本地查询一样,L2S和EF查询也是延迟执行的,这样就允许我们渐进地创建LINQ查询。但是,有一个方面,L2S和EF有自己特殊的延迟执行语义,这就是当一个子查询出现在Select表达式中时:

  • 对于本地查询,你获得了两个延迟执行,因为从功能角度来看,你选择了包含多个查询的一个sequence。若以当你遍历外层结果sequence时,并不会遍历内部子查询,所以子查询此时也就不会执行。
  • 而对于L2S/EF,子查询和外层的主查询在同一时间被执行,这样就避免了过度的连接远程数据库导致性能问题。

比如,对于L2S/EF,下面的查询在第一个foreach语句时执行,且只执行一次:

复制代码
            var context = new LifePoemContext("database connection string");

var query = from c in context.Customers
select
from o in context.Orders
select new { c.Name, o.Price };

foreach (var customerOrders in query)
foreach (var namePrice in customerOrders)
Console.WriteLine(namePrice.Name + " spent " + namePrice.Price);
复制代码

换句话说,我们在select表达式中明确指定的EntitySets/EntityCollections总是在一次执行中就能被获取到:

复制代码
            var context = new LifePoemContext("database connection string");
var query = from c in context.Customers
select new { c.Name, c.Orders };

foreach (var row in query)
foreach (var order in row.Orders) // 没有额外的连接查询
Console.WriteLine(row.Name + " spent " + order.Price);
复制代码

但如果我们没有事先进行数据转换,就对EntitySet/EntityCollection属性进行遍历的话,就会适用于延迟执行。下面的示例中,L2S和EF在每一次循环中都会执行另外的Orders查询:

            context.ContextOptions.DeferredLoadingEnabled = true;   // 仅EF需要此句

foreach (Customer c in context.Customers)
foreach (Order o in c.Orders) // 每次都会开始一个新的SQL查询
Console.WriteLine(c.Name + " spent " + o.Price);

这种模式在我们需要有条件的执行内部查询时具有优势,比如我们可能需要依靠客户端来做某个条件测试时:

            foreach (Customer c in context.Customers)
if(myWebService.HasBadCreditHistory(c.ID))
foreach (Order o in c.Orders) // 开始一个新的SQL查询
Console.WriteLine(c.Name + " spent " + o.Price);

上面我们看到了如何对关联属性进行显示的数据转换(select)来避免重复执行。稍后我们就会看到,L2S和EF还提供了其他的机制来实现这个功能。

 

DataLoadOptions

DataLoadOptions类是L2S的特性,它有两个特殊的作用:

  • AssociateWith让你能够事先对EntitySet关联设置过滤条件
  • LoadWith让你能够设置某些EntitySets为主动加载(eager loading),从而减少连接数据库的次数

设置过滤条件

假如我们只关心那些Price大于1000的Orders,我们就可以通过DataLoadOptions来设置过滤条件:

复制代码
            var context = new LifePoemContext("database connection string");

DataLoadOptions options = new DataLoadOptions();
options.AssociateWith<Customer>(c => c.Orders.Where(order => order.Price > 1000));
context.LoadOptions = options;

foreach (Customer c in context.Customers)
if (myWebService.HasBadCreditHistory(c.ID))
ProcessCustomer(c); // 如果在该方法中引用c.Orders,只有那些Price > 1000的Orders被返回
复制代码

这会指示我们的DataContext实例总是使用给定的条件对Customer的Orders进行过滤。需要注意的是,AssociateWith并不会改变延迟执行的语义,它只是命令对特定的关系进行隐式的过滤。

主动加载(Eager Loading)

DataLoadOptions的第二个作用是请求让某个EntitySets跟随父EntitySets一起加载。比如,假设你想在加载所有Customers的同时一起加载他们的Orders,而不是对每一个Customer分别查询一次Orders:

复制代码
            var context = new LifePoemContext("database connection string");

DataLoadOptions options = new DataLoadOptions();
options.LoadWith<Customer>(c => c.Orders);
context.LoadOptions = options;

foreach (Customer c in context.Customers) // 一次查询
foreach (Order o in c.Orders) // 因为上面的DataLoadOptions,所有的Orders都在上面的查询中被同时Load了
Console.WriteLine(c.Name + " bought a " + o.Description);
复制代码

这会指示DataContext,不论何时,只要一个Customer被获取,它的Orders也会在同一时间被加载。我们可以组合LoadWith和AssociateWith方法,这样既能获得主动加载,还能对加载的EntitySets进行过滤,比如:

            DataLoadOptions options = new DataLoadOptions();
options.LoadWith<Customer>(c => c.Orders);
options.AssociateWith<Customer>(c => c.Orders.Where(order => order.Price > 1000));

Entity Framework中的主动加载(Eager Loading)

在Entity Framework中,如果我们希望某个关联的EntitySets被主动加载,则可以使用Include方法。下面的代码就是在一个SQL查询中,获取所有的Customers和他们的Orders:

            foreach (var c in context.Customers.Include("Orders"))
foreach (var order in c.Orders)
Console.WriteLine(order.Description);

Include可以被级联使用,假如每个Order都有OrderDetails和SalesPersons导航属性的话,我们可以写出如下的查询让这些数据也被一同加载:

            context.Customers.Include("Orders.OrderDetails")
.Include("Orders.SalesPersons")

 

更新

L2S和EF也会跟踪你对Entities所做的修改并允许你把他们更新到数据库。对于L2S,我们调用DataContext对象的SubmitChanges方法;而对于EF,我们调用ObjectContext对象的SaveChagnes方法。

L2S的Table<>类提供了InsertOnSubmit和DeleteOnSubmit方法让我们从一个表中插入或删除行;EF的ObjectSet<>类则提供了AddObject和DeleteObject方法来实现相同的功能,请看下面的代码示例:

复制代码
            var context = new LifePoemContext("database connection string");

Customer cust = new Customer { ID = 1000, Name = "Yoyoo" };
context.Customers.InsertOnSubmit(cust); // 插入Customer,在EF中使用AddObject
context.SubmitChanges(); // 在EF中使用SaveChanges

// 现在我们获取上面插入的数据行,对其进行更新,然后删除它
Customer cust2 = context.Customers.Single(c => c.ID == 1000);
cust2.Name = "Yoyoo2";
context.SubmitChanges(); // 更新Customer

context.Customers.DeleteOnSubmit(cust2); //在EF中使用DeleteObject
context.SubmitChanges(); // 删除Customer
复制代码

SubmitChanges/SaveChanges会收集自context创建(或上一次Save)以来对entities所做的所有修改,然后执行一个SQL语句来把他们写回数据库。

我们还可以调用Add方法来向一个EntitySet/EntitiyCollection添加数据行,在执行SubmitChanges或SaveChanges,L2S和EF会自动生成相应的外键:

复制代码
            Order o1 = new Order { ID = 100, OrderDate = DateTime.Now, Price = 100 };
Order o2 = new Order { ID = 101, OrderDate = DateTime.Now, Price = 500 };

Customer cust = context.Customers.Single(c => c.ID == 1);
cust.Orders.Add(o1);
cust.Orders.Add(o2);

context.SubmitChanges();
复制代码

在这个例子中,L2S/EF会自动把外键值1写入新增的Order的CustomerID列,这是因为我们为Customer和Order定义了关联属性,如:

        // With L2S
[Association(Name="Customer_Order", Storage="_Orders", ThisKey="ID", OtherKey="CustomerID")]
public EntitySet<Order> Orders { get {...} set {...} }

当你从一个EntitySet/EntityCollection中移除某行数据时,他的外键列会被自动设置为null。下面的代面会在我们最近新增的两个orders和他们的Customer之间移除关联,注意,只是去除关联,Remove并不会删除子entities:

复制代码
            var context = new LifePoemContext("database connection string");

Customer cust = context.Customers.Single(c => c.ID == 1);
cust.Orders.Remove(cust.Orders.Single(order => order.ID == 100));
cust.Orders.Remove(cust.Orders.Single(order => order.ID == 101));

context.SubmitChanges();
复制代码

因为上面的代码会把每个Order的CustomerID列设为空,所以数据库中Order.CustomerID列必需是可空的,否则会抛出异常。

如果我们要完全删除子entities,则需要调用DeleteOnSubmit:

复制代码
            // with L2S
context.Orders.DeleteOnSubmit(context.Orders.Single(order => order.ID == 100));
context.Orders.DeleteOnSubmit(context.Orders.Single(order => order.ID == 101));
context.SubmitChanges();

// with EF
context.Orders.DeleteObject(context.Orders.Single(order => order.ID == 100));
context.Orders.DeleteObject(context.Orders.Single(order => order.ID == 101));
context.SaveChanges();
复制代码

 

LINQ to SQL和Entity Framework的API对比

正如我们在这两篇文章中看到的那样,L2S和EF在LINQ查询和数据更新方面非常相似,只是创建的对象或调用的方法有所不同罢了,下表总结了他们的API差异: 

目的

LINQ to SQL

Entity Framework

获取保持所有CRUD操作的类

DataContext

ObjectContext

从数据库中(延迟)获取某种类型的所有entities

GetTable

CreateObjectSet

上面方法的返回类型

Table<T>

ObjectSet<T>

提交对实体对象的更新

SubmitChanges

SaveChanges

新增一个entity

InsertOnSubmit

AddObject

删除一个entity

DeleteOnSubmit

DeleteObject

代表关联属性(有多个相关entities的那一方)的类型

EntitySet<T>

EntityCollection<T>

代表关联属性(有多个相关entities的那一方)的类型(字段类型)

EntityRef<T>

EntityReference<T>

装载关联属性时的默认策略

自动延迟加载

明确调用

主动加载(eager loading)

DataLoadOptions

.Include()

 

通过这两篇文章,我们有针对性的了解了L2S和EF在LINQ查询支持上的特性。通过比较他们的异同,让我们更好的他们的内在联系和区分他们在使用上的差别。在接下来的几篇博客中,我准备对LINQ查询运算符(LINQ Operators)进行更加详细的分类介绍。只有在了解了大多数查询运算符后,才能更好的写出功能强大而又简洁优雅的 LINQ查询。

posted on 2017-07-07 21:57  不及格的程序员-八神  阅读(23)  评论(0编辑  收藏  举报