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

Entity Framework 与 nHydrate

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.86/5 (6投票s)

2010年7月14日

Ms-PL

7分钟阅读

viewsIcon

31448

downloadIcon

175

使用 nHydrate 模型生成 Entity Framework 数据访问层。

概述

在启动 nHydrate 项目时,您可以从现有的 SQL Server 数据库导入模型,或者从头开始创建。为了简单起见,我们将导入一个模型。导入可以帮助您非常快速地获得一个可用的模型。我们将从我的本地机器导入 AcmeDemo 数据库。

创建项目

在 VS.NET 2010 中,创建一个空白解决方案。拥有解决方案后,在项目资源管理器中右键单击,然后从上下文菜单中选择“添加新项”。从“已安装的模板”左侧栏中选择“模型文件”部分。在右侧列表中选择唯一的选项,即“模型”。这将在您的解决方案中创建一个空白模型,并在 nHydrate 设计器中打开它。

将显示一个向导,询问公司名称和项目名称。按“下一步”按钮后,您可以输入数据库设置并按“导入”按钮。这将加载数据库中的所有表。按“完成”按钮,您将看到加载了所有表的新模型。

Wizard 1

第二个向导屏幕允许您设置数据库属性并从中导入。

Wizard 2

导入后,您可以选择要添加到模型中的表。

Wizard 3

实体差异

您会注意到表的图标不同。导入器会查看关系来确定实体的设置方式。请注意,SYSTEM_USER 和 CUSTOMER 表之间存在一对一关系,SYSTEM_USER 和 EMPLOYEE 表之间也存在一对一关系。这些表集之间还有一个关系和一个共同的主键。有了这个标准,导入器就决定让 CUSTOMER 和 EMPLOYEE 继承自 SYSTEM_USER。前两个表将后者设为其父表。您可以在设计器中更改这一点。您还会注意到 EMPLOYEE_TERRITORY 表的图标不同。这是一个关联表。它是 EMPLOYEE 和 TERRITORY 表之间的中间表,并包含多对多关系。

New Model

此模型中还有一个类型表,您必须手动设置。EMPLOYEE_TYPE 表用于存储员工类型。我们必须将 IsTypeTableImmutable 属性设置为 true。我们还必须在设计器中添加一些静态数据。我们将向此表添加两个值,主键分别为 1 和 2,值为 BigFishNormal。这将生成一个我们可以用来设置相关表的枚举。无需记住像 1 这样的魔术数字。我们只需将相关外键设置为枚举值 BigFish

现在,为了保持一致性、代码美观和最佳实践,我们将更改表和列的名称。生成器引擎使用这些名称来创建对象、属性、方法等。默认情况下,它会从数据库名称生成 Pascal 格式的名称。每个元素都有一个 codefacade 属性,允许您定义映射到数据库名称的代码名称。这会自动完成,方法是替换下划线并进行标题化命名;但是,您可以使用 codefacade 属性为表或属性定义一个全新的名称。在此示例中,我们将使用默认设置。规则引擎的命名格式如下。名为“user_id”的列将在代码中显示为“UserId”。表也有类似的模式。名为“CUSTOMER”的表将在代码中生成为标题化的“Customer”。最佳实践要求所有表都是单数形式,因为在生成的数据库访问层中会有复数形式的集合和列表。如果您有一个名为“CUSTOMERS”的表,您将拥有一个名为“Customers”的对象和一个名为“CustomersList”等。的代码。

生成

在“工具”菜单下,您会看到三个新菜单:生成、导入和验证,以及 nHydrate 设置。您可以验证模型以确保没有验证错误。此过程在您实际生成时也会执行。选择“生成”菜单以执行实际生成并观察生成过程。系统会提示您选择要生成的项目。我们只需要数据库安装程序和 Entity Framework 数据访问层 (DAL)。选择这两个选项,然后按“确定”。

Generator Dialog

由于 nHydrate 已集成到 VS.NET 环境中,所有项目和文件将直接添加到解决方案中。您将看到两个新项目和许多文件添加到项目资源管理器中。您应该能够毫无问题地进行构建。就是这样!您已经有了一个可以用于应用程序的生成框架。

解决方案中添加了两个项目。第一个是“Acme.Demo.Install”。该名称基于公司名称、项目名称,然后是 install。第二个项目是“Acme.Demo.EFDAL”。这是 Entity Framework DAL。

Solution Explorer

数据库安装程序

首先,让我们处理数据库安装程序。该项目可以直接从环境中使用 .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 的解决方案?)。

© . All rights reserved.