在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中使用存储过程(五):如何通过存储过程维护多对多关系?

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏维C果糖

详述 SQL 中的 distinct 和 row_number() over() 的区别及用法

1 前言 在咱们编写 SQL 语句操作数据库中的数据的时候,有可能会遇到一些不太爽的问题,例如对于同一字段拥有相同名称的记录,我们只需要显示一条,但实际上数据库...

2367
来自专栏漏斗社区

工具| sqlmap payload修改之路(下)

上周通过一个例子让大家大致了解了sqlmap 如何添加以及修改payload,本周斗哥将带领各位完整地学习sqlmap与payload有关的xml文件下的pay...

4159
来自专栏Jerry的SAP技术分享

使用ABAP正则表达式解析HTML标签

需求就是我用ABAP的某个函数从数据库读取一个字符串出来,该字符串的内容是一个网页。

1222
来自专栏coding

django2.0入门教程第二节

1923
来自专栏恰同学骚年

《T-SQL查询》读书笔记Part 3.索引的基本知识

索引优化是查询优化中最重要的一部分,索引是一种用于排序和搜索的结构,在查找数据时索引可以减少对I/O的需要;当计划中的某些元素需要或是可以利用经过排序的数据时,...

1253
来自专栏SpringBoot 核心技术

第五章:使用QueryDSL与SpringDataJPA实现查询返回自定义对象

5824
来自专栏岑玉海

RavenDb学习(二)简单的增删查改

在上一节当中已经介绍了RavenDb的文档设计模式,这一节我们要具体讲一讲如何使用api去访问RavenDb 1.连接RavenDb var docum...

3915
来自专栏菩提树下的杨过

用JS + WCF打造轻量级WebPart

自打.net2.0起,ms就推出了webPart功能,用它可以轻松开发出具有web2.0风格的个性化网站功能,比如拖放,定制标题栏等,但是WebPart的设计是...

20710
来自专栏Java技术栈

图解 5 种 Join 连接及实战案例!(inner/ left/ right/ full/ cross)

1803
来自专栏张善友的专栏

Linq to SQL 查询Tips

LINQ to SQL当中的灵活的查询操作是其一个很大的优点, 但是当编写较复杂的链接时有时候需要注意一些细节。 1、LINQ to SQL 提供了 DataL...

2119

扫码关注云+社区

领取腾讯云代金券