在Entity Framework中使用存储过程(二):具有继承关系实体的存储过程如何定义?
在《实现存储过程的自动映射》中,我通过基于T4的代码生成实现了CUD存储过程的自动映射。由于映射的都是基于数据表结构的标准的存储过程,所以它们适合概念模型和存储模型结构相同的场景。如果两种模型存在差异,在进行数据更新操作的时候就会出错。本篇文章主要介绍当概念模型中具有继承关系的两个实体映射到数据库关联的两个表,如何使用存储过程。
目录
一、创建具有继承关系的实体
二、基于继承关系实体的查询与更新
三、映射标准的CUD存储过程
四、修正存储过程
一、创建具有继承关系的实体
假设数据库中有如下两个关联的表:T_EMP和T_SALES。T_EMP用于存储员工信息,主键为代表员工ID号的EMP_ID。为了简单起见,我仅仅定义两个额外的字段:FIRST_NAME和LAST_NAME。另一个表T_EMP用于存储销售人员的信息,它具有一样的主键EMP_ID,额外的两个字段代表负责的区域(Territory)和提成的比率(Commission Rate)。两者通过EMP_ID进行关联。
然后我们通过选择这两个表创建.edmx模型。由于这两个表之间具有关联,.edmx模型得两个实体之间会默认创建联系,你首先需要删除此联系。由于销售人员也是公司的员工,它属于是员工类型的子类。所以你需要建立它们之间的继承关系。由于具有继承关系的两个实体不能有重复的属性,属于你需要删除掉T_SALES的EMP_ID属性。最后你需要修正实体和属性的名称使之更具可读性。最后的.edmx模型如下图所示。
二、基于继承关系实体的查询与更新
在引入存储过程之前,我们先来谈谈针对于如上一个具有继承关系实体的.edmx模型,如果进行查询和更新。使用过EF的读者应该很清楚,客户端代码进行数据的查询和更新都是通过自动生成的一个继承自ObjectContext的类来完成的。我们不妨来看看针对上面创建的.edmx模型,这个类具有怎样的定义。由于我为该模型的Entity Container起名为HrEntities,随后最终生成的是如下一个同名的类。
1: public partial class HrEntities : ObjectContext
2: {
3: public ObjectSet<Employee> Employees{get}
4: public void AddToEmployees(Employee employee)
5: {
6: //...
7: }
8: }
9:
10: public partial class Employee : EntityObject
11: {
12: //...
13: }
14: public partial class Sales : Employee
15: {
16: //...
17: }
我们可以看到,虽然在模型中有两个实体,但是对于HrEntities来说,它仅仅具有一个类型为ObjectSet<Employee>的Employees属性(没有ObjectSet<Sales>类型的属性)和对应的AddToEmployee方法。但是针对这个两个实体对应的类都是存在的,并且存在继承关系。理解起来也容易,Sales也是Employee,所以Employees属性表述的ObjectSet可以同时包括普通的Employee和Sales。
最后我们在一个控制台应用中编写如下一段代码。这段代码中,先删除掉现有的Employee(包括Sales)记录,然后分别添加一个Employee对象和Sales对象。最后通过查询确保它被成功提交到数据库中。
1: static void Main(string[] args)
2: {
3: using (HrEntities context = new HrEntities())
4: {
5: foreach (Employee emp in context.Employees)
6: {
7: context.Employees.DeleteObject(emp);
8: }
9: context.SaveChanges();
10:
11: Employee employee = Employee.CreateEmployee("001", "Jin Nan", "Jiang");
12: Employee sales = Sales.CreateSales("002", "Yan Yan", "Xu", "West", 10);
13: context.AddToEmployees(employee);
14: context.AddToEmployees(sales);
15: context.SaveChanges();
16:
17: foreach (Employee emp in context.Employees)
18: {
19: Console.WriteLine("Employee ID: {0}; First Name: {1}; Last Name: {2}; Is Sales: {3}",
20: emp.ID, emp.FirstName, emp.LastName, (emp is Sales) ? "Yes" : "No");
21: }
22: }
23: }
下面是我们希望的输出结果:
1: Employee ID: 001; First Name: Jin Nan; Last Name: Jiang; Is Sales: No
2: Employee ID: 002; First Name: Yan Yan; Last Name: Xu; Is Sales: Yes
从上面的代码中我们可以看到,当我们通过ObjectContext添加一个Employee对象的时候,它会先根据对象的真实类型,判断仅仅需要添加Employee对应的数据表记录,还是需要同时在Employee和Sales对应的两张数据表中同时添加一条记录。修改和删除操作采用的机制也是如此。
三、映射标准的CUD存储过程
到目前为止,我们的程序运行的很好,现在我们分别Employee和Sales实体映射我们创建的标准的数据表,你可以手工是完成,也可以利用在《实现存储过程的自动映射》提到的代码生成的方式。在这之前我们不妨先来看看我们标准的存储过程长什么模样。下面是基于T_EMP数据表的CUD存储过程。
1: CREATE PROCEDURE [dbo].[P_EMP_I]
2: @p_emp_id VARCHAR(50),
3: @p_first_name NVARCHAR(50),
4: @p_last_name NVARCHAR(50)
5: AS
6: BEGIN
7: INSERT T_Emp(EMP_ID, FIRST_NAME, LAST_NAME)
8: VALUES(@p_emp_id,@p_first_name,@p_last_name)
9: END
10: GO
11:
12: CREATE PROCEDURE [dbo].[P_EMP_U]
13: @o_emp_id VARCHAR(50),
14: @p_first_name NVARCHAR(50),
15: @p_last_name NVARCHAR(50)
16: AS
17: BEGIN
18: UPDATE T_EMP
19: SET FIRST_NAME = @p_first_name, LAST_NAME = @p_last_name
20: WHERE emp_id = @o_emp_id
21: END
22: GO
23:
24: CREATE PROCEDURE [dbo].[P_EMP_D]
25: @o_emp_id VARCHAR(50)
26: AS
27: BEGIN
28: DELETE T_EMP
29: WHERE EMP_ID = @o_emp_id
30: END
31: GO
下面三个是基于T_SALES数据表的三个存储过程。
1: CREATE PROCEDURE [dbo].[P_SALES_I]
2: @p_emp_id VARCHAR(50),
3: @p_territory NVARCHAR(50),
4: @p_commission_rate INT
5: AS
6: BEGIN
7: INSERT T_SALES(EMP_ID, TERRITORY,COMMISSION_RATE)
8: VALUES(@p_emp_id, @p_territory, @p_commission_rate)
9: END
10: GO
11:
12: CREATE PROCEDURE [dbo].[P_SALES_U]
13: @o_emp_id VARCHAR(50),
14: @p_territory NVARCHAR(50),
15: @p_commission_rate INT
16: AS
17: BEGIN
18: UPDATE T_SALES
19: SET TERRITORY = @p_territory,
20: COMMISSION_RATE = @p_commission_rate
21: WHERE emp_id = @o_emp_id
22: END
23: GO
24:
25: CREATE PROCEDURE [dbo].[P_SALES_D]
26: @o_emp_id VARCHAR(50)
27: AS
28: BEGIN
29: DELETE T_SALES
30: where EMP_ID = @o_emp_id
31: END
完成存储过程的映射后,再次运行上面的代码,会有如下一个UpdateException异常抛出来。追踪InnerException,你会发现一条有用的异常消息:The INSERT statement conflicted with the FOREIGN KEY constraint "FK_T_SALES_T_EMP". The conflict occurred in database "EFExtensions", table "dbo.T_EMP", column 'EMP_ID'.The statement has been terminated.
之所以出现上述的异常在于:当我们添加一个Sale对象的时候,只有Sales实体对象的Insert存储过程被执行。而该存储过程仅仅是为T_SALES数据表中插入数据,但是此时主表T_EMP没有相应的记录,违反外键约束。在进行数据的修改和删除时,也有相同的问题。
四、修正存储过程
为了解决这个问题,我们只需要修改子类对应表的存储过程,让它们同时去添加、修改和删除主记录。下面列出了修正后的存储过程定义。
P_SALES_I
1: CREATE PROCEDURE [dbo].[P_SALES_I]
2: @p_emp_id VARCHAR(50),
3: @p_first_name NVARCHAR(50),
4: @p_last_name NVARCHAR(50),
5: @p_territory NVARCHAR(50),
6: @p_commission_rate INT
7: AS
8: BEGIN
9: INSERT T_Emp(EMP_ID, FIRST_NAME, LAST_NAME)
10: VALUES(@p_emp_id,@p_first_name,@p_last_name)
11:
12: INSERT T_SALES(EMP_ID, TERRITORY,COMMISSION_RATE)
13: VALUES(@p_emp_id, @p_territory, @p_commission_rate)
14: END
P_SALES_U
1: CREATE PROCEDURE [dbo].[P_SALES_U]
2: @o_emp_id VARCHAR(50),
3: @p_first_name NVARCHAR(50),
4: @p_last_name NVARCHAR(50),
5: @p_territory NVARCHAR(50),
6: @p_commission_rate INT
7: AS
8: BEGIN
9: UPDATE T_EMP
10: SET FIRST_NAME = @p_first_name,
11: LAST_NAME = @p_last_name
12: WHERE EMP_ID = @o_emp_id
13:
14: UPDATE T_SALES
15: SET TERRITORY = @p_territory,
16: COMMISSION_RATE = @p_commission_rate
17: WHERE emp_id = @o_emp_id
18: END
P_SALES_D
1: CREATE PROCEDURE [dbo].[P_SALES_D]
2: @o_emp_id VARCHAR(50)
3: AS
4: BEGIN
5:
6: DELETE T_SALES
7: WHERE EMP_ID = @o_emp_id
8:
9: DELETE T_EMP
10: WHERE EMP_ID = @o_emp_id
11: END
然后在EF的模型设计器中对新的参数进行映射即可。
在Entity Framework中使用存储过程(一):实现存储过程的自动映射
在Entity Framework中使用存储过程(二):具有继承关系实体的存储过程如何定义?
在Entity Framework中使用存储过程(三):逻辑删除的实现与自增长列值返回
在Entity Framework中使用存储过程(四):如何为Delete存储过程参数赋上Current值?
在Entity Framework中使用存储过程(五):如何通过存储过程维护多对多关系?