EF里单个实体的增查改删以及主从表关联数据的各种增删改查 - lonelyxmas
2015-09-06 17:18
176 查看
原文:http://www.cnblogs.com/oppoic/p/ef_single_entity_crud_related_data_crud.html
本文目录
EF对单个实体的增查改删
增加单个实体
查询单个实体
修改单个实体
删除单个实体
EF里主从表关联数据的各种增删改查
增加(增加从表数据、增加主从表数据)
查询(查询导航属性为集合、查询导航属性为单个对象)
修改(修改从表的外键)
删除(删除主从表关系、删除主表数据、删除主从表数据、修改从表数据外键)
补充内容
SaveChanges方法提交多次操作
DbSet.Add方法返回当前实体
源码和系列文章导航
注:本章节多次演示了各种删除,若要重复查看效果,需解开注释初始化数据的方法。
产生的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表达式的写法,都很简单,下面看复杂点的。
仅添加从表数据:
Lodging是住宿类,有两个类继承本类,分别Resort度假村类和Hostel宿舍类。上面的方法添加了一个Grand Canyon景点的度假村,Name是Pete's Luxury Resort。这里的Grand Canyon是主表数据,Pete's Luxury Resort是从表数据。跟踪到的sql:
这跟单个实体的增加区别就是这样增加出来的数据外键值是有的,不是一个独立的实体了,是一个指向主表某条数据的从表数据。并且注意看sql,最后一列是Discriminator,是用来区分属于哪个表的数据,因为Resort度假村类跟Destination没有直接的关系,而是继承的住宿类Lodging跟Destination是多对一的关系。具体点这里了解。
添加主表数据同时添加相关联的从表数据:
监控到三段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,先介绍两个新的实体:
View Code
??表示前者如果为null就使用后者。很明显,库里不存在ssn为123456789的人,那么程序添加一个新的ssn为123456789的人,添加完毕,这个person对象就是刚刚调用Add方法添加的person。这里并没有调用SaveChanges方法,如果调用SaveChanges方法通过调用person.PersonId还可以获取自增长的主键id。
详细了解请点园长dudu的文章
源码下载
本文目录
EF对单个实体的增查改删
增加单个实体
查询单个实体
修改单个实体
删除单个实体
EF里主从表关联数据的各种增删改查
增加(增加从表数据、增加主从表数据)
查询(查询导航属性为集合、查询导航属性为单个对象)
修改(修改从表的外键)
删除(删除主从表关系、删除主表数据、删除主从表数据、修改从表数据外键)
补充内容
SaveChanges方法提交多次操作
DbSet.Add方法返回当前实体
源码和系列文章导航
注:本章节多次演示了各种删除,若要重复查看效果,需解开注释初始化数据的方法。
一、EF对单个实体的增查改删
增加单个实体:/// <summary> /// 增加单个实体 /// </summary> private static void AddMachuPicchu() { using (var context = new DbContexts.DataAccess.BreakAwayContext()) { var mauchuPicchu = new DbContexts.Model.Destination { Name = "Machu Picchu", Country = "Peru" }; context.Destinations.Add(mauchuPicchu); context.SaveChanges(); } }
产生的insert sql:
exec sp_executesql N'insert [baga].[Locations]([LocationName], [Country], [Description], [Photo], [TravelWarnings], [ClimateInfo]) values (@0, @1, null, null, null, null) select [LocationID] from [baga].[Locations] where @@ROWCOUNT > 0 and [LocationID] = scope_identity()',N'@0 nvarchar(200),@1 nvarchar(max) ',@0=N'Machu Picchu',@1=N'Peru'
查询单个实体:
/// <summary> /// 查询单个实体 /// </summary> private static void GetGreatBarrierReef() { using (var context = new DbContexts.DataAccess.BreakAwayContext()) { var destination = context.Destinations.Find(4); Console.WriteLine(destination.Name); } }
生成的select sql(find方法生成的查询sql略复杂点,普通的linq查询或者Lambda表达式写法就简单许多了):
exec sp_executesql N'SELECT [Limit1].[LocationID] AS [LocationID], [Limit1].[LocationName] AS [LocationName], [Limit1].[Country] AS [Country], [Limit1].[Description] AS [Description], [Limit1].[Photo] AS [Photo], [Limit1].[TravelWarnings] AS [TravelWarnings], [Limit1].[ClimateInfo] AS [ClimateInfo] FROM ( SELECT TOP (2) [Extent1].[LocationID] AS [LocationID], [Extent1].[LocationName] AS [LocationName], [Extent1].[Country] AS [Country], [Extent1].[Description] AS [Description], [Extent1].[Photo] AS [Photo], [Extent1].[TravelWarnings] AS [TravelWarnings], [Extent1].[ClimateInfo] AS [ClimateInfo] FROM [baga].[Locations] AS [Extent1] WHERE [Extent1].[LocationID] = @p0 ) AS [Limit1]',N'@p0 int',@p0=4
修改单个实体:
/// <summary> /// 修改单个实体 /// </summary> private static void ChangeGrandCanyon() { using (var context = new DbContexts.DataAccess.BreakAwayContext()) { var canyon = (from d in context.Destinations where d.Name == "Grand Canyon" select d).Single(); canyon.Description = "227 mile long canyon."; context.SaveChanges(); } }
产生的update sql:
exec sp_executesql N'update [baga].[Locations] set [Description] = @0 where ([LocationID] = @1) ',N'@0 nvarchar(500),@1 int',@0=N'227 mile long canyon.',@1=1
删除单个实体:
/// <summary> /// 删除单个实体 /// </summary> private static void DeleteWineGlassBay() { using (var context = new DbContexts.DataAccess.BreakAwayContext()) { var bay = (from d in context.Destinations where d.Name == "Wine Glass Bay" select d).Single(); context.Destinations.Remove(bay); context.SaveChanges(); } }
产生的delete sql:
exec sp_executesql N'delete [baga].[Locations] where ([LocationID] = @0)',N'@0 int',@0=3
删除方法这样写可能有点效率问题:要删除一个实体,只要知道它的id就可以了,但是上面的方法却先加载了这个实体到内存中,这个是多余的步骤。使用attach方法改进:
/// <summary> /// 删除单个实体attach /// </summary> private static void DeleteWineGlassBayAttach() { using (var context = new DbContexts.DataAccess.BreakAwayContext()) { var toDelete = new DbContexts.Model.Destination { DestinationId = 2 }; context.Destinations.Attach(toDelete); //attach context.Destinations.Remove(toDelete); context.SaveChanges(); } }
自然就没有了先加载实体到内存中的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:
/// <summary> /// 删除单个实体ExecuteSqlCommand /// </summary> private static void DeleteWineGlassBayExecuteSqlCommand() { using (var context = new DbContexts.DataAccess.BreakAwayContext()) { context.Database.ExecuteSqlCommand("delete from baga.Locations where LocationName = 'Hawaii'"); } }
可见,都不需要调用上下文的SaveChanges方法了,因为是直接执行sql,所以并不需要被数据库上下文跟踪到任何状态。
ok,对于单个的增删改查就是这么简单,有Linq的写法,也有Lambda表达式的写法,都很简单,下面看复杂点的。
二、主从表关联数据的各种增删改查
1.增加 主从表数据的添加分为:仅添加从表数据、添加主表同时增加相关联的从表数据仅添加从表数据:
/// <summary> /// 添加从表数据 /// </summary> private static void NewGrandCanyonResort() { using (var context = new DbContexts.DataAccess.BreakAwayContext()) { var resort = new DbContexts.Model.Resort { Name = "Pete's Luxury Resort" }; context.Lodgings.Add(resort); var canyon = (from d in context.Destinations where d.Name == "Grand Canyon" select d).Single(); canyon.Lodgings.Add(resort); context.SaveChanges(); } }
Lodging是住宿类,有两个类继承本类,分别Resort度假村类和Hostel宿舍类。上面的方法添加了一个Grand Canyon景点的度假村,Name是Pete's Luxury Resort。这里的Grand Canyon是主表数据,Pete's Luxury Resort是从表数据。跟踪到的sql:
exec sp_executesql N'insert [dbo].[Lodgings]([Name], [Owner], [MilesFromNearestAirport], [destination_id], [PrimaryContactId], [SecondaryContactId], [Entertainment], [Activities], [MaxPersonsPerRoom], [PrivateRoomsAvailable], [Discriminator]) values (@0, null, @1, @2, null, null, null, null, null, null, @3) select [LodgingId] from [dbo].[Lodgings] where @@ROWCOUNT > 0 and [LodgingId] = scope_identity()',N'@0 nvarchar(200),@1 decimal(18,2),@2 int,@3 nvarchar(128)',@0=N'Pete''s Luxury Resort',@1=0,@2=1,@3=N'Resort'
这跟单个实体的增加区别就是这样增加出来的数据外键值是有的,不是一个独立的实体了,是一个指向主表某条数据的从表数据。并且注意看sql,最后一列是Discriminator,是用来区分属于哪个表的数据,因为Resort度假村类跟Destination没有直接的关系,而是继承的住宿类Lodging跟Destination是多对一的关系。具体点这里了解。
添加主表数据同时添加相关联的从表数据:
/// <summary> /// 添加主表数据同时添加相关联的从表数据 /// </summary> private static void AddSingleAndRelatedData() { using (var context = new DbContexts.DataAccess.BreakAwayContext()) { var destination = new DbContexts.Model.Destination { Name = "AnHui HuangShan", Lodgings = new List<DbContexts.Model.Lodging> { new DbContexts.Model.Lodging {Name="HuangShan Hotel"}, new DbContexts.Model.Lodging {Name="YingKeSong Hotel"} } }; context.Destinations.Add(destination); context.SaveChanges(); } }
监控到三段sql,分别是添加主表数据,和两条添加相关联的从表数据,它们是通过外键destination_id相关联的:
exec sp_executesql N'insert [baga].[Locations]([LocationName], [Country], [Description], [Photo], [TravelWarnings], [ClimateInfo]) values (@0, null, null, null, null, null) select [LocationID] from [baga].[Locations] where @@ROWCOUNT > 0 and [LocationID] = scope_identity()',N'@0 nvarchar(200)',@0=N'AnHui HuangShan'
exec sp_executesql N'insert [dbo].[Lodgings]([Name], [Owner], [MilesFromNearestAirport], [destination_id], [PrimaryContactId], [SecondaryContactId], [Entertainment], [Activities], [MaxPersonsPerRoom], [PrivateRoomsAvailable], [Discriminator]) values (@0, null, @1, @2, null, null, null, null, null, null, @3) select [LodgingId] from [dbo].[Lodgings] where @@ROWCOUNT > 0 and [LodgingId] = scope_identity()',N'@0 nvarchar(200),@1 decimal(18,2),@2 int,@3 nvarchar(128)',@0=N'HuangShan Hotel',@1=0,@2=5,@3=N'Lodging'
exec sp_executesql N'insert [dbo].[Lodgings]([Name], [Owner], [MilesFromNearestAirport], [destination_id], [PrimaryContactId], [SecondaryContactId], [Entertainment], [Activities], [MaxPersonsPerRoom], [PrivateRoomsAvailable], [Discriminator]) values (@0, null, @1, @2, null, null, null, null, null, null, @3) select [LodgingId] from [dbo].[Lodgings] where @@ROWCOUNT > 0 and [LodgingId] = scope_identity()',N'@0 nvarchar(200),@1 decimal(18,2),@2 int,@3 nvarchar(128)',@0=N'YingKeSong Hotel',@1=0,@2=5,@3=N'Lodging'
注意看第一段sql,使用了scope_identity(),这个和ado.net里在每条insert的sql后加上;SELECT @@IDENTITY是一个意思,它会返回自增长的主键id。这里当然是需要返回主键id的,因为后面从表的数据需要用这个当外键。可以复制第一条sql到数据库环境里执行看看效果。
2.查找
根据主表找从表数据(显示加载:先Entry,然后Collection):
2013.09.04修改:之前描述的“根据主表找从表数据”有错误,Collection不是主表找从表,而是找导航属性是一个集合的。
/// <summary> /// 查找导航属性为一个集合的 /// </summary> private static void LoadRelateData() { using (var context = new DbContexts.DataAccess.BreakAwayContext()) { var canyon = (from d in context.Destinations where d.Name == "Grand Canyon" select d).Single(); context.Entry(canyon).Collection(d => d.Lodgings).Load(); //显示加载 foreach (var lodging in context.Lodgings.Local) //遍历的是内存中Lodgings的数据 { Console.WriteLine(lodging.Name); } } }
根据从表找主表数据(显示加载:先Entry,然后Reference):
2013.09.04修改:之前描述的“根据从表找主表数据”有错误,Reference不仅仅是根据从表找主表,而是找导航属性是一个实体对象的,常用于一对多关系里从表对象找主表对象,也可以用于一对一关系的查找。
/// <summary> /// 查找导航属性为一个实体对象的 /// </summary> private static void LoadPrimaryKeyData() { using (var context = new DbContexts.DataAccess.BreakAwayContext()) { var lodging = context.Lodgings.First(); //context.Entry(lodging).Reference(l => l.PrimaryContact).Load(); context.Entry(lodging).Reference(l => l.Destination).Load(); foreach (var destination in context.Destinations.Local) //遍历的是内存中的Destinations数据 { Console.WriteLine(destination.Name); } } }
这是EF标准的查询关联表的数据。如果不看官方的API,大家会怎么查呢?我想是这样:先拿到主表主键id,然后根据id使用find方法(甚至使用ExcuteSqlCommad发送sql)去从表里查,最后得到结果集。从表查主表也一样。这样写有什么不好呢?语句多了不少,其次不是EF建议的写法,我个人还是建议使用Entry配合Collection和Reference方法。
3.修改 修改从表的外键:
/// <summary> /// 修改从表的外键 /// </summary> private static void ChangeLodgingDestination() { using (var context = new DbContexts.DataAccess.BreakAwayContext()) { var hotel = (from l in context.Lodgings where l.Name == "Grand Hotel" select l).Single(); var reef = (from d in context.Destinations where d.Name == "Great Barrier Reef" select d).Single(); hotel.Destination = reef; context.SaveChanges(); } }
Grand Hotel本来的外键是LocationId为1的Grand Canyon,代码把它修改成到了LocationId为4的Great Barrier Reef下。生成的sql简单明了:
exec sp_executesql N'update [dbo].[Lodgings] set [destination_id] = @0 where ([LodgingId] = @1) ',N'@0 int,@1 int',@0=4,@1=1
4.删除
删除分为:删除主从表关系、删除主表数据不删除相关联的从表数据、同时删除主从表数据(级联和不级联删除)、删除主表数据同时修改相关联的从表数据指向另一个主表实体
删除主从表关系:主从表的关系是通过从表的外键列确定的,只需要赋值从表外键列为null即可
/// <summary> /// 删除主从表关系(ForeignKeys方式) /// </summary> private static void RemovePrimaryContactForeignKeys() { using (var context = new DbContexts.DataAccess.BreakAwayContext()) { var davesDump = (from l in context.Lodgings where l.Name == "Dave's Dump" select l).Single(); davesDump.PrimaryContactId = null; context.SaveChanges(); } }
另一种方式:
/// <summary> /// 删除主从表关系(Reference方式) /// </summary> private static void RemovePrimaryContactReference() { using (var context = new DbContexts.DataAccess.BreakAwayContext()) { var davesDump = (from l in context.Lodgings where l.Name == "Dave's Dump" select l).Single(); context.Entry(davesDump).Reference(l => l.PrimaryContact).Load(); //找主表数据 davesDump.PrimaryContact = null; //清空 context.SaveChanges(); } }
住宿类Lodging跟人类Person有一个多对一的关系,这个很好理解,一个人可以有多个酒店。Dave's Dump这个住宿的地方本来本来是PrimaryContactId为1,也就是PersionId为1的这个人的记录,上面的两个方法都是修改这个1为空,即这个Dave's Dump这个住宿的地方不属于任何人了。看看生成的sql:
exec sp_executesql N'update [dbo].[Lodgings] set [PrimaryContactId] = null where ([LodgingId] = @0) ',N'@0 int',@0=2
删除主表数据不删除相关联的从表数据:
ok,先介绍两个新的实体:
/// <summary> /// 有就查询,没有就添加并查询 /// </summary> private static void FindOrAddPerson() { using (var context = new DbContexts.DataAccess.BreakAwayContext()) { var ssn = 123456789; var person = context.People.Find(ssn) ?? context.People.Add(new DbContexts.Model.Person { SocialSecurityNumber = ssn, FirstName = "Phelps", LastName = "Michael" }); Console.WriteLine(person.FirstName); } }
View Code
??表示前者如果为null就使用后者。很明显,库里不存在ssn为123456789的人,那么程序添加一个新的ssn为123456789的人,添加完毕,这个person对象就是刚刚调用Add方法添加的person。这里并没有调用SaveChanges方法,如果调用SaveChanges方法通过调用person.PersonId还可以获取自增长的主键id。
详细了解请点园长dudu的文章
源码下载
相关文章推荐
- java反射
- 2015-0906-osg学习历程6
- [DS][Hash][PAT][Hashing]
- onMeasure流程,MeasureSpec详解
- Spring AspectJ AOP 完整示例
- vi批量转换“{”结尾的代码风格到“{”另起一行的代码风格
- pt-table-checksum
- javaScript 内置对象四 (String)
- 上传文件大小限制
- 腾讯2014校招笔试---软件开发A1
- 自媒体项目架构
- 网络IP地址
- 记一次更换域名ip遇到的坑
- shell 包含文件
- java web 通过ip获取当前地理位置
- java Pattern和Matcher详解
- 文本框删格线
- gdb 调试笔记
- Linux_4day------------搭建samba服务器
- ATS 5.3.0命令行工具traffic_via