一. 背景
说起EF的增删改操作,相信很多人都会说,有两种方式:① 通过方法操作 和 ② 通过状态控制。
相信你在使用EF进行删除或修改操作的时候,可能会遇到以下错误:“ The object cannot be deleted because it was not found in the ObjectStateManager”,通过百度查询,说是没有进行上下文的附加,需要attach一下,那么都哪些情况需要附加,哪些是不需要附加的呢?
在本章节,将结合EF的两种方式,从EF本地缓存的角度来详细介绍EF的增删改情况。
二. EF核心结论
经过无数次摸爬滚打,详细的代码测试,整理出下面关于EF增删改操作的结论。
1. 总纲
SaveChangs的时候一次性保存本地属性状态的全部变化.(换言之:只有本地缓存属性的状态发生变化了,SaveChanges才会实际生效)
补充:这里的属性状态的变化是存在于服务器端,一定不要理解为存在于本地,这也是为什么EF上下文不能用单例创建了。
EF的本地缓存属性的三种形式:
①.通过Attach附加.
②.通过EF的即时查询,查询出来的数据,自动就本地缓存了.
③.通过状态控制. eg:Added、Modified、Deleted. (db.Entry(sl).State = EntityState.Added;)
2. EF的增删改操作的操作有两种形式
(一). 通过方法来操控
a. 增加1个实体. Add() 不需要Attach()附加.(当然附加了也不受影响)
b. 增加1个集合. AddRange() 不需要Attach()附加.(当然附加了也不受影响)
c. 删除. Remove(). 分两种情况:
特别注意:如果数据为空,会报错.所以在实际开发过程中,要采用相应的业务逻辑进行处理.
①:自己创建了一个实体(非查询出来的),必须先Attach,然后Remove.
②:访问数据库,即时查询出来的数据(已经放到EF本地缓存里了),可以省略Attach,直接Remove(当然附加了也不受影响)
d. 修改(如果数据主键不存在,执行增加操作). AddOrUpdate(),可以省略Attach,直接AddOrUpdate.
需要引用程序集:using System.Data.Entity.Migrations;
①: 如果是执行增加操作,不需要进行Attach附加,但附加了Attach不受影响
②:如果是执行修改操作,不能进行Attach的附加,附加了Attach将导致修改失效,saveChange为0(无论是自己创建的或即时查询出来的,都不能进行Attach的附加)
e. 修改. 不需要调用任何方法.
该种方式如果实体为空,SaveChanges时将报错.
①:自己创建对象→先Attach(根据主键来区分对象)→然后修改属性值→最后saveChange
②: EF即时查询对象(自动本地缓存)→然后修改属性值→最后saveChange
(二). 通过修改本地属性的状态来操控.
(该种方式本身已经改变了本地缓存属性了,所以根本不需要Attach附加)
a. 增加. db.Entry(sl).State = EntityState.Added;
b. 删除. db.Entry(sl).State = EntityState.Deleted;
特别注意:如果数据为空,会报错.所以在实际开发过程中,要采用相应的业务逻辑进行处理.
①.适用于自己创建对象(根据主键来确定对象),然后删除的情况.
②.适用于即时查询出来的对象,然后进行删除的情况.
c. 修改. db.Entry(sl).State = EntityState.Modified;
特别注意:如果数据为空,会报错.所以在实际开发过程中,要采用相应的业务逻辑进行处理.
①.适用于自己创建对象(根据主键来确定对象),然后修改的情况.
②.适用于即时查询出来的对象,然后修改的情况.
三. 实战操练
1. 增加方法(Add和AddRange)
1 private static void ADD()2 {3 using (DbContext db = new CodeFirstModel())4 {5 Console.WriteLine("---------------------------1. Add()方法-------------------------------------");6 //监控数据库SQL情况7 // db.Database.Log += c => Console.WriteLine(c);8 TestInfor tInfor = new TestInfor()9 {
10 id = Guid.NewGuid().ToString("N"),
11 txt1 = "t1",
12 txt2 = "t2"
13 };
14 // db.Set<TestInfor>().Attach(tInfor); //特别注意Add方法前不需要进行Attach状态的附加,当然附加了也不会出错.
15 db.Set<TestInfor>().Add(tInfor);
16 int n = db.SaveChanges();
17 Console.WriteLine("数据作用条数:" + n);
18 }
19 using (DbContext db = new CodeFirstModel())
20 {
21 Console.WriteLine("---------------------------2. AddRange()方法-------------------------------------");
22 //监控数据库SQL情况
23 //db.Database.Log += c => Console.WriteLine(c);
24 List<TestInfor> tList = new List<TestInfor>()
25 {
26 new TestInfor()
27 {
28 id = Guid.NewGuid().ToString("N"),
29 txt1 = "t11",
30 txt2 = "t22"
31 },
32 new TestInfor()
33 {
34 id = Guid.NewGuid().ToString("N"),
35 txt1 = "t11",
36 txt2 = "t22"
37 },
38 new TestInfor()
39 {
40 id = Guid.NewGuid().ToString("N"),
41 txt1 = "t11",
42 txt2 = "t22"
43 }
44 };
45 //特别注意AddRange方法前不需要进行Attach状态的附加,当然附加也不错.
46 foreach (var item in tList)
47 {
48 db.Set<TestInfor>().Attach(item);
49 }
50 db.Set<TestInfor>().AddRange(tList);
51 int n = db.SaveChanges();
52 Console.WriteLine("数据作用条数:" + n);
53 }
54 }
2. 删除方法(先Attach-后Remove)
1 private static void Delete1()2 {3 using (DbContext db = new CodeFirstModel())4 {5 Console.WriteLine("---------------------------1. Remove()方法 (调用Attach状态附加)-------------------------------------");6 //监控数据库SQL情况7 //db.Database.Log += c => Console.WriteLine(c);8 TestInfor tInfor = new TestInfor()9 {
10 id = "11", //实际测试的时候要有这条id的数据才能去测试哦
11 };
12 /*
13 * 特别注意1:Remove方法删除必须调用Attach进行状态的附加,如果不附加将报下面的错误。
14 * The object cannot be deleted because it was not found in the ObjectStateManager.
15 * 特别注意2:直接使用状态的方式进行删除,db.Entry(tInfor).State = EntityState.Deleted; 是不需要进行attach附加的
16 * 该种方式在后面进行测试讲解
17 * 特别注意3:无论是Remove凡是还是直接状态的方式,如果传入的删除的数据为空,会报错抛异常
18 */
19
20 db.Set<TestInfor>().Attach(tInfor); //如果注释掉该句话,则报错
21 db.Set<TestInfor>().Remove(tInfor);
22
23 int n = db.SaveChanges();
24 Console.WriteLine("数据作用条数:" + n);
25 }
26 }
3. 删除方法(先查询→后Remove删除)
1 private static void Delete2()2 {3 using (DbContext db = new CodeFirstModel())4 {5 Console.WriteLine("---------------------------3. Remove()方法 (调用Attach状态附加)-------------------------------------");6 int n;7 //监控数据库SQL情况8 //db.Database.Log += c => Console.WriteLine(c);9 TestInfor tInfor = db.Set<TestInfor>().Where(u => u.id == "3").FirstOrDefault();
10 /*
11 * 特别注意1:对于先查询(即时查询,查出来放到了EF的本地缓存里),后删除,这种情况可以省略Attach状态的附加。
12 * 因为查出来的数据已经放在EF的本地缓存里了,相当于已经附加了,无须再次附加(当然附加也不报错)
13 */
14 if (tInfor == null)
15 {
16 n = 0;
17 }
18 else
19 {
20 //db.Set<TestInfor>().Attach(tInfor); //对于先查询(即时查询,查出来放到了EF的本地缓存里),后删除,这种情况省略该句话,仍然有效
21 db.Set<TestInfor>().Remove(tInfor);
22 n = db.SaveChanges();
23 }
24 Console.WriteLine("数据作用条数:" + n);
25 }
26 }
4. 修改(AddOrUpdate)
1 private static void Update1()2 {3 using (DbContext db = new CodeFirstModel())4 {5 Console.WriteLine("---------------------------1. AddOrUpdate()方法-------------------------------------");6 Console.WriteLine("-------------------------测试增加和自己创建数据的修改情况-----------------------------");7 //监控数据库SQL情况8 // db.Database.Log += c => Console.WriteLine(c);9 TestInfor tInfor = new TestInfor()
10 {
11 id = "123",
12 txt1 = "马茹",
13 txt2 = "马茹2"
14 };
15 /*
16 特别注意AddOrUpdate方法前不需要进行Attach状态的附加
17 * 如果是执行增加操作,不需要附加Attach,附加了Attach不受影响
18 * 如果是执行修改操作,不能附加Attach,附加了Attach将导致修改失效,saveChange为0
19 */
20 //db.Set<TestInfor>().Attach(tInfor);
21 db.Set<TestInfor>().AddOrUpdate(tInfor);
22 int n = db.SaveChanges();
23 Console.WriteLine("数据作用条数:" + n);
24 }
25 using (DbContext db = new CodeFirstModel())
26 {
27 Console.WriteLine("-------------------------测试即时查询出来的数据的修改情况-----------------------------");
28 //监控数据库SQL情况
29 // db.Database.Log += c => Console.WriteLine(c);
30 TestInfor tInfor =db.Set<TestInfor>().Where(u=>u.id=="123").FirstOrDefault();
31 tInfor.txt1="ypf11";
32 /*
33 即时查询出来的数据,调用AddorUpdate方法执行修改操作
34
35 * 如果是执行修改操作,不需要进行Attach的附加,附加了Attach将导致修改失效,saveChange为0
36 */
37 db.Set<TestInfor>().Attach(tInfor);
38 db.Set<TestInfor>().AddOrUpdate(tInfor);
39 int n = db.SaveChanges();
40 Console.WriteLine("数据作用条数:" + n);
41 }
42 }
5. 修改(自己创建对象,然后attach附加→修改属性值→SaveChanges)
1 private static void Update2()2 {3 using (DbContext db = new CodeFirstModel())4 {5 Console.WriteLine("---------------------------1. attach附加→修改属性值→SaveChanges-------------------------------------");6 //监控数据库SQL情况7 // db.Database.Log += c => Console.WriteLine(c);8 TestInfor tInfor = new TestInfor()9 {
10 id = "123"
11 };
12
13 /*
14 特别注意1:该方式为自己创建对象(对象中必须要有主键值),然后通过attach附加,然后修改属性值,最后保存SaveChange。可以实现修改操作.
15 特别注意2:该种方式如果实体为空,SaveChanges时将报错.
16 */
17 db.Set<TestInfor>().Attach(tInfor);
18 tInfor.txt1 = "ypf1";
19
20 int n = db.SaveChanges();
21 Console.WriteLine("数据作用条数:" + n);
22 }
23 }
6. 修改(即时查询→修改属性值→SaveChanges)
1 private static void Update3()2 {3 using (DbContext db = new CodeFirstModel())4 {5 Console.WriteLine("---------------------------1. 即时查询→修改属性值→SaveChangess-------------------------------------");6 //监控数据库SQL情况7 // db.Database.Log += c => Console.WriteLine(c);8 TestInfor tInfor = db.Set<TestInfor>().Where(u => u.id == "123").FirstOrDefault();9
10 /*
11 特别注意1:EF即时查询出来一个对象(自动保存到本地缓存了),然后修改属性值,最后保存SaveChange。可以实现修改操作.
12 特别注意2:该种方式如果实体为空,SaveChanges时将报错.
13 */
14 tInfor.txt1 = "ypf333";
15
16 int n = db.SaveChanges();
17 Console.WriteLine("数据作用条数:" + n);
18 }
19 }
7. 增加方法(EntityState.Added)
1 private static void ADD2()2 {3 using (DbContext db = new CodeFirstModel())4 {5 Console.WriteLine("---------------------------1. EntityState.Added-------------------------------------");6 //监控数据库SQL情况7 // db.Database.Log += c => Console.WriteLine(c);8 TestInfor tInfor = new TestInfor()9 {
10 id = Guid.NewGuid().ToString("N"),
11 txt1 = "t1",
12 txt2 = "t2"
13 };
14 db.Entry(tInfor).State = EntityState.Added;
15 int n = db.SaveChanges();
16 Console.WriteLine("数据作用条数:" + n);
17 }
18 }
8. 删除方法(EntityState.Deleted-自己创建的对象)
1 private static void Delete3()2 {3 using (DbContext db = new CodeFirstModel())4 {5 Console.WriteLine("---------------------------EntityState.Deleted-自己创建的对象-------------------------------------");6 //监控数据库SQL情况7 // db.Database.Log += c => Console.WriteLine(c);8 TestInfor tInfor = new TestInfor()9 {
10 id = "122",
11 };
12 db.Entry(tInfor).State = EntityState.Deleted;
13 int n = db.SaveChanges();
14 Console.WriteLine("数据作用条数:" + n);
15 }
16
17 }
9. 删除方法(EntityState.Deleted-即时查询的对象)
1 private static void Delete4()2 {3 using (DbContext db = new CodeFirstModel())4 {5 Console.WriteLine("---------------------------EntityState.Deleted-即时查询的对象-------------------------------------");6 //监控数据库SQL情况7 // db.Database.Log += c => Console.WriteLine(c);8 TestInfor tInfor = db.Set<TestInfor>().Where(u => u.id == "123").FirstOrDefault();9 db.Entry(tInfor).State = EntityState.Deleted;
10 int n = db.SaveChanges();
11 Console.WriteLine("数据作用条数:" + n);
12 }
13
14 }
10. 修改(自己创建对象,然后Modified→SaveChanges)
1 private static void Update4()2 {3 using (DbContext db = new CodeFirstModel())4 {5 Console.WriteLine("---------------------------1. 自己创建对象,然后Modified→SaveChanges-------------------------------------");6 //监控数据库SQL情况7 // db.Database.Log += c => Console.WriteLine(c);8 TestInfor tInfor = new TestInfor()9 {
10 id = "1",
11 txt1 = "ypf1",
12 txt2="ypf1"
13 };
14 db.Entry(tInfor).State = EntityState.Modified;
15 int n = db.SaveChanges();
16 Console.WriteLine("数据作用条数:" + n);
17 }
18 }
11. 修改(即时查询→修改属性值→然后Modified→SaveChanges)
1 private static void Update5()2 {3 using (DbContext db = new CodeFirstModel())4 {5 Console.WriteLine("---------------------------1. 即时查询→修改属性值→SaveChanges-------------------------------------");6 //监控数据库SQL情况7 // db.Database.Log += c => Console.WriteLine(c);8 TestInfor tInfor = db.Set<TestInfor>().Where(u => u.id == "2").FirstOrDefault();9
10 tInfor.txt1 = "ypf2";
11 tInfor.txt2 = "ypf2";
12
13 db.Entry(tInfor).State = EntityState.Modified;
14 int n = db.SaveChanges();
15 Console.WriteLine("数据作用条数:" + n);
16 }
17 }