Entity Framework 与 nHydrate






3.86/5 (6投票s)
使用 nHydrate 模型生成 Entity Framework 数据访问层。
概述
在启动 nHydrate 项目时,您可以从现有的 SQL Server 数据库导入模型,或者从头开始创建。为了简单起见,我们将导入一个模型。导入可以帮助您非常快速地获得一个可用的模型。我们将从我的本地机器导入 AcmeDemo 数据库。
创建项目
在 VS.NET 2010 中,创建一个空白解决方案。拥有解决方案后,在项目资源管理器中右键单击,然后从上下文菜单中选择“添加新项”。从“已安装的模板”左侧栏中选择“模型文件”部分。在右侧列表中选择唯一的选项,即“模型”。这将在您的解决方案中创建一个空白模型,并在 nHydrate 设计器中打开它。
将显示一个向导,询问公司名称和项目名称。按“下一步”按钮后,您可以输入数据库设置并按“导入”按钮。这将加载数据库中的所有表。按“完成”按钮,您将看到加载了所有表的新模型。
第二个向导屏幕允许您设置数据库属性并从中导入。
导入后,您可以选择要添加到模型中的表。
实体差异
您会注意到表的图标不同。导入器会查看关系来确定实体的设置方式。请注意,SYSTEM_USER 和 CUSTOMER 表之间存在一对一关系,SYSTEM_USER 和 EMPLOYEE 表之间也存在一对一关系。这些表集之间还有一个关系和一个共同的主键。有了这个标准,导入器就决定让 CUSTOMER 和 EMPLOYEE 继承自 SYSTEM_USER。前两个表将后者设为其父表。您可以在设计器中更改这一点。您还会注意到 EMPLOYEE_TERRITORY 表的图标不同。这是一个关联表。它是 EMPLOYEE 和 TERRITORY 表之间的中间表,并包含多对多关系。
此模型中还有一个类型表,您必须手动设置。EMPLOYEE_TYPE 表用于存储员工类型。我们必须将 IsTypeTable
和 Immutable
属性设置为 true
。我们还必须在设计器中添加一些静态数据。我们将向此表添加两个值,主键分别为 1 和 2,值为 BigFish
和 Normal
。这将生成一个我们可以用来设置相关表的枚举。无需记住像 1 这样的魔术数字。我们只需将相关外键设置为枚举值 BigFish
。
现在,为了保持一致性、代码美观和最佳实践,我们将更改表和列的名称。生成器引擎使用这些名称来创建对象、属性、方法等。默认情况下,它会从数据库名称生成 Pascal 格式的名称。每个元素都有一个 codefacade
属性,允许您定义映射到数据库名称的代码名称。这会自动完成,方法是替换下划线并进行标题化命名;但是,您可以使用 codefacade
属性为表或属性定义一个全新的名称。在此示例中,我们将使用默认设置。规则引擎的命名格式如下。名为“user_id”的列将在代码中显示为“UserId”。表也有类似的模式。名为“CUSTOMER”的表将在代码中生成为标题化的“Customer”。最佳实践要求所有表都是单数形式,因为在生成的数据库访问层中会有复数形式的集合和列表。如果您有一个名为“CUSTOMERS”的表,您将拥有一个名为“Customers
”的对象和一个名为“CustomersList
”等。的代码。
生成
在“工具”菜单下,您会看到三个新菜单:生成、导入和验证,以及 nHydrate 设置。您可以验证模型以确保没有验证错误。此过程在您实际生成时也会执行。选择“生成”菜单以执行实际生成并观察生成过程。系统会提示您选择要生成的项目。我们只需要数据库安装程序和 Entity Framework 数据访问层 (DAL)。选择这两个选项,然后按“确定”。
由于 nHydrate 已集成到 VS.NET 环境中,所有项目和文件将直接添加到解决方案中。您将看到两个新项目和许多文件添加到项目资源管理器中。您应该能够毫无问题地进行构建。就是这样!您已经有了一个可以用于应用程序的生成框架。
解决方案中添加了两个项目。第一个是“Acme.Demo.Install”。该名称基于公司名称、项目名称,然后是 install。第二个项目是“Acme.Demo.EFDAL”。这是 Entity Framework DAL。
数据库安装程序
首先,让我们处理数据库安装程序。该项目可以直接从环境中使用 .NET 安装实用程序运行。要设置此功能,请右键单击安装项目并选择“属性”菜单。然后单击“调试”选项卡。选中“启动外部程序”单选按钮,并选择 .NET Framework 提供的安装实用程序。在我的机器上,它位于 C:\Windows\Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe。您也可以使用 4.0 框架的路径,但默认情况下,此项目生成为 3.5 项目。在命令行参数框中,输入已编译程序集的名称 Acme.Demo.Install.dll。您现在可以通过按 F5 来运行项目,或者右键单击项目并选择 Debug|Start New Instance。
在运行安装程序之前,我们需要了解它在做什么。它将运行任何生成的脚本,然后在数据库上编译所需的存储过程。生成的脚本之一是 CreateSchema.sql。此文件在数据库上创建所有表、索引、关系等。
nHydrate 框架管理您所有的表、字段、索引等。因此,这些将根据模型中的信息生成。如果您有一个现有的数据库,您需要删除所有索引、关系、主键、默认值等。生成的 Create 脚本将为您处理所有这些,并带有格式良好的名称。如果您需要对现有数据库执行此操作,请将您的 Remove 脚本添加到 FirstRun.sql 文件中。此文件仅在数据库首次升级到 nHydrate 模型时运行。下面是一个可用于执行此操作的 Remove 脚本。
在 Upgrade Scripts\Generated 文件夹中的生成脚本存根文件中,我们需要添加以下脚本来删除这些对象。此文件将由于架构文件而运行。
--DROP ALL INDEXES
declare @schema nvarchar(128), @tbl nvarchar(128), @constraint nvarchar(128)
DECLARE @sql nvarchar(255)
declare cur cursor fast_forward for
select distinct cu.constraint_schema, cu.table_name, cu.constraint_name
from information_schema.table_constraints tc
join information_schema.referential_constraints rc on _
rc.unique_constraint_name = tc.constraint_name
join information_schema.constraint_column_usage cu on _
cu.constraint_name = rc.constraint_name
--where tc.constraint_catalog = @database and tc.table_name = @table
open cur
fetch next from cur into @schema, @tbl, @constraint
while @@fetch_status <> -1
begin
select @sql = 'ALTER TABLE [' + @schema + '].[' + @tbl + '] _
DROP CONSTRAINT [' + @constraint + ']'
exec sp_executesql @sql
fetch next from cur into @schema, @tbl, @constraint
end
close cur
deallocate cur
GO
--DROP ALL DEFAULTS
declare @name nvarchar(128), @parent nvarchar(128)
DECLARE @sql nvarchar(255)
declare cur cursor fast_forward for
select so.[name], sop.[name] as [parentname] from sysobjects _
so inner join sysobjects sop on so.parent_obj = sop.id where so.xtype = 'D'
open cur
fetch next from cur into @name, @parent
while @@fetch_status <> -1
begin
select @sql = 'ALTER TABLE [' + @parent + '] DROP CONSTRAINT [' + @name + ']'
exec sp_executesql @sql
fetch next from cur into @name, @parent
end
close cur
deallocate cur
GO
我们现在可以运行安装程序项目,AcmeDemo 数据库将得到适当的更新。将添加 DAL 操作数据所需的存储过程。整个 CRUD 层都通过这些存储过程进行处理。您无需修改或查看这些脚本。事实上,您永远不应该修改它们,因为每次更改和重新生成模型时都会重新生成它们。
生成的代码
示例应用程序显示了许多如何添加、编辑、选择和删除数据的示例。此处显示了员工添加代码。我们循环并将一些员工添加到 Entity Framework 上下文中并保存。每个对象的属性都分配了任意值。请注意,有一个相关的 EmployeeType
字段,但我们设置了一个枚举而不是一个数字。在数据库中,有一个 EMPLOYEE_TYPE 表,其主键是整数,但我们从未使用它。我们只使用生成的映射。
/// <summary>
/// Add a number of Employees (derived from SYSTEM_USER)
/// </summary>
private void AddEmployees()
{
using (DemoEntities context = new DemoEntities())
{
//Add 10 employees
for (int ii = 0; ii < 10; ii++)
{
//Notice that all fields from Employee are here
//and all base fields from SystemUser are present as well
Employee newItem = new Employee();
newItem.BirthDate = new DateTime(2010, 1, 1);
newItem.Address = "123 Elm Street";
newItem.City = "Atlanta";
//Assign an employee type via Enumeration to this object
//Employee types are defined as a typep table
//and have a generated enum with them
//The database actually holds an integer foreign key
//and a relationship to that type table
if ((rnd.Next(0, 2) % 2) == 0)
newItem.EmployeeType =
Acme.Demo.EFDAL.EmployeeTypeConstants.BigFish;
else
newItem.EmployeeType =
Acme.Demo.EFDAL.EmployeeTypeConstants.Normal;
newItem.HireDate = new DateTime(2010, 2, 1);
newItem.Country = "USA";
newItem.FirstName = "John";
newItem.LastName = "Smith";
newItem.PostalCode = "12345";
context.AddItem(newItem);
}
context.SaveChanges();
}
}
多对多关系也很有趣,因为我们从未见过中间表。在下面的示例中,我们加载了员工列表和地区列表。我们遍历员工列表,并将一个任意的地区对象分配给其 TerritoryList
属性。每个 Employee
对象都有一个 TerritoryList
,而每个 Territory
都有一个 EmployeeList
。
/// <summary>
/// Associate existing Territories with existing Employees
/// </summary>
private void AddEmployeeTerritories()
{
using (DemoEntities context = new DemoEntities())
{
//Get the list of Employees from the database
var employeeList = (from x in context.Employee
select x).ToList();
//Get the list of Territories from the database
var territoryList = (from x in context.Territory
select x).ToList();
//Loop through the Employee list
//and associate it with an arbitrary territory
foreach (Employee employee in employeeList)
{
Territory territory =
territoryList[rnd.Next(0, territoryList.Count)];
if (!employee.TerritoryList.Contains(territory))
employee.TerritoryList.Add(territory);
}
//We could just as easily have run this code
//to do the same thing in the other direction
//foreach (Territory territory in territoryList)
//{
// territory.EmployeeList.Add(
// employeeList[rnd.Next(0, employeeList.Count)]);
//}
int count = context.SaveChanges();
}
}
nHydrate 在 Entity Framework 之上的另一个有趣特性是继承。EF 确实支持继承,但它不允许您像预期的那样选择实体。派生实体不能像真实实体一样被调用。您必须使用指定其类型的语法。下面的两个代码片段展示了语法上的差异。
//Select from a derived table using nHydrate EFDAL
var employees = from x in context.Employee
select x;
//Select from a derived table using default Entity Framework
var employees = from x in context.SystemUser.OfType<Employee>()
select x;
nHydrate 生成器使以面向对象的方式访问数据库变得非常容易。您执行的大部分操作都是标准的 EF 操作。区别在于建模。这种创建代码的方式通过将数据库跟踪直接内置到框架中来增强 Entity Framework。还有其他项目类型可以与同一个模型一起使用。与使用 Entity Framework 的默认功能不同,nHydrate 允许您在使用 EF 的同时增强生成的代码。
有关更多信息,请访问 nHydrate 站点,或阅读我们的博客文章(为什么要在 nHydrate 中实现基于 Entity Framework 的解决方案?)。