2007年06月23日

1.1 Future Directions

This section discusses some of the areas were we have plans to invest more, or at least we want to think about it more. All the enhancements listed here, just like all the features listed in the previous section, are subject to change. Feedback on these areas will greatly help the team prioritize these various areas appropriately.

1.1.1 Application scenarios

1.1.1.1 Stored-procedure support

The August CTP always uses dynamic SQL both for fetching and updating data. In many real-world environments the use of dynamic SQL is restricted due to security or operational reasons and stored-procedures, views and/or table-valued functions are used instead.

We’re working on introducing stored-procedure support to the system in a way that integrates naturally with the rest of the Entity Framework.

1.1.1.2 3-tier systems, SOA & WebServices

3-tier systems and web services require support from the system to be able to disconnect and re-connect entities into the state management machinery. We’re working on a design that will allow web-services interact with the Entity Framework in a way that’s natural for the typical patterns used in web services.

We’re also looking at SOA scenarios where contracts are important and may not match the shapes of entities exactly.

1.1.1.3 Data-binding support

We would like to include more explicit support for data binding, probably by exposing binding-list implementations that know about the Entity Framework and can coordinate the interactions with it (e.g. add/remove to notify the object-context appropriately).

1.1.1.4 Transactions

This CTP relies entirely on transactions support from the underlying providers. The only part of the system that controls its own transactions is the update subsystem, where updates are always done within a single transaction.

We’re analyzing options for having a deeper integration with System.Transactions and we if decide to proceed with this it would be included in future iterations within this version.

1.1.2 Data model & metadata

1.1.2.1 Entity Data Model

While the EDM is almost complete in this CTP release, there are few details that are still pending. Specifically, there are two aspects that will be addressed:

· Complex Types. Currently entities are made only of atomic types. Complex types are types with structure that can be a member of an entity.

· Better services integration. As more services integrate and build on top of the EDM, the need for capturing service-specific aspects in EDM metadata becomes more important. We’re working on the right way of providing a mechanism to allow rich services on top of EDM schemas.

1.1.2.2 Attributes instead of XML files

We’re investigating an option where a developer could create the entity types by hand and annotate them with attributes and that would be all that would be needed for the system to work. This would be in addition to the option that we already support, where metadata is expressed in XML files.

With this addition, developers would have the option to choose between a “model-first” approach, where the EDM model is designed and the entity types automatically generated from it, and a “code-first” approach, where the developer creates the .NET classes directly in code, and the system derives the metadata from it.

1.1.3 Query & LINQ

1.1.3.1 Entity SQL

We’ve received some feedback about Entity SQL already, and we expect to receive more from this CTP. Based on the feedback that we’ve receive so far, plus some pending work, we plan on focusing on the following aspects:

· Case insensitivity: identifiers in Entity SQL are currently case-sensitive (its actually configurable, but the knob to configure it is not public). We’re going to switch to case-insensitive, with a special construct for when case is important to preserve.

· Literals: we currently only have string and some numeric literals. We’re thinking of adding more, including GUID and Date/Time.

· Navigation properties. In this CTP navigation properties (e.g. customer.Orders) are only supported when querying against the object layer, not always. We would like to remove this limitation and make them work equally regardless of which layer the query comes from.

· Paging. We would like to introduce explicit constructs for paging, so you can define a window of records to be returned by a query.

· Syntax shortcuts. We’re considering a number of shortcuts for common patters and would make constructs that are used very often more compact.

1.1.3.2 LINQ to Entities

While the current CTP supports LINQ to Entities, there are various scenarios that are not implemented today and will fail with NotImplementedException. We plan to have full LINQ support in RTM.

Additionally, we plan on mapping well-known .NET Framework functions to SQL in certain cases. For example, if in a “where” clause you say “Customer.Name.Length > 10”, we would translate that to the appropriate representation in SQL.

1.1.3.3 LINQ to DataSet

LINQ to DataSet already supports all the LINQ operators that make sense over a DataTable or a sequence of DataRow objects. It also has the infrastructure to analyze a query and decide whether certain optimizations are possible and if so implement them so the queries run faster.

In the next iteration we plan on adding a broader set of patterns that are “optimizable”, and also and broader set of optimizations.

We’ll also investigate and consider feedback around the custom DataSet operators such as ToDataTable() and LoadSequence().

1.1.4 Tools

In addition to the existing tool to generate a model out of a database schema, we’re planning on two key tools for the Entity Framework:

  • Model designer: a visual tool to design EDM models. This visual tool would allow developers to use all of the elements of the Entity Data Model, including Entity Types, Entity Sets, Entity Containers, Relationships, etc.
  • Visual mapping: a visual tool to describe the mapping between the conceptual and the logical model.

1.1.5 ADO.NET data-access providers

The August CTP includes support for 3rd providers only for query execution, but not for update processing. We’ll complete the extensibility work so 3rd party providers can be integrated into the update processing pipeline.

Additionally, the metadata system will be augmented so providers can declaratively describe their type system, conversion rules and supported functions to the system.

1.1.6 Performance

While we’ve kept performance in mind throughout the design process for the entire Entity Framework, we haven’t implemented various of the key performance enhancements in this CTP. In upcoming iterations of ADO.NET vNext you can expect to see a noticeable performance gain across the board.

Some of the areas we’ll be working on for performance include:

  • Better caching of query plans. The client-views engine brings great expressivity to applications, but there is additional overhead in processing queries over those views in the client. We’re working on a caching strategy that will allow the system to process queries once and re-use the resulting plans multiple times.
  • Metadata caching and sharing. In this CTP there is very little sharing of metadata across instances of connections, contexts, commands, queries, etc. This affects both the memory usage and also the latency of activities because metadata gets reloaded often. Once metadata caching and sharing is in place, this effect will be greatly reduced and sometimes eliminated.
  • LINQ to DataSet optimizations. On a somewhat separate area, we’ll continue to enhance the heuristic optimizer used for executing LINQ queries over DataSets.

DataSet Finally Gets Full Query Capabilities: LINQ to DataSet
数据集的完全查询能力:LINQ到数据集

ADO.NET编程的一个关键元素就是可以当连接断开或者使用后台数据集的时候显式的缓存数据。一个数据集表现为一组表和关系的集合,通过元数据来表述结构和数据包含的约束。ADO.NET包含了一些类来让从数据库中载入数据到数据集更加容易,并且方便的把数据集的改变反映到数据库中。

数据集一个有趣的方面就是允许应用程序带有数据库中数据的一个子集,并且在内存中操纵这部分数据,当然,关系也是被完整的保存在了数据集中。这将让我们可以在很多要求数据表现和处理很灵活的场景中使用数据而不用担心“顽固的数据库”。特别的,一般的报告、分析和商业智能应用程序都支持这些操作数据的方法。.

为了查询数据集中的数据,数据集API包含了一系列的方法,例如DataTable.Select(),可以用来搜索数据,以特定的、某些预定义的方式。尽管如此,仍然没有一个通用的一般的机制来对数据集对象进行“富查询”(语意丰富的查询)和进行在很多以数据为中心的应用中的典型逻辑表达。

LINQ提供了一个独一无二的机会在数据集顶端引入“富查询能力”,并且整合开发环境的其他能力下实现这些富查询要求。

ADO.NET vNext 包含了对LINQ查询的完整的支持,不论是一般数据集还是强类型数据集。想象这个情景,我们的数据集中有两个表“SalesOrderHeader”、“SalesOrderDetail”;下面就是我们如何从所有的在线订单中得到订单的标识符和订单的日期:

DataSet ds = new DataSet();
FillOrders(ds); // this method fills the DataSet from a database
DataTable orders = ds.Tables["SalesOrderHeader"];
var query = from o in orders.ToQueryable()
                where o.Field<bool>("OnlineOrderFlag") == true
                select new { SalesOrderID = o.Field<int>("SalesOrderID"),
                                  OrderDate = o.Field<DateTime>("OrderDate") };
foreach(var order in query) {
        Console.WriteLine("{0}\t{1:d}", order.SalesOrderID, order.OrderDate);
}

下面是使用Visual Basic表达的代码:

Dim ds As New DataSet()
FillOrders(ds)
Dim orders As DataTable = ds.Tables("SalesOrderHeader")
Dim query = From o In orders.ToQueryable() _
                 Where o!OnlineOrderFlag = True _
                 Select o!SalesOrderID, o!OrderDate
For Each Dim o In query
        Console.WriteLine("{0} {1:d}", o.SalesOrderID, o.OrderDate)
Next

我们观察到Visual Basic支持非强类型数据集的晚绑定,这对VB的代码阅读与阅读C#的代码相比更加容易。下面我们将讨论怎样更好的使用两种语言(C#、VB)解决强类型数据集的这个问题。

一个普通的对数据集的要求就是支持跨表的连接查询,现在LINQ也支持。这里有一个跨表SalesOrderHeader和SalesOrderDetail的连接查询的例子:

DataSet ds = new DataSet();
FillOrders(ds);
DataTable orders = ds.Tables["SalesOrderHeader"];
DataTable details = ds.Tables["SalesOrderDetail"];
var query = from o in orders.ToQueryable()
                 join d in details.ToQueryable()
                   on o.Field<int>("SalesOrderID") equals d.Field<int>("SalesOrderID")
                 where o.Field<bool>("OnlineOrderFlag") == true
                 select new { SalesOrderID = o.Field<int>("SalesOrderID"),
                                                        OrderDate = o.Field<DateTime>("OrderDate"),
                                                        ProductID = d.Field<int>("ProductID"),
                                                        Quantity = d.Field<short>("OrderQty") };
foreach(var line in query) {
        Console.WriteLine("{0}\t{1:d}\t{2}\t{3}",
                                 line.SalesOrderID, line.OrderDate,
                                 line.ProductID, line.Quantity);
}

上面的LINQ和数据集是非常好用的工具,这个代码稍微有一点混乱的感觉,原因如下:

  • 列访问仍然是通过晚绑定完成的,所以列名是使用字符串表明的。(不仅代码有点混乱,也阻止了编译器对列名的编译时检查)
  • 数据集域的访问默认是弱类型的(这里,我们使用table["column"]来说作为对象类型);一个解决的办法就是使用cast,但是这也不能阻止null值(数据集中的DBNull.Value)。为了提供一个统一的数据访问和合适的处理null,一个新的操作符“Field”被引入。为了表明“Date”的类型,我们可以使用table.Field<DateTime>("Date")来说明。

如果数据集的架构在应用程序的设计时就知道了,那么通过LINQ来使用数据集就可以提供一个非常好的编程体验。在强数据类型的数据集中表和行都有自己的类型,每一个字段也有自己的类型,这让访问变得更加容易了。此外,数据集还有一些属性来让访问它内部的表更加容易、方便、简单。

如果我们使用上面的同样的表(见上一个例子)创建一个强类型的数据集,我们可以这样编写我们的第一个查询:

OrdersDataSet dsOrders = new OrdersDataSet();
FillOrders(ds); // this method fills the DataSet from a database
var query = from o in dsOrders.SalesOrderHeader
                 where o.OnlineOrderFlag == true
                 select new { o.SalesOrderID,
                                   o.OrderDate };
foreach(var order in query) {
        Console.WriteLine("{0}\t{1:d}", order.SalesOrderID, order.OrderDate);
}

如你所见,查询变得更加容易了、简单了。下面是另一个例子:

OrdersDataSet ds = new OrdersDataSet();
FillOrders(ds);
var query = from o in dsOrders.SalesOrderHeader
                 join d in dsOrders.SalesOrderDetail
                   on o.SalesOrderID equals d.SalesOrderID
                 where o.OnlineOrderFlag == true
                 select new { o.SalesOrderID,
                                   o.OrderDate,
                                   d.ProductID,
                                   Quantity = d.OrderQty };
foreach(var line in query) {
        Console.WriteLine("{0}\t{1:d}\t{2}\t{3}", 
                                 line.SalesOrderID, line.OrderDate,
                                 line.ProductID, line.Quantity);
}

除了支持适当的整合到LINQ之外,数据集LINQ实现了足够“聪明的”对查询进行求值的功能,来决定是否对给定的数据集进行默认的查询(就是我们怎么写,它就怎么执行),还是进行适当的优化处理。如果我们的数据集中创建了索引,这将使我们的查询执行的速度更快,这时候,系统的查询策略将尝试进行这项优化,以使运行更加高效。

 

LINQ到SQL

对于开发者来说,不需要重新映射一个概念模型,LINQ到SQL(就是我们已知的DLinq)允许开发者在已有的数据库架构上直接使用LINQ编程模型。

类似于ADO.NET实体中LINQ的支持一样,LINQ to SQL允许开发者生成一些类来描述数据。相比映射为一个概念模型,这些生成的类更加直接的从数据库表、视图、存储过程和用户自定义函数中映射出来,使用LINQ to SQL,当以前使用内存中的集合、实体或者数据集(也包括以前使用的XML数据源)来描述存储架构时,开发者可以对存储架构直接进行LINQ编程样式的编码开发。

使用LINQ to SQL生成的类,我们可以写下下面的这些看上去就舒服的代码来连接本地的SQLExpress数据库中的Northwind数据库:

string connectString =
        "AttachDBFileName=’C:\\ProgramFiles\\LINQ Preview\\Data\\Northwnd.mdf’;" +
        "Server=’.\\SQLEXPRESS’;Integrated Security=SSPI;enlist=false";
using(Northwind db = new Northwind(connectString)) {
        var customers = from c in db.Customers
                               where c.City == "London"
                               select c;
        foreach(Customer c in customers) {
                Console.WriteLine("{0}\t{1}", c.ContactName, c.CompanyName);
        }
}

如你所见,我们写的代码与之前的Adventureworks数据库使用实体的那些代码一样“赏心悦目”。LINQ的威力就在于可以使用相同的简单的编程来访问任何类型的数据。

注意:目前的LINQ to SQL已经被5月份的LINQ CTP所包含。ADO.NEt vNEXT CTP还没有包含一个更新的版本,只是增加了ADO.NET Entity Framework和LINQ to Entities。

尽管在数据库和开发环境的整合上有着巨大的优势,这里仍然有一个容易出现问题的地方:不容易解决对库的使用和数据编程的API的使用之间的匹配问题。当Entity Framework最小化这种逻辑行(Logical Rows)与实体对象之间的匹配误差的时候,Entity Framework同时整合了现有的编程语言,并加以扩展,来很自然的让该语言自己表示对自己的查询。

进一步的说,今天的绝大多数商业应用程序的开发者必须处理至少两种编程语言:一种是用来对商业逻辑进行建模的,还包括对表现层的设计(例如现有的很多高级面向对象编程语言VB.NET、C#之类的);另一种是用来与数据库进行交互的(典型的就是SQL语言)。

这不仅意味着开发者必须精通很多们语言才能够进行高效的开发,也意味着引入了一些在两个编程语言之间的“跳跃”的误差。例如,在很多典型的应用程序中执行对数据库的查询使用的是数据访问API或者ADO.NET技术,并且显式的在程序代码中嵌入查询语句;当这个查询语句字符串传递给编译器的时候,并没有进行语法检查或者校验它所引用的现有的元素(例如表和字段的名称)。

为了解决该问题,下一个版本的Microsoft C#和Visual Basic语言将会有一些新的特性。

Language-Integrated Query 语言整合查询

在下一个版本的C#和Visual Basic编程语言中包含了一组创新的特性来让在程序代码中操纵数据更容易。LINQ项目设计了一套语言的扩展和支持库来让用户可以使用本身的编程语言来规范数据查询语言,而不需要去借助别的语言,然后嵌入在用户程序中,而且让编译器无法检查其中的错误和校验有效性。

使用LINQ进行的查询可以针对很多种类的数据源,例如内存中的数据结构、XML文档和ADO.NET下的数据库、实体模型和数据集(DataSet)。当一些底层机制和实现不同的技术被使用的时候,LINQ可以保证对外暴露的都是相同的语法和语言特性。

对于每一种编程语言它的查询语法细节都是特定的,并且在LINQ数据源面前都是一样的。例如,下面就有Visual Basic查询一个普通的内存中的数组的例子:

Dim numbers() As Integer = {5, 7, 1, 4, 9, 3, 2, 6, 8}
Dim smallnumbers = From n In numbers _
                           Where n <= 5 _
                           Select n _
                           Order By n
For Each Dim n In smallnumbers
        Console.WriteLine(n)
Next

下面是一个同样的查询,只不过是C#版本的:

int[] numbers = new int[] {5, 7, 1, 4, 9, 3, 2, 6, 8};
var smallnumbers = from n in numbers
                          where n <= 5
                          orderby n
                          select n;

foreach(var n in smallnumbers) {
        Console.WriteLine(n);
}

查询数据源中的实体模型或者数据集在表面上看上去具有相同的语法,我们在下面将深入的讨论。

我们随后会对LINQ项目的细节,希望有兴趣的朋友继续关注我的Blog。

LINQ and the ADO.NET Entity Framework LINQ和ADO.NET实体框架

当我们在上面的实体服务中讨论的时候,知道对于下一个版本的ADO.NET包含了一个可以将数据库数据暴露为.NET对象的抽象层次。进一步的,ADO.NET将提供一个工具用来生成这些在.NET环境中表现EDM架构的.NEt类。这将让对象层成为LINQ理想的支持目标,允许开发者通过一般的编程语言来查询、操纵数据,从而更好的构建商业逻辑。这种能力称为LINQ到实体(LINQ to Entities)。

例如,在前面的文章中我们用下面的代码片段,用来查询数据库(使用对象的方式):

using(AdventureWorksDB aw = new AdventureWorksDB(Settings.Default.AdventureWorks)) {
        Query<SalesPerson> newSalesPeople = aw.GetQuery<SalesPerson>(
                "SELECT VALUE sp " +
                "FROM AdventureWorks.AdventureWorksDB.SalesPeople AS sp " +
                "WHERE sp.HireDate > @date",
                new QueryParameter("date", hireDate));
        foreach(SalesPerson p in newSalesPeople) {
                Console.WriteLine("{0}\t{1}", p.FirstName, p.LastName);
        }
}

有了自动代码生成工具,LINQ对ADO.NET的支持可以说是上了一个新台阶,我们重写上面的代码:

using(AdventureWorksDB aw = new AdventureWorksDB(Settings.Default.AdventureWorks)) {
        var newSalesPeople = from p in aw.SalesPeople
                                        where p.HireDate > hireDate
                                        select p;
        foreach(SalesPerson p in newSalesPeople) {
                Console.WriteLine("{0}\t{1}", p.FirstName, p.LastName);
        }
}

下面是对应的Visual Basic的形式:

Using aw As New AdventureWorksDB(Settings.Default.AdventureWorks)
        Dim newSalesPeople = From p In aw.SalesPeople _
                                        Where p.HireDate > hireDate _
                                        Select p
For Each p As SalesPerson In newSalesPeople
        Console.WriteLine("{0} {1}", p.FirstName, p.LastName)
Next
End Using

使用LINQ,我们的代码将完全的提交给编译器,这意味着我们可以得到编译时的代码检查。关于成员名称和数据类型的语法错误也可以被编译器捕获,并且在编译时得到相关的报告,而不是像以前一样,使用编程语言和SQL等DML等到通过编译,运行时才发现SQl语句存在错误(这是我们以前经常遇到的情况)。

通过查询得到的返回结果仍然是ADO.NET实体的对象,所以可以操纵和更新数据,就像我们之前使用实体SQl一样,它们具有同样的语意。

这个例子只是显示了一个非常简单的查询例子,LINQ可以支持非常复杂的查询要求,例如包含排序、分组、连接等。查询可以产生一个“平板”的结果或者具有复杂结构的结果,而这些都只需要我们使用原本的C#或者Visual Basic语言就可以了。

例如,下面有一些代码,首先根据雇用日期然后根据FirstName来排序,只要求返回整个实体中的一部分信息:

using(AdventureWorksDB aw = new AdventureWorksDB(Settings.Default.AdventureWorks)) {
        var newSalesPeople = from p in aw.SalesPeople
                                        where p.HireDate > hireDate
                                        orderby p.HireDate, p.FirstName
                                        select new { Name = p.FirstName + " " + p.LastName,
                                                        HireDate = p.HireDate };
        foreach(SalesPerson p in newSalesPeople) {
                Console.WriteLine("{0}\t{1}", p.FirstName, p.LastName);
        }
}

我们可以非常灵活的使用LINQ和ADO.NET提供给我们的特性,下面的代码是不是看上去好多了:

using(AdventureWorksDB aw = new AdventureWorksDB(Settings.Default.AdventureWorks)) {
        var newSalesPeople = from o in aw.SalesOrders
                                where o.SalesPerson.HireDate >= new DateTime(2006, 1, 1)
                                select o;
        // process orders
        // …
}

Manipulating Data and Persisting Changes 操纵数据和持久化改变

通常当一个应用程序打算将一些数据的改变保存在数据库中,需要使用INSERT、UPDATE或者DELETE语句来实现。事实上,应用程序往往是一些“实体”(即使是非正式的或者隐式的定义的),并且开发者必须为每一个数据系统中的实体编写(或者使用工具来生成)SQL DML语句。这是单调枯燥的,容易出错的,特别是面对大量这样的代码,未来的维护简直是不能忍受的。

ADO.NET Entity Framework有足够的元数据。原来标识系统中的实体,让其能够反射从数据库中的改变,而不需要用户撰写SQL语句,例如:

using(AdventureWorksDB aw = new AdventureWorksDB                                                        (Settings.Default.AdventureWorks)) {
        // find all people hired at least 5 years ago
        Query<SalesPerson> oldSalesPeople = aw.GetQuery<SalesPerson>(
                "SELECT VALUE sp " +
                "FROM AdventureWorks.AdventureWorksDB.SalesPeople AS sp " +
                "WHERE sp.HireDate < @date",
                new QueryParameter("date", DateTime.Today.AddYears(-5)));
         foreach(SalesPerson p in oldSalesPeople) {
                // call the HR system through a webservice to see if this
                // sales person has a promotion coming (note that this
                // entity type is XML-serializable)
                if(HRWebService.ReadyForPromotion(p)) {
                        p.Bonus += 10; // give a raise of 10% in the bonus
                        p.Title = "Senior Sales Representative"; // give a promotion 
                }
        }
        // push changes back to the database
        aw.SaveChanges();
}

更新是很简单的,因为Entity Framework为我们做了很多事情来简化开发者的工作,这个系统:

  • 持续跟踪每一个查询产生的对象。Object Context对象,一个ObjectContext实例或者一个类似于AdventureWorks的类型化对象,这些被跟踪的实例表现为一些逻辑域。
  • 保持使用的每个对象的原始数据。这将允许系统在执行数据库的更新过程中进行乐观的并发检查。
  • 持续跟踪那些被改变的对象,当你调用SaveChanges的时候,ADO.NET能够知道那些实体需要被更新到数据库。
  • 保持结果集的元数据信息(用来描述实体集从何而来的、是哪些实体类型等),所有信息将允许生成要求的更新语句,而不需要用户提供额外的信息。
  • 在对象层次中进行到概念层次或者逻辑(关系)层次的更新转换。

更新并不仅仅立即作用在查询的结果上,它们也作用子表达实体关系的集合上。例如,添加一个新的销售订单并且将给定的销售人员关联起来,这是一种改变的形式,并且它将被通过简单的添加新的SalesOrder对象到销售人员的Orders集合中,并且调用更新。

这里有一种情景,就是你知道你不需要更新哪些查询返回的对象的情况。在这种情况下,你可能打算避免系统在跟踪管理这些对象上的开销,你可以使用MergeOption属性(在Query<T>类中),其中有一个选项是“NoCaching”,这就将关掉所有返回对象的状态信息管理。

EDM Concepts in the Object Space 在对象空间中的EDM概念

在前面,我们讨论了一些EDM概念(例如实体、关系和继承),一般的,所有的EDM概念都被映射为.NET环境下的对象服务。

实体被简单的映射为类,这遵循一定的规范来进行。为了让事情简单化,这些类可以被EDM架构的一个工具自动实现代码的生成。

对象空间中的关系被暴露为可以直接导航的属性。例如,我们的Adventureworks的EDM模型中,销售人员SalesPerson实体类型和销售订单SalesOrder实体类型之间就存在着关系。基于这些信息,代码自动生成工具将创建适当的成员,下面的代码就可以如我们想象一般的工作了:

using(AdventureWorksDB aw = new AdventureWorksDB                                                     (Settings.Default.AdventureWorks)) {
                Query<SalesPerson> newSalesPeople = aw.GetQuery<SalesPerson>(
                "SELECT VALUE sp " +
                "FROM AdventureWorks.AdventureWorksDB.SalesPeople AS sp " +
                "WHERE sp.HireDate > @date",
                new QueryParameter("date", hireDate));
        foreach(SalesPerson p in newSalesPeople) {
                // for each sales person that matches certain condition, process
                // their pending orders (assuming pending is status == 0)
                if(NeedsOrderProcessing(p)) {
                        // query for the pending orders
                        foreach(SalesOrder o in p.Orders.Source.Where("it.Status == 0")) {
                                // process order
                                ProcessOrder(o);
                        }
                }
        }
}

注意到关系导航可以通过某个实体属性来简单的进行,实体作为关系的端点。

另一个有趣的描述EDM元素怎样被暴露到对象空间层的是“继承”概念。EDM架构中,我们必须使用SalesOrders实体集(包含SalesOrder和StoreSalesOrder类的实例);这是直接反映到.NET环境中的类的继承关系中的。代码自动生成工具将生成一个StoreSalesOrder类(从SalesOrder类继承而来),并且实例将被适当的实例化。

using(AdventureWorksDB aw = new AdventureWorksDB                                                        (Settings.Default.AdventureWorks)) {
                Query<SalesOrder> pendingOrders = aw.GetQuery<SalesOrder>(
                        "SELECT VALUE " +
                        "FROM AdventureWorks.AdventureWorksDB.SalesOrders AS o " +
                        "WHERE o.Status = 0");
                foreach(SalesOrder o in pendingOrders) {
                        // use the run-time type to determine how to process this order
                        if(o is StoreSalesOrder) {
                                ValidateTaxByState((StoreSalesOrder)o);
                                ProcessLocalOrder(o);
                        }
                else {
                        ProcessOnlineOrder(0);
                }
        }
}

当然,在很多情况下你不会打算对什么东西都特别处理,而是使用OOP的方法来进行这些类似的工作,例如使用OOP中的继承概念(还有虚函数等)。当使用查询返回了实例的时候,这些返回的实例都是一些CLR对象,因为模型定义而拥有不同类型,所有的这些东西都让我们的代码充满Pure Business Logic而不是对数据库的控制。

ADO.NET Entity Framework: Object Services

大量的开发者采用了通用目的的、类似于VB.NET和C#一样的面向对象的编程语言来编写大量的新的商业应用程序。这些编程语言和它们的开发环境建立了商业实体(一般处理为类)和它们说具有的行为的代码模型。与之形成鲜明对比的是,ADO.NET将数据从数据库中取出作为“Values、Rows、Columns”存在。为了与数据库进行交互,应用程序必须处理数据和应用程序代码之间的些许“沟壑”;这包括查询方式的规格化和结果的标识。

ADO.NET Entity Framework 包含了一个对象服务层来提供对数据和应用之间的“沟壑”的消除或者减弱。

同样的数据,不一样的表现——对象

应用程序、特定的大规模应用或者大型系统都有很多的应用,极少有在数据和应用之间使用单一的数据表现形式的;很多方面(例如静态-动态查询知识和结果集的结构、用户应用交互模型等)都影响着与在数据库中的数据的具体交互方式。

需要一种全新的、卓越的基础架构来将数据暴露为对象,ADO.NET Entity Framework包含了一个对象服务层(Object Serivces Layer)来整合这些数据,将实体数据暴露为.NET的对象就是一种良好的选择。

不论你选择处理你的数据是使用“Values”(行与列的方式)还是作为对象来处理,你仍然使用着相同的架构;因此,相同的概念EDM架构都可以使用,同样的,映射和查询语言(实体SQL)也可以不变。

例如,下面的代码片段就是获得和操纵销售人员的数据的实例代码,这里我们使用了一般的DataReader(我们这里使用“Values”的方式):

using(MapConnection con = new MapConnection(Settings.Default.AdventureWorks)) {
        con.Open();
        MapCommand cmd = con.CreateCommand();
        cmd.CommandText =
                "SELECT VALUE sp " +
                "FROM AdventureWorks.AdventureWorksDB.SalesPeople AS sp " +
                "WHERE sp.HireDate > @date";
        cmd.Parameters.AddWithValue("@date", hireDate);
        DbDataReader r = cmd.ExecuteReader();
        while(r.Read()) {
        Console.WriteLine("{0}\t{1}", r["FirstName"], r["LastName"]);
        }
}

作为一个Report或者商业智能系统,延迟绑定是很不错的选择,或者直接序列化结果,例如,从一个WebService,但是对于一个重量级的商业逻辑,我们提供商业实体对象也许系统会更加容易“消化”一点。下面就是我们使用下一个版本的ADO.NET写出的基于对象的代码:

using(MapConnection con = new MapConnection(Settings.Default.AdventureWorks)) {
        con.Open();
        ObjectContext ctx = new ObjectContext(con);
        Query<SalesPerson> newSalesPeople = ctx.GetQuery<SalesPerson>(
                "SELECT VALUE sp " +
                "FROM AdventureWorks.AdventureWorksDB.SalesPeople AS sp " +
                "WHERE sp.HireDate > @date",
                new QueryParameter("date", hireDate));
        foreach(SalesPerson p in newSalesPeople) {
                Console.WriteLine("{0}\t{1}", p.FirstName, p.LastName);
        }
}

取代使用command对象来表示查询的是“object context”(对象上下文),它提供了一个对象服务和查询对象的入口,这都是发生在一个对象空间内的。注意,我们仍然使用了同样的map connection(指向同样的EDM架构和映射),使用同样的查询语言。除此之外的唯一的去别在于返回的结果是以对象的形式表现的。

下一个问题是:类型SalesPerson来自何处?ADO.NET Entity Framework包含了一个工具,一旦给定一个EDM架构,就会自动生成使用.NET环境的用来表示EDM实体的类。生成的这些类都是“Partial Class”(局部类),所以它们可以被很容易的扩展,将自定义的商业逻辑放在独立的文件中,而不需要修改自动生成的代码。

这个工具可以访问实体EDM架构,并且不仅仅是实体类型的定义,还包括实体集、关系等等。基于这些信息,该工具不仅可以生成每个实体类型的类,也可以生成一个从EDM透视过来的反映存储情况的高层次类,包含了实体集和关系等信息。这将进一步简化数据访问和使用数据的代码的书写。我们使用上面同样的例子,但是使用强类型的基于object context(由工具生成),代码如下:

using(AdventureWorksDB aw = new AdventureWorksDB                                                       (Settings.Default.AdventureWorks)) {
        Query<SalesPerson> newSalesPeople = aw.GetQuery<SalesPerson>(
                "SELECT VALUE sp " +
                "FROM AdventureWorks.AdventureWorksDB.SalesPeople AS sp " +
                "WHERE sp.HireDate > @date",
                new QueryParameter("date", hireDate));
        foreach(SalesPerson p in newSalesPeople) {
                Console.WriteLine("{0}\t{1}", p.FirstName, p.LastName);
        }
}

我们可以看到大多数的非业务代码消失了,只有体现应用本身意义的代码保留下来。这就消除了很多数据和业务之间的结合的代码问题。这就是ADO.NET Entity Framework的其中一个关键目标。

EDM模型的查询: Entity SQL (实体SQL)

当一个应用程序使用EDM模型和映射provider时,它不再需要直接连接到数据库服务器或者“知道”任何数据库特定的情况;整个程序的操作都是在高度抽象的EDM模型上进行的。

这意味着你可以不再使用原生数据库查询语言了;不仅仅数据库不需要理解EDM模型,当前的数据库查询语言也不需要某种机制去处理EDM中的例如继承、关系和复杂数据类型之类的特性。

为了允许EDM模型能够使用查询,ADO.NET Entity Framework引入了一种新的查询语言来配合EDM的工作,可以完善的、简单方便的,提供了对EDM模型的完全描述能力。这门语言称为Entity SQL(实体SQL),它与开发者以前使用的SQL语言看上去很类似。实体SQL提供了对Entity Framework的动态查询能力,我们既可以在设计时静态的写入也可以在运行时绑定到应用程序的上下文中。

例如,下面就是一个电信的实体SQL查询(我们已经见过了):

SELECT sp.FirstName, sp.LastName, sp.HireDate
FROM AdventureWorks.AdventureWorksDB.SalesPeople AS sp
WHERE sp.HireDate > @date

从整体来看,实体SQl的结构是普通的 SELECT-FROM-WHERE 顺序,就如同在传统的SQL中一样。但是另一些实体SQL基本元素引入了一些概念让开发者可以描述EDM模型;下面就来介绍一下几个关键的概念(Picture by Entity SQL):

处理实体 概念EDM架构是围绕着实体来设计的。商业概念可以从存储为相应EDM的实体类型的实体集中的实例中直接反映出来。这和关系型使用表一样,在EDM中我们使用实体集来进行查询。所以从开始,我们的查询就是针对实体的(这些实体来自于一个或者多个实体集)。

取决于我们特定场景的需求,你可以选择返回独立的数据或者整个实体。返回整个实体是很有趣的,特别是当你打算你的系统和服务都是围绕着实体进行的时候。例如,感谢在EDM架构中和映射规范中存在的元数据,ADO.NET Entity Framework知道如何反射更新到实体存储中,而不需要用户使用例如INSERT、UPDATE、DELETE等命令,而这是传统的ADO.NET DataAdapter所需要的。

我们的查询非常类似于传统的SQL语句(注意表的别名):

SELECT sp.FirstName, sp.LastName, sp.HireDate
FROM AdventureWorks.AdventureWorksDB.SalesPeople AS sp
WHERE sp.HireDate > @date

如果在查询中我们打算得到整个sales person的实体,我们将这样写代码:

SELECT sp
FROM AdventureWorks.AdventureWorksDB.SalesPeople AS sp
WHERE sp.HireDate > @date

这个查询将产生一个单列的结果集在该列中我们可以找到一个“Record”(如果我们使用一个DataReader,我们将的到一个DbDataRecord对象的返回),里面包含了该实体的所有成员。

某些时候这将使结果集的处理变得简单一点而不需要对Record进行包装;这可以应用在获取实体(如同例子中说做的一样),就如同我们获取一个一般的scalar数值一样。如果你打算消除对Record的任何封装,你可以使用关键字“VALUE”,写在实体SQL代码中,如下:

SELECT VALUE sp
FROM AdventureWorks.AdventureWorksDB.SalesPeople AS sp
WHERE sp.HireDate > @date

这个查询将返回同样的实体,只不过并不是一列的形式。结果的读取器(如果我们使用DbDataReader)将包含多列字段,分别对应实体的每个属性。当使用对象层——Object layer(我们将在后面介绍),使用VALUE将变得更加有趣,因为这会让实体将变得类似于CLR对象一样“物化”,而没有任何的数据结构上的封装。

关系导航 除了实体之外,另外一些EDM的关键元素通过关系来显式的描述关联的概念;当系统知道关于实体之间的关系时,查询语言可以用在显式的在关系之间导航,而不需要使用任何的例如“join”的传统手法。

例如,在此之前我们使用下面的查询语言来找到订单数目大于某个数值的销售人员:ue:

SELECT SalesPersonID, FirstName, LastName, HireDate
FROM SalesPerson sp
INNER JOIN Employee e ON sp.SalesPersonID = e.EmployeeID
INNER JOIN Contact c ON e.EmployeeID = c.ContactID
INNER JOIN SalesOrder o ON sp.SalesPersonID = o.SalesPersonID
WHERE e.SalariedFlag = 1 AND o.TotalDue > 200000

这带来了复杂性,复杂性来源于1)销售人员的信息被分割在不同的表中;2)从销售人员导航到销售订单并不是直接可以完成的,需要做连接。

使用新的概念EDM架构来完成这项工作,我们这样写代码:

SELECT VALUE sp
FROM AdventureWorks.AdventureWorksDB.SalesPeople AS sp
WHERE EXISTS(
        SELECT VALUE o
        FROM NAVIGATE(p, AdventureWorks.SalesPerson_Order) AS o
        WHERE o.TotalDue > 200000)

这个“导航”的操作允许查询显式的穿过关系;应用程序并不需要知道关系是被怎样维护的或者如果间接的通过连接来使用这些数据。

继承的支持 EDM支持实体类型之间的继承,查询语言必须允许用户使用允许类型层次继承的查询。

例如,我们的概念EDM模型有一个实体类型SalesOrder,有一个子类型StoreSalesOrder(包含一些特殊的属性特征)。每个StoreSalesOrder都是“is a”SalesOrder,一个对SalesOrder实体集的查询将返回一个多态的的结果集,其中包含SalesOrder的同时也包含了StoreSalesOrder:

SELECT VALUE o
FROM AdventureWorks.AdventureWorksDB.SalesOrders AS o

如果我们想只返回StoreSalesOrder,我们需要向系统说明这种继承关系:

SELECT VALUE o
FROM AdventureWorks.AdventureWorksDB.SalesOrders AS o
WHERE o IS OF (AdventureWorks.StoreSalesOrder)

这里,操作符“IS OF”被看作一个表达式,用来说明类型的具体情况的(具体类型包含在后面的括号里)。

教训学习 除了实体SQL被设计包含多种扩展特性和提供了第一层次的EDM架构的查询特性,实体SQL仍然与传统的SQL语言有着很多不兼容的地方(多在于扩展特性上)。

举一个例子,在实体SQL的表达式中可以产生单个结果或者结果集,并且结果集可以在第一个层次上表现为很多的形式,这就让我们得到了很多的灵活性选择。例如,一个集合可以在FROM子句中表现的如同一个查询的数据源一样,也可以在SELECT列表中,成为只有一列的结果集(使用集合来替代Scalar)。

如果想进一步的了解实体SQL,请继续关注本Blog的文章。

把数据带入EDM模型——映射(Mapping)

EDM是一个概念数据模型,可以用来对给定的域进行建模。尽管如此,在某些情况下,数据必须被存储在真实的数据库中,典型的就是关系型数据库。

为了一共一个存储模型化数据的存储机制,也就是在关系型数据库中使用EDM,ADO.NET Entity Framework封装了一个强有力的客户-视图基础架构用来管理应用程序在逻辑数据架构(存在于关系型数据库中)和概念EDM架构之间的转换关系。

除了EDM架构之外,系统可以输出映射规范(Mapping Specification);这个映射规范可以由映射工具产生(以XML文件的形式)。

继续我们的例子,如果我们打算映射一个逻辑数据架构,我们使用开始的时候的概念EDM架构进行,我们将要做以下事情:

当映射工具用来创建一个概念上到逻辑上的映射,它产生一个XML文件,该文件能被ADO.NET Entity Framework的运行时组件所支持。幸运的是,这些工具让大量的用户都必须理解和处理XML的情况成为历史。

注意:目前的ADO.NET vNext CTP 还不包含可视化工具,我们还只能使用直接编辑XML文件的办法。

 除了提供对EDM架构映射的支持之外,ADO.NET中的客户端-视图基础架构还有其他好处。从文章一开始我们就讨论了我们现在应用程序开发者使用现有的数据库架构,会将不必要的复杂性带入到程序代码之中。通过使用客户端-视图基础架构,这个额外的逻辑复杂性可以被阻止在应用程序之外;取而代之的是,视图可以通过对数据的“重构”提供给应用程序。这个方式让应用程序专注于要解决的问题,并且有合适的数据视图可以使用。这也提供了应用程序与数据之间的独立性。

一个显而易见的问题是:我们为什么不直接使用传统数据库中提供的视图功能。当数据库视图从很多映射中抽象出来的时候,解决方案往往会因为下面几个程序或者功能性原因而无法正常工作:(a)过多的视图会导致生成与维护这些视图的成本效益比下降,甚至于只是一些简单的概念模型到逻辑的映射。(b)视图类型的字段的自动更新很多时候受到限制。(c)大中型公司的核心系统的数据库同时被很多处理中心或者部门应用所使用,让每个独立的应用创建不同的视图将会把整个数据库架构“污染掉”,为数据库管理者带来巨大的工作量。另外,数据库视图被关系模型所局限,典型的缺乏很多现实世界的实体数据模型的概念,例如继承和复杂数据类型。

ADO.NET 客户端-视图的工作主要是在客户端,每一个应用程序开发者可以创建视图来适应不同的应用程序的独特要求,而不用影响到真实的数据库或者其他应用程序。可更新的视图是被Entity Framework所支持的,而且使用上更加方便、灵活。

为了解决我们之前说过的第一个问题——我们需要找到体格方法去使用高层次的构造来描述数据的结构。

Entity Data Model –或者说是 EDM- 是一个实体关系数据模型。EDM的核心概念如下:

  • 实体Entity: 实体是一些实体类型的实例化(例如Employee、SalesOrder),有一个主键的富结构化的信息。实体被聚合为实体集(Entity-Sets)。
  • 关系Relationship: 实体之间的联系,是一些联系类型的实例化(例如SalesOrder被SalesPerson提交)。关系被聚合为关系集(Relationship-Sets)。

显式的引入实体和关系将允许开发者描述结构时更清楚。除了这些核心概念以外,EDM还支持多种其他的构造来进一步扩展表达力。例如:

· 继承Inheritance: 实体类型能够通过从其他的类型继承而被定义(例如Employee可以继承Contact)。这种继承是强结构化的,意味着没有行为的继承发生(相对于面向对象的编程语言来说)。继承的是基实体类型的数据结构;除了结构的继承,一个派生出来的实例实体类型还是满足面向对象的满足与基实体类型“is a ”的关系。

· 复杂类型Complex types: 除了一些常用的在大多数数据库中都具有的可伸缩的数据类型外,EDM还支持定义复杂数据类型,并且将它们作为实体类型的成员来使用。例如,你可以定义一个Address地址复杂类型包含StreetAddressin、City和Province,然后将其添加为Contarct实体类型的一个属性。

有了这些新工具,我们可以重定义逻辑架构,帮助我们实现一个概念模型了:

上面的架构有下面的元素:

  • 3个实体类型:SalesPerson, SalesOrder 和 StoreSalesOrder。注意StoreSalesOrder “is a” SalesOrder (继承了SalesOrder),带着独特的属性——tax information(税务信息)。
  • 在SalesOrder 和 SalesPerson 实体类型之间的一个关系
  • 两个实体集:SalesOrders 和 SalesPeople;注意SalesOrders实体集可以有两个类型的示例salesOrder 和 StoreSalesOrder 实体类型的实例。

这一个新的模型更加接近于销售应用程序所需要使用的存储方式。一些重要的事情需要注意,销售人员不再一次访问多个表了,它们只需要一次访问一个实体集;另外,也不存在所谓的外键;而且模型中的关系被显式的声明。

举一个具体的例子,之前我们查询销售人员的情况时需要做3次链接,就像下面:

SELECT sp.FirstName, sp.LastName, sp.HireDate
FROM SalesPerson sp
INNER JOIN Employee e ON sp.SalesPersonID = e.EmployeeID
INNER JOIN Contact c ON e.EmployeeID = c.ContactID
WHERE e.SalariedFlag = 1 AND e.HireDate >= ‘2006-01-01′

现在我们有了一个高层次的EDM模型,我们能够实现上面的要求,只需要写下一下代码查询SalesPeople 实体的集合就可以:

SELECT sp.FirstName, sp.LastName, sp.HireDate
FROM SalesPeople AS sp
WHERE sp.HireDate >= ‘2006-01-01′

这明显的简单了不少,并且有精确的相同的语义,还附带了关于如何建立合适的数据视图的信息。(EDM schema 和 mapping 我们稍后讨论)。

ADO.NET包含了可视化工具来帮助开发者设计一个架构。工具的输出是XML文件,描述了概念架构(使用架构描述语言或者SDL)。

注意:目前的ADO.NET vNext CTP 还尚未包括可视化设计工具来进行EDM架构设计和映射,现在的版本还需要直接的编辑XML文件。目前的版本还没有包含一个向导来帮助我们生成一个架构或者映射现存的数据库架构;也许这对我们实际的掌握架构有着其他好的意义吧 : )

现在,如果这个新的概念架构不同于实际数据库中的逻辑架构,怎样让系统在不同的架构中穿行呢?答案是“mapping“(映射)。

每一个商业应用程序都有一个显式的或者隐式的概念数据模型,用来描述问题域中的元素,例如这些元素的结构、元素之间的关系、约束和其他信息。

直到现在绝大多数程序都是基于关系型数据库编写的,迟早他们将遇到处理用关系的形式来表述数据的问题。即使有一个高层次的概念模型已经在设计中被使用,该模型一般来说也不是可以直接“运行”的,所以它必须被转译为关系形式并且应用到数据库逻辑架构中和实际编写的代码中。

当关系型模型在最近几十年的时间里表现的极其出色,它的模型的抽象层次的目标经常不能与大多数使用现代开发环境进行开发的商业应用进行很好的融合。

让我们来看一个例子,下面是一段AdventureWorks数据库例子(包含在了Microsoft SQL Server 2005中)的变种:

如果我们在该数据库基础上构建一个人力资源应用程序并且要求找到所有在2006年1月1日之后雇用的全职员工,列出他们的名字和头衔,我们将写下如下的SQL查询语句:

SELECT c.FirstName, e.Title
FROM Employee e
INNER JOIN Contact c ON e.EmployeeID = c.ContactID
WHERE e.SalariedFlag = 1 AND e.HireDate >= ‘2006-01-01′

这个查询有着恶心的复杂性,这是基于下面一些原因的:

  • 当特定的应用只需要处理“employees”,由于逻辑数据库的规范化(范式),“employees”的联系信息被存储为单独的一张表,我们的应用程序必须去处理这些“既定事实”。当这些都不影响应用时,开发者仍然需要包含处理employees所需要的全部知识,这在所有的查询中都是强制要求的。一般的,应用程序不能选择逻辑数据库的架构(例如,部门应用程序从公司的核心数据库中获取信息时),但是怎样将逻辑架构映射到适当的数据视图中的知识,应用程序要求开发者通过查询语句隐含的包括在代码中。
  • 上面的应用程序的例子只处理全职的雇员,太理想化了而没有看到还有其他那么多种雇员形式。尽管如此,只要这是一个共享数据库,所有的employees都在Employe表中,并且使用SalariedFlag字段进行标识区分,因此,这意味着所有的查询都会导致要求应用程序“嵌入”如何区分不同雇员类型的“特定知识”。理想的情况是,如果应用程序处理某个数据的子集,这个系统只呈现出这部分子集的数据,开发者将可以声明怎样的子集才是合适的,用以满足应用需求。

下面这个问题是值得高度关注的:逻辑数据库架构对特定的应用来说并不一定是有着合适的数据视图的。注意在特定的情景中经常使用已有的数据库架构(已经存在于数据库中的表和字段)创建一些更加适合需求的视图。这里有一些其他问题,当构建一个以数据为中心的应用时,我们不容易进行建模,如果我们只使用已有关系数据模型的话。

让我们来看另外一个程序应用,这次是销售系统,也是构建于同样的数据库。使用前面我们使用过的相同的逻辑架构,我们将使用下面的查询来获得那些销售订单超过¥200,00元的销售人员:

SELECT SalesPersonID, FirstName, LastName, HireDate
FROM SalesPerson sp
INNER JOIN Employee e ON sp.SalesPersonID = e.EmployeeID
INNER JOIN Contact c ON e.EmployeeID = c.ContactID
INNER JOIN SalesOrder o ON sp.SalesPersonID = o.SalesPersonID
WHERE e.SalariedFlag = 1 AND o.TotalDue > 200000

再一次,这个查询太“恶心”了~(相对于这样一个概念层次上的简单的问题而言),“恶心”的原因如下:

  • 还是那个原因,逻辑数据库架构“支离破碎”、分片太多,引入了应用程序所不必要的复杂性。在这个例子中,应用程序只是对销售人员“sales persons”和销售订单“sales orders”感兴趣;销售人员的个人信息却分散在3张独立的表中,我们的程序代码这样就必须隐含包括这些知识,虽然它们和我们的目标一点关系都没有。
  • 概念上,我们知道一个销售人员与一些订单有关联,尽管如此,查询还是需要以某些方式进行形式化(就目前我们的知识水平而言);也就是说,查询必须进行显式的链接(因为这些关系,或者成为概概念关联)。

除了上面指出的问题之外,两个查询都表现出了另外一个有趣的问题:它们分别返回的关于雇员和销售人员的信息。尽管如此,你不能向系统询问一个“employee“或者“sales person”。系统没有包含关于“是什么意思”的知识。所有的查询返回值都只是简单的将表中的数据进行拷贝和“发射”,已经丢失了数据源本身的所有关系信息。这意味着在应用程序代码中没有一个关于一些核心应用概览(例如employee)的无差异理解,或者需要外来的充分强的约束作用于这些概念上。进一步说,现在的返回值只是简单的“发射出来的”,数据源描述数据来源的信息被丢失了,要求开发者显式的告诉系统如何插入、更新、删除信息,这些都是使用SQl语句来完成的。

刚才我们讨论的问题主要是两个方面:

  • 关于实体和逻辑模型之间的关系:关系的基础架构不能很好的平衡调节应用程序数据模型的知识概念域,一次它无法理解商业实体、它们之间的关系和那些约束。
  • 实际的问题:数据库的逻辑架构经常不能满足应用需要;这些架构经常不能被很好的适应,因为它们经常被太多的程序所共享或者由于某些非功能性需求(例如操作、数据所有权、性能和安全)——所谓众口难调。

上面描述的问题都是非常具有普遍意义的,特别是对现在企业级的以数据为中心的商业应用。为了解决这些问题,ADO.NET引入了Entity Framework,,它能组成一个数据模型和一组设计时、运行时服务来允许开发者在概念层次上描述应用程序数据和交互,这对商业程序是适合的,并且能够帮助应用程序独立于底层的数据库逻辑架构。