天天看点

EF里单个实体的增查改删以及主从表关联数据的各种增删改查

本文目录

<a href="#1">EF对单个实体的增查改删</a>

<a href="#1_1">增加单个实体</a>

<a href="#1_2">查询单个实体</a>

<a href="#1_3">修改单个实体</a>

<a href="#1_4">删除单个实体</a>

<a href="#2">EF里主从表关联数据的各种增删改查</a>

<a href="#2_1">增加(增加从表数据、增加主从表数据)</a>

<a href="#2_2">查询(查询导航属性为集合、查询导航属性为单个对象)</a>

<a href="#2_3">修改(修改从表的外键)</a>

<a href="#2_4">删除(删除主从表关系、删除主表数据、删除主从表数据、修改从表数据外键)</a>

<a href="#3">补充内容</a>

<a href="#3_1">SaveChanges方法提交多次操作</a>

<a href="#3_2">DbSet.Add方法返回当前实体</a>

<a href="#4">源码和系列文章导航</a>

注:本章节多次演示了各种删除,若要重复查看效果,需解开注释初始化数据的方法。

<a></a>

增加单个实体:

产生的insert sql:

查询单个实体:

生成的select sql(find方法生成的查询sql略复杂点,普通的linq查询或者Lambda表达式写法就简单许多了):

修改单个实体:

产生的update sql:

删除单个实体:

产生的delete sql:

删除方法这样写可能有点效率问题:要删除一个实体,只要知道它的id就可以了,但是上面的方法却先加载了这个实体到内存中,这个是多余的步骤。使用attach方法改进:

自然就没有了先加载实体到内存中的sql,只有一个简单的删除sql。attach方法是让EF知道DestinationId为2的实体是一个存在的实体。当然不使用attach,直接调用Remove方法删除会报一个InvalidOperationException错:无法删除此对象,因为未在 ObjectStateManager中找到它。

attach中文意为“连接、附上、贴上”等意思。

注:Attach的实体事先不能已经在内存中,否则上下文会追踪到两个相同键名的实体,并且会报一个InvalidOperationException错:An object with the same key already exists in the ObjectStateManager.

还有一种不加载实体到内存就可以删除实体的简单方法,用EF直接执行sql:

可见,都不需要调用上下文的SaveChanges方法了,因为是直接执行sql,所以并不需要被数据库上下文跟踪到任何状态。

ok,对于单个的增删改查就是这么简单,有Linq的写法,也有Lambda表达式的写法,都很简单,下面看复杂点的。

1.增加

主从表数据的添加分为:仅添加从表数据、添加主表同时增加相关联的从表数据

仅添加从表数据:

Lodging是住宿类,有两个类继承本类,分别Resort度假村类和Hostel宿舍类。上面的方法添加了一个Grand Canyon景点的度假村,Name是Pete's Luxury Resort。这里的Grand Canyon是主表数据,Pete's Luxury Resort是从表数据。跟踪到的sql:

添加主表数据同时添加相关联的从表数据:

监控到三段sql,分别是添加主表数据,和两条添加相关联的从表数据,它们是通过外键destination_id相关联的:

注意看第一段sql,使用了scope_identity(),这个和ado.net里在每条insert的sql后加上;SELECT @@IDENTITY是一个意思,它会返回自增长的主键id。这里当然是需要返回主键id的,因为后面从表的数据需要用这个当外键。可以复制第一条sql到数据库环境里执行看看效果。

2.查找

根据主表找从表数据(显示加载:先Entry,然后Collection):

2013.09.04修改:之前描述的“根据主表找从表数据”有错误,Collection不是主表找从表,而是找导航属性是一个集合的。

根据从表找主表数据(显示加载:先Entry,然后Reference):

2013.09.04修改:之前描述的“根据从表找主表数据”有错误,Reference不仅仅是根据从表找主表,而是找导航属性是一个实体对象的,常用于一对多关系里从表对象找主表对象,也可以用于一对一关系的查找。

这是EF标准的查询关联表的数据。如果不看官方的API,大家会怎么查呢?我想是这样:先拿到主表主键id,然后根据id使用find方法(甚至使用ExcuteSqlCommad发送sql)去从表里查,最后得到结果集。从表查主表也一样。这样写有什么不好呢?语句多了不少,其次不是EF建议的写法,我个人还是建议使用Entry配合Collection和Reference方法。

3.修改

修改从表的外键:

Grand Hotel本来的外键是LocationId为1的Grand Canyon,代码把它修改成到了LocationId为4的Great Barrier Reef下。生成的sql简单明了:

4.删除

删除分为:删除主从表关系、删除主表数据不删除相关联的从表数据、同时删除主从表数据(级联和不级联删除)、删除主表数据同时修改相关联的从表数据指向另一个主表实体

删除主从表关系:主从表的关系是通过从表的外键列确定的,只需要赋值从表外键列为null即可

另一种方式:

住宿类Lodging跟人类Person有一个多对一的关系,这个很好理解,一个人可以有多个酒店。Dave's Dump这个住宿的地方本来本来是PrimaryContactId为1,也就是PersionId为1的这个人的记录,上面的两个方法都是修改这个1为空,即这个Dave's Dump这个住宿的地方不属于任何人了。看看生成的sql:

删除主表数据不删除相关联的从表数据:

ok,先介绍两个新的实体:

没有配置任何Data Annotation和Fluent API。两个实体的关系是通过Reservation类的Trip导航属性确立的。很明显,这是一个一对一的关系,且预约类Reservation的外键Trip_Identifier是可空的(为何生成的外键名是Trip_Identifier?EF默认映射是取主表实体类名字加主键列),意思很明确,就是预约表Reservations的数据可以对应到旅行表Trip,也可以不对应:

EF里单个实体的增查改删以及主从表关联数据的各种增删改查
EF里单个实体的增查改删以及主从表关联数据的各种增删改查

看看这两张表在数据库里有的数据:

EF里单个实体的增查改删以及主从表关联数据的各种增删改查

很明显,Reservations预约表的Trip_Identifier列(guid类型)指向了Trips表的主键列Identifier。试着删除:

根据Description列的内容从数据库取出主表Trips的某条数据,然后直接调用上下文的Remove方法删除。程序跑起来会报一个DbUpdateException错:

EF里单个实体的增查改删以及主从表关联数据的各种增删改查

违反了主外键的约束。这个很好理解:从表的某条数据指向主表的这条数据,主表的这条数据自然不能随便删除。修改下方法,删除主表某条数据,同时加载其关联的从表数据:

看看这几行代码生成了多少sql:

1.查出主表数据:

2.查出从表数据:

3.更新从表的外键为null:

4.删除主表数据:

看完了你肯定会想EF删除主表数据真麻烦:同时加载主表和从表的数据,然后设置从表外键为null让它不指向主表任何数据,然后再删除主表数据。

正常的思维删除主表数据是这样的:取出主表的主键字段,然后根据主键去从表里找,看看有没有相关联的数据,有就赋值外键为null,最后删除主表数据。写出来无非就是各种find,然后update,最后delete。这是正常的思维和写法,但是缺点很明显:比上面的方法多写了很多代码。

所以,还是按照EF的思路来:删除主表数据,就同时加载主表和从表数据到内存中再执行删除主表数据的操作。只需要直接调用Remove方法就好,EF自动把从表的相关数据外键列设置为null。

删除主表数据同时删除相关联的从表数据(级联删除)

因为Destination类和Lodging类已经设置好了级联删除,所以直接找到主键删除即可,相关联的从表数据由数据库自动删除:

删除主表数据同时删除相关联的从表数据(非级联删除)

标注每个从表的数据为删除状态,然后调用数据库上下文的SaveChanges方法:

删除主表数据同时修改相关联的从表数据指向另一个主表实体:

补充内容:以上所有的演示调用SaveChanges都是提交一个更改,试着提交多个操作:

增加一个Destinations表对象,又修改了一个对象,跟踪下sql发现很明确的是一条insert,一条update的sql。SaveChanges也是一个事务,如果一个不成功,那么所有都提交不成功。

仔细看上面的DbSet.Add方法可知,DbSet.Add方法返回的对象就是添加的实体对象,上面的Add方法返回的就是DbContexts.Model.Destination。这个给编码提供了很好的便利性,来看一个方法: