EF实现增删改查
这是Ryan的第一篇博客,里面还有很多知识待去深入的探索,共勉!
入下图所示存在EFTest数据库,包含2个名为Score和Student的数据表。下面所有例子中,context 是EFTest数据库上下文的实例。
首先介绍一下 EF的五种状态
1.detached:实体不在上下文的追踪范围内,如刚new的实体,可以通过Attach()添加到上下文,此时状态为unchanged。
2.unchanged:未改变,如刚从数据库读取出来的实体。
3.added:添加状态,一般执行Add时标记为added。
4.deleted:删除状态,一般执行Remove/RemoveRange时标记为deleted。
5.modified:修改状态,改变了实体的属性会处于这个状态。
1)实现数据查询
单纯的Linq是不会返回查询结果的,也不会执行查询指令,系统只会将Linq翻译成了SQL,只有加入例如First、ToList等指令才可以返回查询结果,若无结果将返回null
//方法1
Student student = context.Student.Where(o => o.ID == 1).FirstOrDefault();
//方法2-Find里面放置主键
Student student1 = context.Student.Find(2);
//方法3
List student2 = (from d in context.Student where d.Age <= 19 select d).ToList();
//方法4
Student student3 = context.Student.SqlQuery(“select * from Student where ID = @id”, new SqlParameter("@id", 1)).FirstOrDefault();
//方法5
var student4 = (from d in context.Student where d.Age <= 20 orderby d.Age descending, d.ID ascending select new { ID = d.ID,Name=d.Name}).ToList();
//方法6-只读,没有数据追踪这个反应会很快,一般用于单纯的数据查询
Score score = context.Score.Where(o => o.Name == “James”).AsNoTracking
.FirstOrDefault();
2)数据增加
//方法1
EFTestEntities context = new EFTestEntities();
Score score = new Score() { Name = “Sandy”, ScoreValue = 99 };
context.Score.Add(score);
context.SaveChanges();
//方法2
EFTestEntities context = new EFTestEntities();
Score score = new Score() { Name = “Wendy”,ScoreValue=98 };
context.Entry(score).State = System.Data.Entity.EntityState.Added;
context.SaveChanges();
3)数据删除
//方法1
EFTestEntities context = new EFTestEntities();
Score score = (from d in context.Score where d.ID == 8 select d).First();
context.Score.Remove(score);
context.SaveChanges();
//方法2-新的实例要加入到DbContext中,才可以Remove
EFTestEntities context = new EFTestEntities();
Score score1 = new Score() { ID = 1005};
context.Score.Attach(score1);
context.Score.Remove(score1);
context.SaveChanges();
4)数据修改
数据更新的时候,推荐只更新需要修改的字段,这样可以提高执行效率。在遇到表字段较多的情况下,字段全部更新很不明智。下面这个数据更新的指令要格外注意,因为调用该指令所有字段将全部更新
context.Entry(score).State = System.Data.Entity.EntityState.Modified;,下面的EF执行会翻译成真实的SQL,大家注意区分一下方法!
//方法1-通过数据追踪-Change Tracking自动更新数据,若关闭Changing Track-数据将不会自动变更context.Configuration.AutoDetectChangesEnabled = false;
EFTestEntities context = new EFTestEntities();
Score score1 = context.Score.Where(o => o.ID == 1).FirstOrDefault();
score1.Name = “Sam”;
context.SaveChanges();
实际执行的SQL如下:
//Select Top(1) * From Score Where ID = 1;
//Update Score Set Name=’Sam’ Where ID = 1;
//方法2,但是这么写纯属有病,只是验证attach和Change Tracking方法
EFTestEntities context = new EFTestEntities();
Score score = context.Score.Where(o => o.Name == “James”).AsNoTracking().FirstOrDefault();
context.Score.Attach(score);
score.ScoreValue = 9000;
context.SaveChanges();
实际执行的SQL如下:
//Select Top(1) * From Score Where Name = ‘James’;
//Update Score Set ScoreValue = 9000 Where ID = 1;
//方法3,验证Detached状态,使用attach方法。
这种方法简单,少了一个Select方法,但是ID 是主键值,一旦ID值不存在,SaveChanged将会报错
注意:Score实例化的时候若某个字段值为Null,且数据库设置了非空属性,Attach将会报错
EFTestEntities context = new EFTestEntities();
Score score = new Score() { ID = 6};//Detached无法Change Tracking
context.Score.Attach(score); //附加后状态为Unchanged可以Change Tracking
score.Name= “Hunter”;
context.SaveChanges();
实际执行的SQL如下:
//Update Score Set Name=’Hunter’ Where ID = 6;
//方法4,使用IsModified属性,单个字段修改
EFTestEntities context = new EFTestEntities();
Score score = new Score() { ID = 6, Name = “Hunter” };
context.Score.Attach(score);
context.Entry(score).Property(“Name”).IsModified = true;
context.SaveChanges();
实际执行的SQL如下:
//Update Score Set Name=’Hunter’ Where ID = 6;
//方法5-与方法1类似,但是这么写也纯属脑袋进水,只是验证使用EntityState.Modified 所有字段全部更新
EFTestEntities context = new EFTestEntities();
Score score = context.Score.Where(o => o.ID == 1).FirstOrDefault();
score.ScoreValue = 10;
context.Entry(score).State = EntityState.Modified;//多此一举
context.SaveChanges();
实际执行的SQL如下:
//Select Top(1) * From Score Where ID = 1;
//Update Score Set Name=’Sam’,ScoreValue=10 Where ID = 1;
//方法6-Detached状态,直接使用EntityState.Modified更新
EFTestEntities context = new EFTestEntities();
Score score = new Score() { ID = 2, Name = “James”, ScoreValue = 100};
context.Entry(score).State = System.Data.Entity.EntityState.Modified;
context.SaveChanges();
实际执行的SQL如下:
//Update Score Set Name=’James’,ScoreValue=100 Where ID = 2;
//方法7-Attach天坑
因为相同类型的其他实体已具有相同的主键值。在使用 “Attach” 方法或者将实体的状态设置为EntityState.Unchanged或EntityState.Modified时如果图形中的任何实体具有冲突键值,则可能会发生上述行为,如下是解决办法:
EFTestEntities context = new EFTestEntities();
Score score1 = context.Score.Where(o => o.ID == 1).FirstOrDefault<Score>();
score1.Name = "Sammy";
context.SaveChanges();
//直接Attach会报错,已经存在了ID=1的数据
Score score3 = new Score() { ID = 1, Name = "Harry", ScoreValue = 100};
if (context.Entry<Score>(score3).State == System.Data.Entity.EntityState.Detached)
{
var objectContext = ((IObjectContextAdapter)context).ObjectContext;
var entitySet = objectContext.CreateObjectSet<Score>();
var entityKey = objectContext.CreateEntityKey(entitySet.EntitySet.Name, score3);
object foundSet;
bool exists = objectContext.TryGetObjectByKey(entityKey, out foundSet);
if (exists)
{
objectContext.Detach(foundSet); //从上下文中移除
}
}
context.Score.Attach(score3);
context.Entry<Score>(score3).State = System.Data.Entity.EntityState.Modified;
context.SaveChanges();
5)事务处理
Database.BeginTransaction() : 为用户提供一种简单易用的方案,在DbContext中启动并完成一个事务,合并一系列操作到该事务中。同时使用户更方便的指定事务隔离级别。Database.UseTransaction() : 允许DbContext使用一个EF框架外的事务。
Database.BeginTransaction()有两个重载方法。一个方法提供一个IsolationLevel参数,另一个无参方法使用底层数据库提供程序默认的数据库事务隔离级别。
两个重载方法均返回一个DbContextTransaction对象,该对象提供Commit和Rollback方法,用于数据库底层事务的提交和回滚。
使用DbContextTransaction意味着,一旦提交或回滚事务,就要释放该对象。一种简单的方法是使用using语法,在using代码块结束时自动调用该对象的Dispose方法。
using (EFTestEntities context = new EFTestEntities())
{
using (DbContextTransaction contextTransaction = context.Database.BeginTransaction())
{
try
{
context.Database.ExecuteSqlCommand("Insert into Score(Name,ScoreValue) values('Lily5',98)");
context.Database.ExecuteSqlCommand("Insert into Student(Name,Age,Gender) values('Lily5',105,'f')");
Score score = new Score() { Name = "JSH", ScoreValue = 55 };
context.Entry<Score>(score).State = EntityState.Added;
Student student = new Student() { Age = 19, Gender = "m" };
context.Student.Add(student);
context.SaveChanges();
contextTransaction.Commit();
}
catch (System.Exception ex)
{
contextTransaction.Rollback();
}
}
}
6)通过EF执行ADO.Net ExecuteNonQuery、ExecuteScalar、ExecuteReader操作
使用类似:Student student3 = context.Student.SqlQuery(“select * from Student where ID = @id”, new SqlParameter("@id", 1)).FirstOrDefault();会有一个问题!
它会自动吧值装到指定的类型内,而且无法支持dynamic类型,若要处理浮动性的查询,就要给一个POCO类型,若想要自行处理,可以调用DbContext.Database.Connection属性内的CreateCommand,可以得到一个DbCommand的对象,这个对象和ADO.NET本身的IdbCommand接口所提供的功能完全相同,所以程序员可以熟悉的ADO.Net操作方式来操作这个对象,以及用熟悉的ExecuteNonQuery、ExecuteScalar、ExecuteReader方法
//方法1-ExecuteReader使用
EFTestEntities context = new EFTestEntities();
DbCommand dbCommand = context.Database.Connection.CreateCommand();
dbCommand.CommandText = “select Top(1) Name from Score order by ID”;
dbCommand.Connection.Open();
DbDataReader Reader = dbCommand.ExecuteReader();
while(Reader.Read())
{
string a = Reader.GetValue(0).ToString();
}
dbCommand.Connection.Dispose();
//方法2-ExecuteNonQuery
EFTestEntities context = new EFTestEntities();
var dbCommand = context.Database.Connection.CreateCommand();
dbCommand.CommandText = "Insert into Student (Name,Gender,Age) Values (‘Martin’,‘m’,29) ";
dbCommand.Connection.Open();
int iRet = dbCommand.ExecuteNonQuery();
dbCommand.Connection.Dispose();
Happy Ending!