在Entity Framework中使用存储过程(三):逻辑删除的实现与自增长列值返回
本篇文章通过实例的方式,讨论两个在EF使用存储过程的主题:如何通过实体和存储过程的映射实现逻辑删除;对于具有自增长类型主键的数据表,在进行添加操作的时候如何将正确的值反映在实体对象上。
目录 一、基于逻辑删除的数据表和存储过程定义 二、如何过滤逻辑删除记录 三、具有自增长列的存储过程定义 四、通过Result Columns Binding将结果集的列于实体属性进行绑定
一、基于逻辑删除的数据表和存储过程定义
较之物理删除(记录彻底从数据表中清除掉),逻辑删除则继续保留该数据,只是为之进行一个删除标记,表明该记录已经被“删除”了。比如通过下面的SQL,我创建了一个简单的表T_CONTACT表,其中BIT类型的字段IS_DELETED就为这个“删除标记”。
1: CREATE TABLE T_CONTACT
2: (
3: [ID] VARCHAR(50) PRIMARY KEY,
4: [NAME] NVARCHAR(50) NOT NULL,
5: [IS_DELETED] BIT NOT NULL
6: )
那么当我们进行删除操作的存储过程中,不是就行Delete操作,而是进行Update操作,将IS_DELETED的值设置成1即可,这样的存储过程定义如下:
1: CREATE PROCEDURE P_CONTACT_D
2: (@p_id VARCHAR(50))
3: AS
4: BEGIN
5: UPDATE T_CONTACT
6: SET IS_DELETED = 1
7: WHERE ID = @p_id
8: END
二、如何过滤逻辑删除记录
打开VS,通过导入该数据表和CUD存储过程创建.edmx模型,同时修改概念模型实体名称(比如T_CONTACT改成Contact)和属性名称。并删除属性IS_DELETED,最终得到如右图所示的.edmx模型。然后为Contact实体映射CUD存储过程和相关参数,其中删除操作的存储过程已经定义在上面。
然后,你需要考虑这样一个问题:由于我们进行的是逻辑删除,被“删除”的记录依然存储于数据库中。当你进行数据查询的时候,如果没有显式设置IS_DELETED=0为筛选条件的情况下,所有被“删除”的记录依然会被返回。进一步地讲,由于我们在.edmx模型的概念实体Contact中,已经将IS_DELETED删除掉了,所以我们在程序中不可能设置这样一个额外的筛选条件。
实际上EF为你考虑到了这一点,你可以在直接通过EF设计器设置这样一个筛选条件。在当前实体被选中的情况下,进入Mapping Details界面,你会发现在于数据库表的映射中具有一个<Add a Condition>的下拉框,通过该下拉框你可以设置基于数据库表相关列的筛选条件。如下图所示,我设置了筛选条件“IS_DELETED = 0”来过滤掉被逻辑删除的记录。
基于上面的设置编写如下的代码,先添加3条Contact记录,然后将它们删除。并在删除前后根据ID获取对应记录,打印出来以验证上面设计的筛选条件是否真的有效。
1: static void Main(string[] args)
2: {
3: string[] contractIds = new string[] {
4: Guid.NewGuid().ToString(),
5: Guid.NewGuid().ToString(),
6: Guid.NewGuid().ToString() };
7: using (EFExtensionsEntities context = new EFExtensionsEntities())
8: {
9: Contact contact1 = Contact.CreateContact(contractIds[0], "Zhang San");
10: Contact contact2 = Contact.CreateContact(contractIds[1], "Li Si");
11: Contact contact3 = Contact.CreateContact(contractIds[2], "Wang Wu");
12: context.Contacts.AddObject(contact1);
13: context.Contacts.AddObject(contact2);
14: context.Contacts.AddObject(contact3);
15: context.SaveChanges();
16:
17: Console.WriteLine("Before Delete...");
18: foreach(var contact in context.Contacts.Where(c=>contractIds.Contains(c.ID)))
19: {
20: Console.WriteLine("{0}: {1}", contact.ID, contact.Name);
21: }
22: foreach (var contact in context.Contacts.Where(c => contractIds.Contains(c.ID)))
23: {
24: context.Contacts.DeleteObject(contact);
25: }
26: context.SaveChanges();
27:
28: Console.WriteLine("After Delete...");
29: foreach (var contact in context.Contacts.Where(c => contractIds.Contains(c.ID)))
30: {
31: Console.WriteLine("{0}: {1}", contact.ID, contact.Name);
32: }
33: }
34: }
下面是输出结果,可见被删除的记录真的不曾出现在查询结果中。
1: Before Delete...
2: 4032d301-80cb-4e6d-a3e7-f5560e918b4a: Li Si
3: 69b8bdbb-4714-4d68-9619-f4cd587c37ef: Zhang San
4: dbadfef9-d6d2-466b-8eae-392f1d731c14: Wang Wu
5: After Delete...
实际上在数据库中,这三条数据依然存在,只是逻辑删除标识字段IS_DELETED被标记为1。
三、具有自增长列的存储过程定义
接下来我们来讨论另一个常见的场景:如果一个表中存在一个自增长列作为该表的主键,当我们通过提交对应的实体对象进行记录添加操作时,数据库中真正的键值如何返回并赋值给该实体对象。为了模拟这个场景,我重新定义了数据表T_CONTACT的定义,将ID列定义成自增长列。创建该表对应的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: )
如果你希望真正的ID能够返回给被添加的Contact对象,在存储过程中完成添加操作后,应该通过SELECT语句将对应的真实ID返回,这样的存储过程应该这样来写:
1: CREATE PROCEDURE [P_CONTACT_I]
2: @p_name NVARCHAR(50)
3: AS
4: BEGIN
5: INSERT INTO T_CONTACT(NAME, IS_DELETED)
6: VALUES( @p_name, 0)
7:
8: SELECT [ID]
9: FROM T_CONTACT
10: WHERE [ID] = SCOPE_IDENTITY()
11: END
四、通过Result Columns Binding将结果集的列于实体属性进行绑定
在.edmx模型的设计器中,点击右键并再上下文菜单中选择"Update Model From Database…”,让VS重新加载我们修改过的存储过程,然后你需要对存储过程映射关系进行重新设置。由于ID的数据类型改变了,你需要修正Update和Delete存储过程,并改变Contact的ID属性的数据类型从String编程Int32。
为了让存储过程中SELECT语句返回的结果集体现在被提交的Contact对象上,你需要设置列名(或者通过AS操作符设置的别名)与实体类型的属性之间的映射关系。这个关系的定义包含在存储过程映射的Result Columns Binding列表中。如下图所示,我设置了存储过程返回列ID和Contact属性ID之间的映射关系。
基于最新的.edmx模型,我们编写如下的代码,分别创建三个Contact记录。从最终的执行结果,我们可以清晰地看到,从数据库中返回的真实ID反映在了被添加的Contact对象上了。
1: static void Main(string[] args)
2: {
3: using (EFExtensionsEntities context = new EFExtensionsEntities())
4: {
5: Contact contact = new Contact { Name = "Zhang San" };
6: context.Contacts.AddObject(contact);
7: context.SaveChanges();
8: Console.WriteLine("{0}: {1}", contact.ID, contact.Name);
9:
10: contact = new Contact { Name = "Li Si" };
11: context.Contacts.AddObject(contact);
12: context.SaveChanges();
13: Console.WriteLine("{0}: {1}", contact.ID, contact.Name);
14:
15: contact = new Contact { Name = "Wang Wu" };
16: context.Contacts.AddObject(contact);
17: context.SaveChanges();
18: Console.WriteLine("{0}: {1}", contact.ID, contact.Name);
19: }
20: }
执行结果:
1: 10: Zhang San
2: 11: Li Si
3: 12: Wang Wu
在Entity Framework中使用存储过程(一):实现存储过程的自动映射 在Entity Framework中使用存储过程(二):具有继承关系实体的存储过程如何定义? 在Entity Framework中使用存储过程(三):逻辑删除的实现与自增长列值返回 在Entity Framework中使用存储过程(四):如何为Delete存储过程参数赋上Current值? 在Entity Framework中使用存储过程(五):如何通过存储过程维护多对多关系?
- HBitcoin:C#高级比特币钱包库 - 保护您的财产安全
- ofbiz实体引擎(四) ModelReader的作用
- ofbiz实体引擎(三) GenericDelegator实例化的具体过程
- 机器学习实战 | 第五章:模型保存(持久化)
- Python编程任务 | 斯坦福CS231n-深度学习与计算机视觉课程
- ofbiz实体引擎(二) delegator实例化具体方式
- ofbiz实体引擎(一) 获取Delegator
- 12个非常实用的JavaScript小技巧
- 关于PHP浮点数精度损失问题
- FreeMarker与JSP 2.0 + JSTL组合进行比较
- 从零开始学设计模式(1):基础编程模式
- 机器学习实战 | 第四章:模型验证和选择
- ofbiz中FreeMarkerWorker的makeConfiguration方法
- 后台进程(守护进程)自动备份PostgreSQL数据库
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- RabbitMQ集群搭建过程
- java中的reference(一): GC与4种基本的Reference(强软弱虚)
- java中的reference(三): FinalReference和Finalizer的源码分析
- 【ceph】性能测试工具cosbench(1)
- linux命令tree的使用
- 腾讯云V3签名方法之iOS
- Go语言学习之 panic 和 recover
- Go语言学习之函数
- 【Spark Operator】webhook的分析
- 图解人脸识别算法facenet系列(一)
- Go 语言学习之 struct
- Go 语言学习之map
- 前端|利用手机号登录获取手机验证码
- Linux netstat命令结果分析
- setlistmap部分源码解析