使用EF自带的EntityState枚举和自定义枚举实现单个和多个实体的增删改查
2013-09-02 09:49
441 查看
本文目录
使用EntityState枚举实现单个实体的增/删/改
增加:DbSet.Add = > EntityState.Added
标记实体为未改变:EntityState.Unchanged
修改:EntityState.Modified
删除:DbSet.Remove = > EntityState.Deleted
EF里实体状态的递归(recursive)
不被上下文追踪的情况下实现增删改操作
让实体实现自定义的IObjectWithState接口来设置实体状态
通用的转换实体状态方法
本文源码和系列文章目录
之前使用EF都是通过调用SaveChanges方法把增加/修改/删除的数据提交到数据库,但是上下文是如何知道实体对象是增加、修改还是删除呢?答案是通过EntityState枚举来判断的,看一个方法:
注:使用EntityState需添加引用system.data
跑下程序,输出结果为:Unchanged。从英文意思便可以猜到一二:取出来的数据是Unchanged,那么添加、修改、删除自然也就是Added、Modified、Deleted了。在EntityState上按F12定位到其定义看看:
View Code
【第二段sql】
【第三段sql】
【第四段sql】
【第五段sql】
跟踪下上面方法里的实体状态:
的确如我们所需:主表数据是修改、从表数据一个修改一个添加一个删除。这样再调用上下文的SaveChanges方法的时候都能正确的提交数据库。看到这里可能疑惑了:这个Big Canyon Lodge的状态为何是Added?因为主表调用了Add方法,相关联的从表实体自动也被标记为Added状态了。上面有提到,EF的这个过程是递归的。
不过此方法弊端也很明显:如果要增/删/改的实体很多,那么还得挨个设置EntityState才可以。试着尝试着让实体实现自定义的IObjectWithState接口,IObjectWithState接口可以记录实体的状态,并且是独立于EF存在的。首先到Model类库上添加一个IObjectWithState接口:
由于独立于EF而存在,所以从数据库取出来的对象得手动设置为Unchanged状态让EF跟踪到,否则取出来的实体都是Detached状态。最好的做法就是在数据库上下文类里监听ObjectMaterialized事件:
注:需要引用命名空间:System.Data.Entity.Infrastructure
然后让Destination和Lodging类分别继承IObjectWithState接口:
都设置好了添加一个方法试试:
方法分析:首先是一个DbSet.Add方法标记主表实体的状态为Added,主表数据其实就是根数据(Root Destination)。正如本文前面演示的根数据被标记为Added状态了,那么相关联的从表数据也自动标记为了Added状态。然后使用ChangeTracker.Entries<TEntity>方法查出所有被上下文跟踪到的实体状态,并通过IObjectWithState接口把上下文中保留的实体状态都设置成了自定义的状态State。这样做有什么好处呢?继续向下看
跟上面的TestSaveDestinationAndLodgings方法基本是类似的,生成的sql也一模一样,不过方法里并没修改每个实体的EntityState,而是设置成了自定义的枚举,好处显而易见:不需要再拿destination.DestinationId > 0 什么来判断哪个实体需要设置成什么状态了。扩展性很强,就算有100个实体被标记为了不同的增删改状态,也不需要挨个判断并设置了。
但是这个还不通用,只能针对demo里的操作,来一个更通用的:
注:需要加上检查实体是否实现了自定义的IObjectWithState接口,否则方法跑完每个实体都被标记为了EntityState.Added状态。
感谢阅读,希望我的分析能给你带来思考并有所进步。本文源码
使用EntityState枚举实现单个实体的增/删/改
增加:DbSet.Add = > EntityState.Added
标记实体为未改变:EntityState.Unchanged
修改:EntityState.Modified
删除:DbSet.Remove = > EntityState.Deleted
EF里实体状态的递归(recursive)
不被上下文追踪的情况下实现增删改操作
让实体实现自定义的IObjectWithState接口来设置实体状态
通用的转换实体状态方法
本文源码和系列文章目录
之前使用EF都是通过调用SaveChanges方法把增加/修改/删除的数据提交到数据库,但是上下文是如何知道实体对象是增加、修改还是删除呢?答案是通过EntityState枚举来判断的,看一个方法:
/// <summary> /// 查看实体状态 /// </summary> private static void GetOneEntityToSeeEntityState() { using (var context = new DbContexts.DataAccess.BreakAwayContext()) { var destination = context.Destinations.Find(4); EntityState stateBefore = context.Entry(destination).State; Console.WriteLine(stateBefore); } }
注:使用EntityState需添加引用system.data
跑下程序,输出结果为:Unchanged。从英文意思便可以猜到一二:取出来的数据是Unchanged,那么添加、修改、删除自然也就是Added、Modified、Deleted了。在EntityState上按F12定位到其定义看看:
SELECT [Project1].[LocationID] AS [LocationID], [Project1].[LocationName] AS [LocationName], [Project1].[Country] AS [Country], [Project1].[Description] AS [Description], [Project1].[Photo] AS [Photo], [Project1].[TravelWarnings] AS [TravelWarnings], [Project1].[ClimateInfo] AS [ClimateInfo], [Project1].[C1] AS [C1], [Project1].[Discriminator] AS [Discriminator], [Project1].[LodgingId] AS [LodgingId], [Project1].[Name] AS [Name], [Project1].[Owner] AS [Owner], [Project1].[MilesFromNearestAirport] AS [MilesFromNearestAirport], [Project1].[destination_id] AS [destination_id], [Project1].[PrimaryContactId] AS [PrimaryContactId], [Project1].[SecondaryContactId] AS [SecondaryContactId], [Project1].[Entertainment] AS [Entertainment], [Project1].[Activities] AS [Activities], [Project1].[MaxPersonsPerRoom] AS [MaxPersonsPerRoom], [Project1].[PrivateRoomsAvailable] AS [PrivateRoomsAvailable] FROM ( 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], [Extent2].[LodgingId] AS [LodgingId], [Extent2].[Name] AS [Name], [Extent2].[Owner] AS [Owner], [Extent2].[MilesFromNearestAirport] AS [MilesFromNearestAirport], [Extent2].[destination_id] AS [destination_id], [Extent2].[PrimaryContactId] AS [PrimaryContactId], [Extent2].[SecondaryContactId] AS [SecondaryContactId], [Extent2].[Entertainment] AS [Entertainment], [Extent2].[Activities] AS [Activities], [Extent2].[MaxPersonsPerRoom] AS [MaxPersonsPerRoom], [Extent2].[PrivateRoomsAvailable] AS [PrivateRoomsAvailable], [Extent2].[Discriminator] AS [Discriminator], CASE WHEN ([Extent2].[LodgingId] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1] 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 N'Grand Canyon' = [Extent1].[LocationName] ) AS [Limit1] LEFT OUTER JOIN [dbo].[Lodgings] AS [Extent2] ON ([Extent2].[Discriminator] IN ('Resort','Hostel','Lodging')) AND ([Limit1].[LocationID] = [Extent2].[destination_id]) ) AS [Project1] ORDER BY [Project1].[LocationID] ASC, [Project1].[C1] ASC
View Code
【第二段sql】
exec sp_executesql N'update [baga].[Locations] set [LocationName] = @0, [Country] = @1, [Description] = @2, [Photo] = null, [TravelWarnings] = @3, [ClimateInfo] = null where ([LocationID] = @4) ',N'@0 nvarchar(200),@1 nvarchar(max) ,@2 nvarchar(500),@3 nvarchar(max) ,@4 int',@0=N'Grand Canyon',@1=N'USA',@2=N'One huge canyon.',@3=N'Carry enough water!',@4=1
【第三段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'Big Canyon Lodge',@1=0,@2=1,@3=N'Lodging'
【第四段sql】
exec sp_executesql N'update [dbo].[Lodgings] set [Name] = @0, [Owner] = null, [MilesFromNearestAirport] = @1, [destination_id] = @2, [PrimaryContactId] = null, [SecondaryContactId] = null where ([LodgingId] = @3) ',N'@0 nvarchar(200),@1 decimal(18,2),@2 int,@3 int',@0=N'New Name Holiday Park',@1=2.50,@2=1,@3=1
【第五段sql】
exec sp_executesql N'delete [dbo].[Lodgings] where ([LodgingId] = @0)',N'@0 int',@0=2
跟踪下上面方法里的实体状态:
/// <summary> /// 设置每一个实体状态保证正确的提交修改 /// </summary> /// <param name="destination">要添加的实体</param> /// <param name="deteledLodgings">要删除的实体</param> private static void SaveDestinationAndLodgings(DbContexts.Model.Destination destination, List<DbContexts.Model.Lodging> deteledLodgings) { using (var context = new DbContexts.DataAccess.BreakAwayContext()) { context.Destinations.Add(destination); if (destination.DestinationId > 0) //避免了新添加的实体 context.Entry(destination).State = EntityState.Modified; foreach (var lodging in destination.Lodgings) { if (lodging.LodgingId > 0) context.Entry(lodging).State = EntityState.Modified; } foreach (var lodging in deteledLodgings) { context.Entry(lodging).State = EntityState.Deleted; } //看看每个实体的状态 Console.WriteLine("{0}:{1}", destination.Name, context.Entry(destination).State); foreach (var lodging in destination.Lodgings) { Console.WriteLine("{0}:{1}", lodging.Name, context.Entry(lodging).State); } foreach (var lodging in deteledLodgings) { Console.WriteLine("{0}:{1}", lodging.Name, context.Entry(lodging).State); } context.SaveChanges(); } }
的确如我们所需:主表数据是修改、从表数据一个修改一个添加一个删除。这样再调用上下文的SaveChanges方法的时候都能正确的提交数据库。看到这里可能疑惑了:这个Big Canyon Lodge的状态为何是Added?因为主表调用了Add方法,相关联的从表实体自动也被标记为Added状态了。上面有提到,EF的这个过程是递归的。
不过此方法弊端也很明显:如果要增/删/改的实体很多,那么还得挨个设置EntityState才可以。试着尝试着让实体实现自定义的IObjectWithState接口,IObjectWithState接口可以记录实体的状态,并且是独立于EF存在的。首先到Model类库上添加一个IObjectWithState接口:
namespace DbContexts.Model { public interface IObjectWithState { State State { get; set; } } public enum State { Added, Unchanged, Modified, Deleted } }
由于独立于EF而存在,所以从数据库取出来的对象得手动设置为Unchanged状态让EF跟踪到,否则取出来的实体都是Detached状态。最好的做法就是在数据库上下文类里监听ObjectMaterialized事件:
public BreakAwayContext() : base("name=BreakAwayContext") { ((IObjectContextAdapter)this).ObjectContext .ObjectMaterialized += (sender, args) => { var entity = args.Entity as DbContexts.Model.IObjectWithState; if (entity != null) { entity.State = DbContexts.Model.State.Unchanged; } }; }
注:需要引用命名空间:System.Data.Entity.Infrastructure
然后让Destination和Lodging类分别继承IObjectWithState接口:
public class Destination : IObjectWithState public class Lodging : IObjectWithState
都设置好了添加一个方法试试:
/// <summary> /// 设置实体的状态为自定义的枚举值,然后统一跟踪 /// </summary> public static void SaveDestinationGraph(DbContexts.Model.Destination destination) { using (var context = new DbContexts.DataAccess.BreakAwayContext()) { context.Destinations.Add(destination); foreach (var entry in context.ChangeTracker.Entries<DbContexts.Model.IObjectWithState>()) { DbContexts.Model.IObjectWithState stateInfo = entry.Entity; entry.State = ConvertState(stateInfo.State); } context.SaveChanges(); } } public static EntityState ConvertState(DbContexts.Model.State state) { switch (state) { case DbContexts.Model.State.Added: return EntityState.Added; case DbContexts.Model.State.Modified: return EntityState.Modified; case DbContexts.Model.State.Deleted: return EntityState.Deleted; default: return EntityState.Unchanged; } }
方法分析:首先是一个DbSet.Add方法标记主表实体的状态为Added,主表数据其实就是根数据(Root Destination)。正如本文前面演示的根数据被标记为Added状态了,那么相关联的从表数据也自动标记为了Added状态。然后使用ChangeTracker.Entries<TEntity>方法查出所有被上下文跟踪到的实体状态,并通过IObjectWithState接口把上下文中保留的实体状态都设置成了自定义的状态State。这样做有什么好处呢?继续向下看
private static void TestSaveDestinationGraph() { DbContexts.Model.Destination canyon; using (var context = new DbContexts.DataAccess.BreakAwayContext()) { canyon = (from d in context.Destinations.Include(d => d.Lodgings) where d.Name == "Grand Canyon" select d).Single(); } canyon.TravelWarnings = "Carry enough water!"; canyon.State = DbContexts.Model.State.Modified; //设置为自定义的枚举,跟EF的EntityState没关系 var firstLodging = canyon.Lodgings.First(); firstLodging.Name = "New Name Holiday Park"; firstLodging.State = DbContexts.Model.State.Modified; //设置为自定义的枚举 var secondLodging = canyon.Lodgings.Last(); secondLodging.State = DbContexts.Model.State.Deleted; //设置为自定义的枚举 canyon.Lodgings.Add(new DbContexts.Model.Lodging { Name = "Big Canyon Lodge", State = DbContexts.Model.State.Added //设置为自定义的枚举 }); SaveDestinationGraph(canyon); }
跟上面的TestSaveDestinationAndLodgings方法基本是类似的,生成的sql也一模一样,不过方法里并没修改每个实体的EntityState,而是设置成了自定义的枚举,好处显而易见:不需要再拿destination.DestinationId > 0 什么来判断哪个实体需要设置成什么状态了。扩展性很强,就算有100个实体被标记为了不同的增删改状态,也不需要挨个判断并设置了。
但是这个还不通用,只能针对demo里的操作,来一个更通用的:
/// <summary> /// 通用的转换实体状态方法 /// </summary> /// <typeparam name="TEntity">要操作的实体</typeparam> /// <param name="root">根实体</param> private static void ApplyChanges<TEntity>(TEntity root) where TEntity : class, DbContexts.Model.IObjectWithState { using (var context = new DbContexts.DataAccess.BreakAwayContext()) { context.Set<TEntity>().Add(root); CheckForEntitiesWithoutStateInterface(context); //检查 foreach (var entry in context.ChangeTracker.Entries<DbContexts.Model.IObjectWithState>()) { DbContexts.Model.IObjectWithState stateInfo = entry.Entity; entry.State = ConvertState(stateInfo.State); } context.SaveChanges(); } } /// <summary> /// 检查实体是否实现了IObjectWithState接口 /// </summary> private static void CheckForEntitiesWithoutStateInterface(DbContexts.DataAccess.BreakAwayContext context) { var entitiesWithoutState = from e in context.ChangeTracker.Entries() where !(e.Entity is DbContexts.Model.IObjectWithState) select e; if (entitiesWithoutState.Any()) throw new NotSupportedException("All entities must implement IObjectWithState"); }
注:需要加上检查实体是否实现了自定义的IObjectWithState接口,否则方法跑完每个实体都被标记为了EntityState.Added状态。
感谢阅读,希望我的分析能给你带来思考并有所进步。本文源码
相关文章推荐
- 使用EF自带的EntityState枚举和自定义枚举实现单个和多个实体的增删改查
- 【EF学习笔记09】----------使用 EntityState 枚举标记实体状态,实现增删改查
- EF里单个实体的增查改删以及主从表关联数据的各种增删改查 - lonelyxmas
- Spring MVC代码实例系列-12:使用自带的validation实现自定义message表单校验
- Eclipse使用自带插件JPA Tools实现自动生成数据库对应表的实体类(不使用Hibernate)
- JAVAWEB开发之mybatis详解(一)——mybatis的入门(实现增删改查操作)、自定义别名、抽取代码块以及动态SQL的使用
- EF里单个实体的增查改删以及主从表关联数据的各种增删改查
- 使用EF框架实现MVC的增删改查功能!!!Entity Framework
- ecshop中自定义页面的分页实现使用ecs自带分页程序
- Asp.net MVC4 使用EF实现数据库的增删改查
- Asp.net MVC4 使用EF实现数据库的增删改查
- ASPX中使用EF实现增删改查
- Asp.net MVC4 使用EF实现数据库的增删查改
- EF里单个实体的增查改删以及主从表关联数据的各种增删改查
- 使用织梦自带的邮件功能:实现自定义表单邮件通知
- 使用EF实现数据库的增删改查
- JAVAWEB开发之mybatis详解(一)——mybatis的入门(实现增删改查操作)、自定义别名、抽取代码块以及动态SQL的使用
- 使用DEDECMS织梦自带的邮件功能实现自定义表单邮件通知
- 在快速自定义的NopCommerce中使用实体框架(EF)代码优先迁移
- 使用Crowd自带Demo,自定义Application接入,并实现SSO