在Entity Framework中使用存储过程(四):如何为Delete存储过程参数赋上Current值?

继续讨论EF中使用存储过程的问题,这回着重讨论的是为存储过程的参数进行赋值的问题。说得更加具体一点,是如何为实体映射的Delete存储过程参数进行赋值的问题。关于文中涉及的这个问题,我个人觉得是EF一个有待改进的地方,不知道各位看官是否同意?

目录 一、EF存储过程参数赋值的版本策略 二、Delete存储参数就一定是Original值吗? 三、如果直接修改.edmx模型的XML呢? 四、为Delete存储过程参数赋上Current值,如何做得到?

一、EF存储过程参数赋值的版本策略

和传统的基于DataSet的ADO.NET类似,EF的核心功能之一就是“状态追踪(State Tacking)”。这中间实际上又涉及到两个方面:通过状态决定数据更新的类型(Insert、Update和Delete);以及同时保存不同版本的属性值(Current值和Original值)。版本策略主要是针对Update操作设计的,一般来讲组成Where条件的为Original值,而更新的值为Current值。

正是因为只有Update操作才需要显式指定映射的是实体属性值的版本(Current/Original),所以在进行实体/存储过程映射的时候,只有Update存储过程才可以选择“是否采用原始值(Use Original Value)”。Insert和Delete存储过程默认的版本为Current和Original。反映在VS的.edmx模型设计器上就是:只有Update存储过程的参数映射才具有“Use Original Value”这个复选框。

二、Delete存储参数队应的就一定是Original值吗?

粗略地想想,EF这样设计也无可厚非:Insert存储过程用于添加一条全新的记录,自然应该采用当前值;而Delete存储过程用于删除一条现有的记录,删除操作的筛选条件自然应该使用原始值。但是,我们忽略掉一点:Delete存储过程一定非得执行删除操作吗?如果我进行“逻辑删除”,实际上进行的是Update操作。关于逻辑删除的实现,可以参阅我上一篇文章《逻辑删除的实现与自增长列值返回》。

如果你看了我提到的这篇文章,你可能会问,即使在文中介绍的关于“逻辑删除”的场景中,也没有使用当前值得要求呀。是的,上一篇文章提到的逻辑删除确实也只需要传入实体属性的原始值作为Delete存储过程的参数,现在我们就举一个这样的例子。

通过是使用T_CONTACT这张简单不过的表,同样是采用逻辑删除。不过现在有这样的一个要求,对于条存储在的记录,我们需要记录最后修改者是谁。对于一条被逻辑删除掉的记录,这个最后修改者就是删除掉该条记录的人。这是一个很常见的需求,为此我们可以直接在T_CONTACT的数据表中添加一个新的字段:LAST_UPDATED_BY,创建该表的DDL定义如下:

   1: CREATE TABLE [T_CONTACT]
   2: (
   3:     [ID]                [INT] IDENTITY(1,1)   PRIMARY KEY,
   4:     [NAME]              [NVARCHAR](50)        NOT NULL,
   5:     [IS_DELETED]        [BIT]                 NOT NULL,
   6:     [LAST_UPDATED_BY]   [NVARCHAR](50)        NOT NULL
   7: )

那么对于Delete存储过程,除了指定需要删除的记录的主键之外,还需要将当前用户名作为参数作为传进来。这样的一个存储过程具有如下的定义

   1: CREATE PROCEDURE [dbo].[P_CONTACT_D]
   2: (
   3:  @p_id INT,
   4:  @user_name NVARCHAR(50)
   5:  )
   6: AS
   7: BEGIN
   8:     UPDATE    T_CONTACT
   9:     SET       IS_DELETED = 1, 
  10:               LAST_UPDATED_BY = @user_name
  11:     WHERE     ID = @p_id
  12: END 

在实际操作场景下,我们需要先获取一条现有的Contact记录,然后将其标记为删除。然后Delete存储过程被执行,并且采用预先定义好的实体属性/参数的映射关系来对存储过程的参数进行赋值。但是,由于Delete存储过程默认使用的是实体对象的初始值,即使你在删除之前为Contact对象的LastUpdatedBy属性设置了新的值,该值也不可能传入到存储过程中去。

三、如果直接修改.edmx模型的XML呢?

由于Delete过程只能接受实体的映射属性的初始值作为参数,导致我们无法指定一个新的值作为参数。我想有人会有这样的疑问:VS提供的设计器不能提供你指定Delete存储过程参数版本的功能,你是否可以直接修改.edmx文件的XML呢?我们不妨来尝试一下:

在整个XML中,实体的CUD存储过程映射对应如下一段XML片段,我们可以看到,只有UpdateFunction中的参数映射节点才有Version属性(而且这是一个必需的属性),用于指定参数定义的是Original值还是Current值。

   1: <EntityTypeMapping TypeName="EFExtensionsModel.Contact">
   2:   <ModificationFunctionMapping>
   3:     <InsertFunction FunctionName="EFExtensionsModel.Store.P_CONTACT_I" >
   4:       <ScalarProperty Name="LastUpdatedBy" ParameterName="user_name" />
   5:       <ScalarProperty Name="Name" ParameterName="p_name" />
   6:       <ResultBinding Name="ID" ColumnName="ID" />
   7:     </InsertFunction>
   8:     <UpdateFunction FunctionName="EFExtensionsModel.Store.P_CONTACT_U" >
   9:       <ScalarProperty Name="LastUpdatedBy" ParameterName="user_name" Version="Current" />
  10:       <ScalarProperty Name="Name" ParameterName="p_name" Version="Current" />
  11:       <ScalarProperty Name="ID" ParameterName="p_id" Version="Original" />
  12:     </UpdateFunction>
  13:     <DeleteFunction FunctionName="EFExtensionsModel.Store.P_CONTACT_D" >
  14:       <ScalarProperty Name="LastUpdatedBy" ParameterName="user_name" />
  15:       <ScalarProperty Name="ID" ParameterName="p_id" />
  16:     </DeleteFunction>
  17:   </ModificationFunctionMapping>
  18: </EntityTypeMapping>

那些现在我们将DeleteFunction的user_name参数的映射节点人为地加上Version=“Current”属性设置。

   1: <DeleteFunction FunctionName="EFExtensionsModel.Store.P_CONTACT_D" >
   2:   <ScalarProperty Name="LastUpdatedBy" ParameterName="user_name" Version="Current" />
   3:   <ScalarProperty Name="ID" ParameterName="p_id" />
   4: </DeleteFunction>

但是当你进行编译的时候,会出现如下的错误,明确告诉你:“This function mapping can only contain bindings to 'original' property versions.”

四、为Delete存储过程参数赋上Current值,如何做得到?

从上面的介绍我们不难发现,Delete存储过程不能接受基于当前值得参数映射,并不仅仅是设计器不支持,EF本来就是这样设计的。在这种情况下要实现我们的要求,只有一个办法:将当前值转化成初始值值,这样的转变通过调用ObjectContext的AcceptAllChanges方法可以实现。具体来说,对于需要删除的实体,现设定LastUpdatedBy属性,然后调用AcceptAllChanges方法,然后再调用ObjectStateManager的ChangeObjectState方法将状态设置为Deleted。最终通过调用SaveChanges方法提交更新,具体的代码如下:

   1: static void Main(string[] args)
   2: {
   3:     using (EFExtensionsEntities context = new EFExtensionsEntities())
   4:     {
   5:         Contact contact = new Contact { Name = "Foo", LastUpdatedBy = "Bar" };
   6:         context.Contacts.AddObject(contact);
   7:         context.SaveChanges();
   8:         Console.WriteLine("{0}: {1}", contact.ID, contact.Name);
   9:  
  10:         contact.LastUpdatedBy = "Baz";
  11:         context.AcceptAllChanges();
  12:         context.ObjectStateManager.ChangeObjectState(contact, EntityState.Deleted);
  13:         context.SaveChanges();
  14:     }
  15: }

执行上面的程序后,你会在数据库中发现为删除对象指定的LastUpdatedBy属性“Baz”,而不是初始值“Bar”最终反映在数据库中。

虽然通过“曲线救国”我们可以实现为实体映射的Delete存储过程指定一个“新值”作为某个参数的值,但是这样的做法总觉得不怎么优雅。所以,我个人觉得这是EF一个值得改进的地方,让Delete存储过程和Update一样,也可以指定不同的版本。

在Entity Framework中使用存储过程(一):实现存储过程的自动映射 在Entity Framework中使用存储过程(二):具有继承关系实体的存储过程如何定义? 在Entity Framework中使用存储过程(三):逻辑删除的实现与自增长列值返回 在Entity Framework中使用存储过程(四):如何为Delete存储过程参数赋上Current值? 在Entity Framework中使用存储过程(五):如何通过存储过程维护多对多关系?

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java学习网

Java开发之使用Java 8 Streams 对数据库进行 CRUD 操作

Speedment 是一个开放源代码的工具集,它可以被用来生成 Java 实体,并且能将我们同数据库的通信过程管理起来。你可以利用一个图形工具连接到数据库并生成...

1073
来自专栏安恒网络空间安全讲武堂

从零基础到解题之 Python is the best language

-目录- 前言 环境搭建 源码结构 题目分析 Python is the best language1 Python is the best langua...

2914
来自专栏技术小讲堂

Entity Framework4.3 Code-First基于代码的数据迁移讲解1.建立一个最初的模型和数据库   2.启动Migration(数据迁移)3.第一个数据迁移4.订制的数据迁移4.动态

前段时间一直在研究Entity Framework4,但是苦于没有找到我特别中意的教程,要么就是千篇一律的文章,而且写的特别简单,可以说,糟践了微软这么牛埃克斯...

3448
来自专栏程序员的SOD蜜

PDF.NET(PWMIS数据开发框架)之SQL-MAP目标和规范

SQL-MAP的目标: 集中管理SQL语句,所有SQL语句放在专门的配置文件中进行管理; 通过替换SQL配置文件,达到平滑切换数据库到另外一个数据库,比如从O...

2618
来自专栏Java帮帮-微信公众号-技术文章全总结

Activiti学习详解【面试+工作】

一:Activiti第一天 1:工作流的概念 ? 说明: 1) 假设:这两张图就是XX兄弟的请假流程图 2) 图的组成部分: A. 人物:范XX 冯X刚 王X军...

6315
来自专栏程序猿

带您理解SQLSERVER是如何执行一个查询的

带您理解SQLSERVER是如何执行一个查询的 连接方式和请求 如果你是一个开发者,并且你的程序使用SQLSERVER来做数据库的话 你会想知道当你用你的程序执...

5179
来自专栏智能大石头

5,ORM组件XCode(动手)

本篇才真正是XCode教程第一篇。《速览》是为了以最简洁的语言最短小的篇幅去吸引开发者;《简介》则是对XCode组件和XCode开发模式的一个整体介绍,让开发者...

2149
来自专栏Java帮帮-微信公众号-技术文章全总结

Web-第三十天 Activiti工作流【悟空教程】

工作流(Workflow),就是“业务过程的部分或整体在计算机应用环境下的自动化”,它主要解决的是“使在多个参与者之间按照某种预定义的规则传递文档、信息或任务的...

7873
来自专栏PingCAP的专栏

TiDB 源码阅读系列文章(三)SQL 的一生

上一篇文章讲解了 TiDB 项目的结构以及三个核心部分,本篇文章从 SQL 处理流程出发,介绍哪里是入口,对 SQL 需要做哪些操作,知道一个 SQL 是从哪里...

41715
来自专栏玄魂工作室

看代码学安全(7 )- parse_str函数缺陷

--------------------------------------------------------------------------------...

1121

扫码关注云+社区

领取腾讯云代金券