本文介绍EntityFramework使用方法
-
Entity Framework的注意点
由于安装和操作的细节讲起来很琐碎,这部分只罗列出难点,其他细节请自行查阅 安装细节 Pluralize or singularize generated object names(确定所生成对象名称的单复数形式)复选框 此复选框的意思是,将数据库中的复数表名映射成单数形式的实体类,如果有一对多,多对多关系的表会将属性名以复数形式显示,增加程序可读性 Include foreign key columns in the model (在模型中包含外键)复选框 将数据库外键关系映射到实体模型中 Import selected stored procedures and functions into entity model (将所选存储过程和函数导入到实体模型中) 将存储过程和函数导入到实体模型 操作CRUD 要先实例化上下文类 using(var ctx = new SchoolDBEntities()) { // 进行增删改查 }
-
支持的查询方法
支持的查询数据的方式有 LINQ to Entites linq方法 var linq = ctx.Students.Where(s => s.StudentID > 2); linq查询语句 var linq = from item in ctx.Students select item; Entity SQL 此法不建议使用 Native SQL var tempResult = ctx.Students.SqlQuery("select * from Student"); 注意:sql语句返回的列不能改名,否则报错 返回值类型的写法 string result = db.Database.SqlQuery<string>("select studentname from Student where studentid = 1").FirstOrDefault<string>(); 执行非查询sql的写法 int noOfRowUpdated = ctx.Database.ExecuteSqlCommand("Update student set studentname ='changed student by command' where studentid=1");
-
ef表示体包含的方法
ctx.Students表实体中除了上面讲到的SqlQuery方法外还包括如下方法 Add方法 db.Students.Add(student); // 添加一条记录到实体框架中 AsNoTracking方法 如果查询的数据只是用来展示的,可以使用AsNoTracking方法将查询的数据和DbContext断开连接,提高查询效率 var result = db.Students.Select(s => new { s.StandardId, s.StudentName }).AsNoTracking().ToList(); Attach方法 将一条Student数据附加到EF的跟踪图中就像数据是从数据库中读取的一样,其实数据库中并不存在 var result = db.Students.Attach(st); Create方法 创建一个空的Student实例,该实例没有附加到Students集合中 var result = db.Students.Create(); Find方法 返回Students表中主键id是5的数据,与之关联的数据也会一起返回 如果数据已经添加到上下文中,但是没有保存到数据库,使用Find同样有效 var result = db.Students.Find(5); Include方法 指定包含在结果集中的导航属性 var result = db.Students.Include("StudentAddress").ToList<Student>(); 也可以传递lambda表达式 var result = db.Students.Include(s => s.Standard).ToList<Student>(); Remove方法 将提供的Student实例标记为已删除,改实例必须存在于上下文中 var result = db.Students.Remove(st); SqlQuery带参数的用法 var result = db.Students.SqlQuery("select * from student where studentid = @p", new SqlParameter("@p", SqlDbType.Int) { Value = 5} ).Select(s => s.StudentName).ToList();
-
DBEntityEntry对象
ef实体框架中,可以通过数据实体获取数据实体相关的DBEntityEntry对象,从而得到数据实体的所有信息 var student = db.Students.Find(1); student.StudentName = "yejiawei"; var entry = db.Entry(student); // 获取数据实体的DBEntityEntry对象 属性 var result = entry.Entity.GetType().FullName; // 获取实体的名称 var result = entry.State.ToString(); // 获取实体当前的状态 IEnumerable<string> result = entry.CurrentValues.PropertyNames; // 获取实体的所有属性名 var result = entry.OriginalValues["StudentName"]; // 获取实体属性修改之前的值 var result = entry.CurrentValues["StudentName"]; // 获取实体属性修改之后的值 可以手动设置实体的状态为 Added,Modified,Deleted entry.State = EntityState.Modified; 方法 Collection 返回导航属性的集合,多对多关系 var result = entry.Collection("Courses"); 可以继续使用 result.Load() 预载数据 还可以继续使用 result.Query().Where(...) 进一步筛选数据 ComplexProperty 返回复杂属性的集合 GetDatabaseValues 返回跟踪的属性集合 var result = entry.GetDatabaseValues(); Property 返回实体指定属性的所有信息组成的集合 var result = entry.Property("StudentName"); Reference 返回导航属性的集合,一对多或者多对一关系 var result = entry.Reference("StudentAddress"); 可以继续使用 result.Load() 预载数据 还可以继续使用 result.Query().Where(...) 进一步筛选数据 Reload方法 entry.Reload(); 调用此方法,之前的所有更改都无效,全部和数据库同步,并且当前状态是Unchanged
-
追踪变化
在ef上下文的生命周期中,ef会自动追踪加载进来的实体,可以通过ChangeTracker来获取所有被上下文追踪的实体 每一个实体都必须包含一个主键,如果没有主键ef是不会追踪的 var student = db.Students.Find(1); 属性 var result = db.ChangeTracker.Entries(); // 获取所有被上下文追踪的实体 var result = db.ChangeTracker.Entries().Count(); // 获取所有被上下文追踪的实体的个数 var result = db.ChangeTracker.Entries(); // 获取当前上下文中的所有DBEntityEntry对象对象
-
维持Entity Framework的实体场景
连接场景 数据检索和增删改都在一个上下文里面操作 在连接场景中完成增删改查是非常容易的,因为上下文自动跟踪实体的状态,AutoDetectChangesEnabled默认是true 添加操作 定义一个引用类型比较类 public class StudentComparer : IEqualityComparer<Student> { public bool Equals(Student x, Student y) { if (x.StudentName.ToLower() == y.StudentName.ToLower()) { return true; } return false; } public int GetHashCode(Student obj) { return obj.GetHashCode(); } } 新增操作 if(!db.Students.ToList().Contains(new Student() { StudentName = "yejiawei" }, new StudentComparer())) { db.Students.Add(new Student() { StudentName = "yejiawei" }); db.SaveChanges(); return Ok("添加成功"); } return Ok("已经存在了"); 修改操作 Student StudetUpdate = db.Students.ToList().Where(s => s.StudentName == "yejiawei").FirstOrDefault(); StudetUpdate.StudentName = "叶家伟"; db.SaveChanges(); 删除操作 db.Students.Remove(db.Students.ToList().ElementAt(0)); db.SaveChanges(); 断开连接场景 数据检索和增删改在不同的上下文里面操作 在断开连接场景中,我们需要附加实体到新的上下文中,并且要指定状态 Add方法,会自动的将实体块附加到新的上下文中并且自动的修改状态为Added,也就是说你调用Add方法,管他啥玩意儿都可以成功 Attach方法,调用Attach方法会将实体块添加到上下文中,但是状态还是Unchanged,需要手动设置 var test = db.Students.Attach(new Student() { StudentName = "yejiawei" }); db.Entry(test).State = EntityState.Added; var result = db.Entry(test).State.ToString(); db.SaveChanges(); 单个的实体 单个的实体,直接更改状态,然后保存即可 var test = new Student() { StudentName = "叶家伟" }; db.Entry(test).State = EntityState.Added; var result = db.Entry(test).State.ToString(); db.SaveChanges();
-
Entity Framework并发问题
在ef中可以根据数据库timestamp类型的列来处理并发问题,timestamp数据会在插入和更新时自动更改并且是唯一的 数据库中timestamp类型的值是自动添加的 然后,需要在ef中将timestamp列属性中的Concurency Mode设置为Fixed 此时,ef在执行更新操作会将timestamp列设置成where的字句,来判断操作的数据有没有并发操作,如果发生了并发操作,那么会抛出DbUpdateConcurrencyException错误 Student test1 = null; using(var context1 = new SchoolDBEntities()) { context1.Configuration.ProxyCreationEnabled = false; // 查询的时候是否包含导航属性 test1 = context1.Students.Where(s => s.StudentID == 9).Single(); test1.StudentName = "小王"; try { context1.Entry(test1).State = EntityState.Modified; context1.SaveChanges(); } catch (DbUpdateConcurrencyException ex) { var t = ex; } }
-
执行存储过程
如下的存储过程导入到ef中 ALTER PROCEDURE [dbo].[GetCoursesByStudentId] -- Add the parameters for the stored procedure here @StudentId int = null AS BEGIN -- SET NOCOUNT ON added to prevent extra result sets from -- interfering with SELECT statements. SET NOCOUNT ON; -- Insert statements for procedure here select c.courseid, c.coursename,c.Location, c.TeacherId from student s left outer join studentcourse sc on sc.studentid = s.studentid left outer join course c on c.courseid = sc.courseid where s.studentid = @StudentId END 然后直接当做函数执行就可以了 var courses = db.GetCoursesByStudentId(1); 可以将更新,删除,新增的存储过程映射到实体上面,从而重写ef默认的增删改的方法 存储过程中,新增的时候执行SELECT SCOPE_IDENTITY() AS StudentId,可以返回新增数据的id
-
在ef中使用枚举
数据库中可以使用一些特殊的整数值,来表示数据的特殊含义,为了是代码的可读性更强,使用枚举来代替这些整数值 首先,将实体中的一个属性列,Convert to Enum 然后,在弹出的选择框中,输入枚举类型的名字,也就是你在写代码时要用到的枚举名称 其次,在成员列表中,指定成员的名字,和成员对应的整数值 现在,你可以使用你定义的枚举了 var type = TeacherType.Guest;
-
在ef中使用表值函数
首先要在数据库中添加一个表值函数 ALTER FUNCTION [dbo].[GetCourseListByStudentID] ( -- Add the parameters for the function here @studentID int ) RETURNS TABLE AS RETURN ( -- Add the SELECT statement with parameter references here select c.courseid, c.coursename,c.Location, c.TeacherId from student s left outer join studentcourse sc on sc.studentid = s.studentid left outer join course c on c.courseid = sc.courseid where s.studentid = @studentID ) 然后将这个表值函数导入到ef中,可以在模型浏览器中修改函数,然后在ef中使用表值函数,如下 var result = db.GetCoursesByStudentId(1).Select(s => s);
-
Local属性
可以通过Local属性访问当前被追踪的实体,被标记为Deleted的实体不可以通过Local访问 db.Students.Load(); // 预载,免去写ToList db.Students.Add(new Student() { StudentName = "New Student" }); return Ok(db.Students.Local);
-
预先加载
使用Include,表示加载导航属性 (from s in context.Students.Include("Standard") where s.StudentName == "Student1" select s).FirstOrDefault<Student>();
-
懒加载
当实体中包含包含复杂属性,只有当访问这个复杂属性的时候才会去查询 如果要为整个上下文关闭懒加载,可以在上下文的类中设置 public SchoolDBEntities(): base("name=SchoolDBEntities") { this.Configuration.LazyLoadingEnabled = false; } 如果想只关闭某一个属性的懒加载,可以导实体的类中设置 public virtual StudentAddress StudentAddress { get; set; }将这个virtual关键字去掉即可
-
异步查询和保存
异步查询 使用async标记此方法是异步的 public async Task<Student> GetStudents() { Student result = null; using (var context = new SchoolDBEntities()) { context.Configuration.ProxyCreationEnabled = false; // 使用await,可以释放当前线程从而可以执行其他代码,避免阻塞程序 result = await context.Students.Where(s => s.StudentID == 10).FirstOrDefaultAsync<Student>(); } return result; } 异步保存 public async Task<int> Get() { using (var context = new SchoolDBEntities()) { var test = context.Courses.Attach(new Course() { CourseName = "数学" }); context.Entry(test).State = EntityState.Added; return await context.SaveChangesAsync(); } }
-
日志的记录
第一种方式,使用默认配置 在web.config配置文件中的entityFramework节点下添加 <interceptors> <interceptor type="System.Data.Entity.Infrastructure.Interception.DatabaseLogger, EntityFramework"> <parameters> <parameter value="F:学习实验区EntityFrameworkLog.txt"/> <parameter value="true" type="System.Boolean"/> </parameters> </interceptor> </interceptors> 即可自动的记录ef做的所有操作 第二种方式,自定义日志记录 using (var context = new SchoolDBEntities()) { // 使用Log方法即可自动实现记录日志,然后追加到日志文件中 context.Database.Log = (string message) => { byte[] bts = Encoding.UTF8.GetBytes(message); using (FileStream fwrite = File.OpenWrite(@"F:学习实验区EntityFrameworkLog1.txt")) { //fwrite.Write(bts, 0, bts.Length); fwrite.Position = fwrite.Length; fwrite.Write(bts, 0, bts.Length); } }; var test = context.Courses.Attach(new Course() { CourseName = "数学" }); context.Entry(test).State = EntityState.Added; return await context.SaveChangesAsync(); }
-
事务提交和回滚
ef默认的操作都是事务处理的,但是我们可以将ef多个操作再用一个事务封装起来,方便回滚 using(DbContextTransaction dbTran = context.Database.BeginTransaction()) { try { var test = context.Courses.Attach(new Course() { CourseName = "333" }); context.Entry(test).State = EntityState.Added; await context.SaveChangesAsync(); dbTran.Commit(); return 1; }catch(Exception ex) { dbTran.Rollback(); return 0; } }
-
AddRange和RemoveRange
AddRange批量增加 List<Course> list = new List<Course>() { new Course() { CourseName = "yuwen" }, new Course() { CourseName = "shuxue" }, new Course() { CourseName = "yinyu" } }; using (var context = new SchoolDBEntities()) { context.Courses.AddRange(list); return await context.SaveChangesAsync(); } RemoveRange批量删除 using (var context = new SchoolDBEntities()) { var items = context.Courses.Where( s => s.CourseName == "yuwen" || s.CourseName == "shuxue" || s.CourseName == "yinyu" ).AsEnumerable<Course>(); context.Courses.RemoveRange(items);; return await context.SaveChangesAsync(); }