65.9K
CodeProject 正在变化。 阅读更多。
Home

nHydrate 与 Entity Framework 对比

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (10投票s)

2010 年 1 月 5 日

Ms-PL

14分钟阅读

viewsIcon

44401

nHydrate 和 Entity Framework 的比较。

引言

nHydrate 生成器和 Entity Framework (EF) 都解决了在软件开发者和数据库之间增加一个生成层(generation layer)的需求。在数据库端有大量的代码曾今需要手动编写。现在有许多产品和应用程序可供您创建这些重复的代码。nHydrate 生成器就是一种解决方案,我们将它与微软的 EF 进行比较。首先要认识到 nHydrate 是一个比 EF 更高级别的开发者产品。EF 为您提供了一个基于数据库构建应用程序的框架平台,而 nHydrate 为您生成了大部分的框架。所以请记住,这并非一个完全可比的比较。您可以在 EF 模型之上编写许多操作和功能,而 nHydrate 开箱即用地生成了这些功能,无需任何自定义代码编写。从这个意义上说,nHydrate 试图为软件架构师和开发人员提供一个应用程序框架的更高层视图,而不是一个简单的数据库快照。

CodePlex 上的 nHydrate 主页

模型

首先,EF 是一个基于松散数据库模型的生成器。模型不必与数据库完全匹配。有无数复杂的映射场景允许您将代码实体映射到物理数据库实现。这非常包容,但几乎不提供指导。本质上,该工具是为了满足所有人的需求。鉴于微软支持几乎整个世界及其所有解决方案,它确实必须如此。生成的代码基于第三方 T4 模板,提供了基本的 CRUD 层功能。

nHydrate 在设计上稍显严格。它也是一个可扩展的框架,但开箱即用就有一套非常强大的模板。您很可能在无需扩展框架的情况下构建整个应用程序。包含的模板基于实际的应用程序开发。

从应用程序的角度来看,EF 的主要问题在于缺乏元数据驱动的单一模型。EF 确实有一个设计画布。这是一个图形模型;然而,区分复杂实体类型和交互的必要元数据非常复杂或不存在。nHydrate 由一个单一模型驱动,允许您塑造实体并定义其相关的复杂性。

存储过程

此外还有安全问题;EF 开箱即用就会运行动态 SQL 查询。这存在一个缺点,不仅导致 SQL 缓存机制无法尽可能有效地工作,而且还需要放松安全限制以允许运行自由格式的 SQL 语句。您需要为以这种方式运行查询的过程启用此权限。当然,这并非无法克服的问题,只是在规划时需要牢记的一点。在 EF 中,可以通过映射到存储过程来克服此限制。这是一个略显麻烦的过程,但可以消除 EF 默认功能的安全性漏洞。nHydrate 默认生成存储过程,并使用它们来构建 CRUD 层。这不仅确保了最快的数据访问(得益于 SQL 查询计划),而且还避免了您手动编写存储过程的麻烦。对于最小的实体来说,这是一个麻烦且耗时的过程;然而,对于继承的实体来说,这根本不令人愉快。

继承

这实际上将我们带到了继承实体的情况。在 EF 中,有两种表示继承层次结构的方法。首先,您可以有一个包含大量可空字段的“怪物表”,并通过一个鉴别器列来确定实际的对象类型。这不仅比必要的使用更多的空间,而且创建了对非开发人员、报表编写者等更复杂的对象。nHydrate 通过为每种对象类型使用自己的表并通过表之间的关系来维护简单性。EF 和 nHydrate 之间的主要区别在于设置继承层次结构的易用性。在 EF 中,加载所有基表然后将字段重新排列成不同的实体表示,直到构建了一个新的实体对象,这有点笨拙。新继承实体中的所有字段都必须映射回正确的基表。使用 nHydrate,您只需定义一个表并添加字段。然后,您可以定义另一个表示继承实体的表并添加它将包含的额外字段。要定义继承,只需将子表的父表属性设置为父表。在两者之间创建关系,就完成了。就是这么简单。

所有需要对所有表执行 CRUD 功能的存储过程(可能存在 N 级继承)都会自动生成。标准 CRUD 操作不会创建动态 SQL。这一切都是隐含的,没有复杂的映射来完成此操作。

实体拆分

EF 采用实体拆分来实现继承。这是一个手动且繁琐的过程,涉及拆分表之间的字段、映射关系以及创建新实体。nHydrate 完全用每个表的父表属性取代了整个过程。使用此属性和关系,您可以定义复杂的继承层次结构,而无需其他设置。

数据并发和审计

如果您费尽周折在 EF 中手动定义存储过程,然后将其映射到正确的实体字段,仍然存在并发问题。nHydrate 从一开始就内置了并发支持。事实上,您必须选择退出使用并发,因为它属于默认功能。所有表默认都会有创建和修改的审计字段,允许您跟踪更改。还有一个隐藏的时间戳字段用于并发。如果并发失败,数据将无法保存到数据库。这使您能够使用更高效的乐观并发方法来同步数据库。

除了上述功能外,还有一个功能可以跟踪所有行随时间的所有更改。您可以定义一个表,在每次保存或删除之前保存行的副本。这允许您拥有所有时间的所有更改的快照。实际上,所有添加、修改和删除都会记录到一个不存在于模型中的影子表中,您可以查询该表来查找对行的任何修改。这甚至扩展到了已删除的行。此功能可用于数据验证、备份、记录保存等。

语法

nHydrate 采用更优化的语法来操作实体。在 EF 中,您必须首先声明模型,然后使用它来选择所需项。nHydrate 使用 static 方法在一行代码中返回所需数据。此外,返回的集合对象在其上直接具有许多便捷函数。它们之间的 where 语法是相似的,都使用 LINQ。EF 可以使用内联 LINQ,如下面的示例所示。nHydrate 始终使用参数 where 语句,尽管它仍然是 LINQ。

//Entity Framework
MyModel model = new MyModel();
var q = from x in model.Customers
				select x;

//nHydrate
CustomerCollection customerCollection = CustomerCollection.RunSelect();

当使用 EF 中使用类型 per hierarchy 设置的继承对象时,还需要添加“OfType”关键字。与 nHydrate 语法相比,这是一个笨拙的语法,nHydrate 语法只是以上述相同方式指定子表。

nHydrate 还包含许多聚合和批量更新功能。EF 中没有相应的对应功能。这使得您通常可以在一行代码中执行复杂的操作。

//Bulk Update the Customer.City to the 
//new value 'Berlin' where Customer.FirstName = 'Sally'
CustomerCollection.UpdateData(
	x => x.City,
	x => x.FirstName == "Sally",
	"Berlin");

//Delete all rows where Customer.FirstName = 'Sally'
CustomerCollection.DeleteData(x => x.FirstName == "Sally");

数据库创建和更新

有一个内置的生成项目用于数据库创建和更新。这允许您直接从 VS.NET 环境创建数据库,或者在需要时将其功能包含在单独的应用程序安装程序中。此功能也扩展到数据库更新。所有数据库都已版本化,因此您可以定义更新脚本,将较低版本转换为较高版本。升级脚本大部分是自动的,因此如果您添加或删除表或字段,会自动创建相应的修改脚本。当然,对于复杂更改存在一些例外情况,但对于大多数情况,这都可以正常工作。版本控制允许您为模型拥有多个企业数据库,并根据需要一次升级它们,并且所有升级都由安装程序自动处理。对于任何管理多版本数据库部署的人来说,这都是一个福音。

复杂实体

由于 nHydrate 模型上有元数据,您可以定义实体的复合功能。例如,实体可以定义为不可变的,这样它们就无法被修改或持久化。更有用的是定义类型表的能力。这些实体没有其他目的,只是作为其他表的鉴别器。这可能包括用户类型,如果您有不同类型的用户,如客户、员工、经理等。它们都在您的用户表中,但您需要一个类型来区分。类型表不仅包含 static 数据且不可变,它们还会生成一个枚举,因此您永远不会在代码中使用“魔术数字”。所有相关的表当然都有外键字段,但还有一个枚举字段,可以用强类型枚举设置进行设置。

//Set the customer type with enum NOT the actual user type primary key 
customer.UserType = UserTypeConstants.Customer;

另一个复杂实体是关联表。它连接了参与多对多关系的两个表。由于关系数据库不处理 N:N 关系,您必须创建一个中间表来促进这种关系类型。nHydrate 完全为您处理了这种映射。您只需将中间表添加到模型中,并定义字段和与它的关系。在生成的代码中,您永远不会看到该表。表 1 将拥有表 2 对象的列表,表 2 将拥有表 1 对象的列表。关联表是不可见的。这是标准 ORM 工具处理此情况方式的重大改进。

分页

两个平台都支持分页。EF 的语法比 nHydrate 的语法更繁琐一些。nHydrate 支持强类型分页对象,以完全定义如何分页结果集。这是该平台真正闪耀的地方。分页从一开始就内置了。它被设计为具有简单的语法进行分页。下面的代码片段展示了如何检索一个按一个字段排序的分页结果集。当然,您可以使用任一框架按任意数量的字段排序。

//Entity framework - cumbersome syntax
int pageNumber = 1;
int pageSize = 20;
using (var model = new MyModel())
{
	//Get the recordSet
	var recordSet = (from x in model.UserAccount
				 where x.UserId > 100
				 orderby x.UserId
				 select x).
				 Skip((pageNumber - 1) * pageSize).
				 Take(pageSize);
}

//nHydrate - declare a paging object and load data
UserAccountPaging paging = new UserAccountPaging(1, 20,	
			UserAccount.FieldNameConstants.UserId, true);
UserAccountCollection recordSet = 
	UserAccountCollection.RunSelect(x => x.UserId > 100, paging);

Components

使用 EF,您可以使用实体拆分来创建表的子集,这样您就不需要从数据库行加载所有数据。这对于具有宽表或包含 blob 字段或图像的旧系统特别有用。您必须创建一个实体并删除多余的字段,或将新字段映射到该实体。字段映射的所有复杂性仍然适用。使用 nHydrate,您可以选择一个表,右键单击在其下创建一个 Component 对象。然后从提供的列表中选择组件的字段,并为对象命名。这就是创建表组件所需的所有操作。

持久化

两个平台设计方式的一个很好的例子就是它们如何保存数据。EF 为您提供了处理其底层对象方式的灵活性,您可以编写包装器或扩展程序来隐藏其处理的复杂性。但当然,这需要编写更多的代码。nHydrate 有一个简单而强大的容器,允许您在几行甚至一行代码中加载、更改并将更改持久化到数据库。

下面是我从微软网站上摘录的一些代码。它演示了如何获取一个订单记录并为其添加一个新的订单明细项,然后保存所有更改。

SalesOrderHeader order =
    context.SalesOrderHeader.Where
    ("it.SalesOrderID = @id", new ObjectParameter(
     "id", orderId)).First();

// Change the status and ship date of an existing order.
order.Status = 1;
order.ShipDate = DateTime.Today;

// Load items for the order, if not already loaded.
if (!order.SalesOrderDetail.IsLoaded)
{
    order.SalesOrderDetail.Load();
}

// Delete the first item in the order.
context.DeleteObject(order.SalesOrderDetail.First());

// Create a new item using the static Create method 
// and add it to the order.
order.SalesOrderDetail.Add(
    SalesOrderDetail.CreateSalesOrderDetail(0,
    0, 2, 750, 1, (decimal)2171.2942, 0, 0,
    Guid.NewGuid(), DateTime.Today));

// Save changes in the object context to the database.
int changes = context.SaveChanges();

使用 nHydrate 也很类似,但选择语法更简单一些。另外,子对象的创建更直接易读。

//Load the order by ID
Order order = OrderCollection.RunSelect(x => x.OrderId == orderId)[0];
SubDomain subdomain = order.ParentCollection.SubDomain;

// Change the status and ship date of an existing order.
order.Status = 1;
order.ShipDate = DateTime.Today;

// Delete the first item in the order.
order.OrderDetailList[0].Delete();

// Get a reference to the OrderDetail collection in the same container
OrderDetailCollection orderDetailCollection = 
		order.ParentCollection.SubDomain.GetCollection();

OrderDetail orderDetail = orderDetailCollection.NewItem();
orderDetail.OrderId = order.OrderId;
orderDetail.Prop1 = 0;
orderDetail.Prop2 = 2;
orderDetail.Prop3 = 750;
//etc...
orderDetailCollection.AddItem(orderDetail);			

// Save changes in the container to the database.
subdomain.Persist();

依赖遍历

遍历关系也是 nHydrate 平台闪耀之处。基于模型实体和关系,您可以遍历对象图。EF 当然也提供相同的功能,但您必须明确告诉它加载对象。

using (var model = new MyModel())
{
	//Get the recordSet
	var recordSet = (from x in model.UserAccount
				 where x.UserId > 100
				 orderby x.UserId
				 select x);

	foreach (UserAccount userAccount in recordSet)
	{
		userAccount.Transaction.Load(); //Explicitly load
		foreach (Transaction t in userAccount.Transaction.ToList())
		{
			System.Diagnostics.Debug.Write("");
		}
	}
}

使用 nHydrate 时,遍历关系可以用更简单的语法完成。

//Get the recordSet
UserAccountCollection recordSet = UserAccountCollection.RunSelect(x =>
	x.UserId > 100);

foreach (UserAccount userAccount in recordSet)
{
	foreach (Transaction t in userAccount.TransactionList)
	{
		System.Diagnostics.Debug.Write("");
	}
}

两者都允许您加载一个对象,然后上下遍历关系图以惰性加载层次结构。提供此功能所需的所有存储过程都作为框架的一部分生成。一切都是真正透明的。无需任何设置,因为所有遍历都由模型中的实体及其之间的关系决定。如果更改模型,所有新的遍历代码都会为您生成。所有存储过程的底层管道都自动添加到安装项目中,并在升级数据库时可用。

相关项目

nHydrate 提供了许多项目,可以帮助您立即构建应用程序。不仅有一个数据访问层(DAL),本文正在讨论它,还有一个数据传输层(DTO)以及其他控制反转(IoC)项目,可以构建复杂的应用程序层次结构,无需额外的框架代码。目前该平台可以生成十个项目,这些项目可以立即用于构建应用程序代码。

领域层独立性

IoC 模式层允许加载多种不同类型的域。这使得可以在同一代码库上开发 Web、Windows 或移动应用程序。所有开发都可以针对 DTO 层进行,后端可以替换为 DAL(直接连接)、WCF 或模拟层。nHydrate 附带的默认 IoC 代理意味着您有一个标准的 API 来检索数据。如果需要更具体或自定义的后端,您可以编写一个基于 EF 或 nHibernate 的专有生成器。

尽管 nHydrate 的当前实现是基于 ADO.NET 对象构建的,但如果需要,也可以用它来编写一个基于 EF 的生成层。这是一个设计标准模式并从中创建模板的问题。当前的源代码生成模板也是以这种方式构建的。这实际上是 EF 的目的,作为其他更具体的应用程序框架的基础。nHydrate 未来的目标是提供多种后端数据访问技术,这些技术可以在健壮的 API 上实现互换。

杂项

一个不正确或正确,但很方便的小细节是如何处理复数。当前的 nHydrate 复数方案只是在相关的集合中添加“List”一词。这有很多好处。首先,它是标准的,因此当您有数百个实体时,您无需考虑对象是复数还是单数。您很快就会习惯所有“Lists”都可以被枚举。其次,这可以避免您为模型中的所有实体手动设置复数。第三,当您重命名实体时,就像在应用程序开发中发生的那样,您无需每次都记住更改两个属性来保持同步。最后,一致性很重要。Person 表的复数形式可能是 PeoplePersonsPersonListPersonCollectionPeopleListPeopleCollection 等。如果您的模型中有数百个实体和许多开发人员,如果不采用随意的复数形式,就必然会出现命名不一致。

摘要

在许多情况下,EF 将是首选平台,但要正确有效地设置应用程序框架需要学习曲线。这绝非易事。EF 在数据库映射和图形设计方面非常出色,但默认功能却很 sparse。您需要编写大量代码才能启动并运行一个可用的框架。nHydrate 试图通过生成许多默认情况下您需要的便捷功能来缓解这个问题。EF 是一个可扩展的平台,允许您在其之上构建自己的框架。这与 nHydrate 的方法不同,nHydrate 试图让您更快地开始编码,学习曲线更短。两个平台都解决了所有开发人员的痛点——重复的数据库代码。然而,EF 为您提供了一个构建框架的平台,而 nHydrate 则为您提供了一个已构建好的框架。

构思、建模、生成!

历史

  • 2010年1月5日:初稿
© . All rights reserved.