0)导包:
注意:提前应导入对应版本的EF包。
EntityFrameWork
EntityFrameWork.SqlClient
这两个包通常集成在一个包中,所以我们只需要下载EntityFrameWork就可以了
1)创建实体
首先是创建一个实体(任意),这里创建一个Student类。
class Student{public int Id { get; set; }public string Name { get; set; }public int Age { get; set; }public string PhoneP { get; set; }}
2)创建一个Repository(仓库)类,继承自DbContext。
》创建一个数据库(这是用C#去创建数据库,当然还有很多方法)
//this.string 是服务连接需要生成的数据库的名字DbContext dbContext = new DbContext("TEST_EF");//调用以下方法就可以声明一个数据库的实例,并将实例去真实的创建在数据库中,当然,这种方式创建的数据库是默认的配置。Database p = dbContext.Database;p.Create();//创建p.Delete();//删除
》里边可以去定义数据库与实体的映像关系等。
class DBUserRepository : DbContext{//确定要与对应数据库的映像(这也是调用父类的构造函数)public DBUserRepository() : base("数据库名字"){//base调用父类的构造函数,参数作用是定义的数据库连接名,也可以是数据库连接字符串。//这里如果是数据库的名字,EF会在APP.config中去查找一个ConnectionString中去找对应 //的连接名,如果没有配置,则会在本地的serveerDB中自动创建一个这样名字的数据库,然后 //通过之后的代码,在数据库中去建立表。}//确定那些class需要映像:泛型为实体 对象为属性(数据库中的表)public DbSet<Student> Students { get; set; }//设置一个方法:向数据库中保存一个实体public Student Save(Student student){//在关系中添加实体Students.Add(student);//保存更改(这个方法才会真正的改变数据库)SaveChanges();//同步数据库return student;}//设置一个方法:获取特定名字的实体(在数据库中的一行)public Student GetByName(string name){return Students.Where(u => u.Name == name).SingleOrDefault();}}
》在App.Config中配置连接字符串
其实这种方式在ADO.net中也可以使用,作用就是便于更改。
<!--配置数据库连接字符串,providerName必须指定,标志着使用哪种字符串 --><connectionStrings><add name="Test" providerName="System.Data.SqlClient" connectionString="Data Source=localhost;Initial Catalog=Test_Pro;User ID=root;Password=root;Connect Timeout=30"/></connectionStrings>
在EF中会自动去扫描App.config中的连接字符串属性,但是在ADO.net中不会,所以需要引用包
System.Management
在C#中,可以用这个工具包去获取,代码例子:
var s = ConfigurationManager.ConnectionStrings[""].ConnectionString;
3)Migration与API的使用
快速打开命令行:ALT+T+N+O 打开程序包管理控制台GET-HELP about_EntityFramework6 //查看是否安装好了EFGET-HELP about_EntityFrameworkcore //查看是否安装好了EFcore//使用以下Migration命令查看和推送 迁移Enable-Migrations //查看创建迁移文件(执行一次就行)Add-Migration //添加更新版本(可以后面接一个版本的名字)Update-Database //更新(推送)与数据库同步,可更新指定的添加的迁移版本。Get-Migrations //获取历史迁移
4)EF的五种状态
在EF中的对象有五个状态:
1、Detach(游离态,脱离态)
—> 自己动手new出来的对象就是处于Detach状态的
2、Unchange(未改变)
—> 当你使用EF框架进行查询的时候,查询出来的对象都是处于这个状态
3、Added(新增)
—> 一个自己new出来的对象想要加入数据库中,必须首先要转换到这个状态
4、Deleted(删除)
—> 要对从EF框架中查询出来的对象进行删除操作的话,要首先转换到此状态下
5、Modified(被修改)
—> 对从EF框架中查询出来的对象进行改动后,对象会处于此状态下

通常这物种状态在对实体数据进行操作CRUD优化的时候,可以用到,
ER映射
这就是实体与关系的映射,这个映射主要是配置实体,在数据库中建立表的时候,对应表与表的关系(ER)。
》这里主要有几种映射关系:(1-1)(1-n)(n-m)分别就是一对一、一对多、多对多。下边用实例去验证这几种关系在实体中的配置,这里的实例还用到了一些注解,注解的作用就是更详细的配置数据库中的属性信息(如是否可null,数据长度,是否唯一等等),这里的注解都会有注释来表明他们的作用,以便于以后的复习后查找。
这里主要建立三个表,他们对应包含关系,分别是学生表(student),班级表(class),学院表(dept)
一对一(1-1 || 1-0)
这里主要使用注解的形式去建立映射关系:==注意:==如果要建立1-1的映射关系,则需要建立谁是主体,这里解释一下主体的意思(主体就是可以不需要外键属性就可以赋值的,外键则必须依赖主体)。这里就是设置Student的SNo为外键,则表示当添加信息时,Student必须有BNo属性且不能为null,但是Berths添加可以不需要SNo,也就是可以为null。再这个实体中的表示就是一个床铺可以没有学生,但是一个学生必须有床铺。
》新的理解,为什么会有外键,因为当一个表过于庞大的时候,需要拆分表,这是就会出现主表与子表的关系这时候,就要为字表设置一个外键指向主表,表示子表依赖主表而存在着。这时候的子表的主键要和主表的主键一致。
因为是一对一的关系,所以删除主表的时候子表必须级联删除
class Berths{[Key]public string SNo { get; set; }//与学生的编号(1-1)public Student Student { get; set; }}class Student{//设置学生基本属性[Key][ForeignKey("Berths")]public string SNo { get; set; }//略过一些基本属性//设置床铺(1-1)public Berths Berths { get; set; }}
protected override void OnModelCreating(DbModelBuilder modelBuilder){// Configure Student & StudentAddress entitymodelBuilder.Entity<Student>().HasOptional(s => s.Address) // Mark Address property optional in Student entity.WithRequired(ad => ad.Student); // mark Student property as required in StudentAddress entity. Cannot save StudentAddress without Student}//上面的例子中,我们从Student实体开始配置,HasOptional()方法配置Student实体中的Address导航属性为可选的,保存Student实体的时候,可以没有StudentAddress实体】,后WithRequired方法设置StudentAddress实体中的Student导航属性为必须的【保存StudentAddress实体的时候,必须要有Student】。上面的代码,同样会使StudentAddressId成为外键。
当然了,还有严格的一对一关系,也就是学生必须有床铺,床铺也必须有学生,这种情况在现实中一般不会出现,但是如果有这种需要,可以用fluent API来配置(这里不过多介绍)。
protected override void OnModelCreating(DbModelBuilder modelBuilder){// Configure StudentId as FK for StudentAddressmodelBuilder.Entity<Student>().HasRequired(s => s.Address).WithRequiredPrincipal(ad => ad.Student);}//两个实体对于彼此都是必须的:上面的代码中,modelBuilder.Entity<Student>().HasRequired(s => s.Address),是设置Student实体中的 StudentAddress导航类型的Address属性是必须的,.WithRequiredPrincipal(ad => ad.Student)是设置StudentAddress实体中的Student属性是必须的。
一对多(1-n)
我们使用Student和Grade两个实体配置一对多的关系,一个Grade中可以有很多的Students。
以为是一对多的关系,删除主表的时候,子表可以引用为null。
public class Student{public int StudentId { get; set; }public string StudentName { get; set; }}public class Grade{public int GradeId { get; set; }public string GradeName { get; set; }public string Section { get; set; }}
其实一对多的关系通过默认的约定就可以实现,但是由于某种需求,也可以使用Fluent API来实现,使用Fluent API可有从两个方面配置,一个是从实体Student配置,一种是从实体Grade配置。
默认的约定
这里有三种配置情况:一下这三种配置情况在数据库中生成的表都是相同的。
//我们想要在Student实体和Grade实体之间建立一对多的关系,并且很多学生关联到一个Grade。这就意味着,每一个Student实体指向一个Grade。这种情况可以通过在Student类中包含一个Grade类型的导航属性做到,例如:
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public Grade Grade { get; set; }
}
public class Grade
{
public int GradeId { get; set; }
public string GradeName { get; set; }
public string Section { get; set; }
//另外一个约定就是,在主体实体中包含一个集合类型的导航属性,例如:
public class Student
{
public int StudentId { get; set; }
public string StudentName { get; set; }
}
public class Grade
{
public int GradeId { get; set; }
public string GradeName { get; set; }
public string Section { get; set; }
public ICollection<Student> Students { get; set; }
//在两个实体中,都包含导航属性:
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public Grade Grade { get; set; }
}
public class Grade
{
public int GradeID { get; set; }
public string GradeName { get; set; }
public string Section { get; set; }
public ICollection<Student> Student { get; set; }
}
在两个实体中,完整的定义关系,也会根据约定生成一对多的关系表:
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
//完整的定义关系,且这么声明的话,标志着GradeId不可null
public int GradeId { get; set; }
public Grade Grade { get; set; }
}
public class Grade
{
public int GradeId { get; set; }
public string GradeName { get; set; }
public ICollection<Student> Student { get; set; }
}
在上面的例子中,Student实体中,包含一个外键属性GradeId,还有一个Grade类型的导航属性。这样就会生成一对多的关系表。并且Student表中生成的外键GradeId是不可空的。
如果GradeId是可空的int类型,那么就会生成可空的外键:
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
//
public int? GradeId { get; set; }
public Grade Grade { get; set; }
}
上面的代码将会生成一个可空的外键GradeId,?只是类型Nullable的简写。
使用Fluent API配置级联删除
级联删除意味着:当父行被删除之后,自动删除相关的子行。例如:如果Grade被删除了,那么所有在这个Grade中的Students应该同样被自动删除。下面的代码,使用WillCascadeOnDelete方法配置级联删除:
modelBuilder.Entity<Grade>()
.HasMany<Student>(g => g.Students)
.WithRequired(s => s.CurrentGrade)
.WillCascadeOnDelete();
多对多(n-m)
通过默认约定配置多对多关系
EF 6包含多对多关系的默认约定,你需要在两个实体间都包含集合类型的导航属性。例如:Student类应该包含一个集合类型的导航属性Course,同样Course类也应该包含一个集合类型的导航属性Student:
public class Student
{
public Student()
{
this.Courses = new HashSet<Course>();
}
public int StudentId { get; set; }
[Required]
public string StudentName { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
public class Course
{
public Course()
{
this.Students = new HashSet<Student>();
}
public int CourseId { get; set; }
public string CourseName { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
下面的上下文类中,包含Student和Course实体:
public class SchoolDBContext : DBContext
{
public SchoolDBContext() : base("SchoolDB-DataAnnotations")
{
}
public DbSet<Student> Students { get; set; }
public DbSet<Course> Courses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
EF API将会创建Students表和Courses表,同样还会创建联接表StudentCourses,StudentCourses表中,包含两个表中的主键作为主键以及外键。
注意:联接表的名称就是两个实体名称+后缀s
通过默认约定配置多对多关系
EF 6包含多对多关系的默认约定,你需要在两个实体间都包含集合类型的导航属性。例如:Student类应该包含一个集合类型的导航属性Course,同样Course类也应该包含一个集合类型的导航属性Student:
public class Student
{
public Student()
{
this.Courses = new HashSet<Course>();
}
public int StudentId { get; set; }
[Required]
public string StudentName { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
public class Course
{
public Course()
{
this.Students = new HashSet<Student>();
}
public int CourseId { get; set; }
public string CourseName { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
下面的上下文类中,包含Student和Course实体:
public class SchoolDBContext : DBContext
{
public SchoolDBContext() : base("SchoolDB-DataAnnotations")
{
}
public DbSet<Student> Students { get; set; }
public DbSet<Course> Courses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
EF API将会创建Students表和Courses表,同样还会创建联接表StudentCourses,StudentCourses表中,包含两个表中的主键作为主键以及外键
使用Fluent API配置多对多关系
上面的例子中,你已经看到了默认的约定为我们创建了多对多关系的表,以及相关的联接表。我们可以使用FLuent API来配置连接表的名称和列。
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>()
.HasMany<Course>(s => s.Courses)
.WithMany(c => c.Students)
.Map(cs =>
{
cs.MapLeftKey("StudentRefId");
cs.MapRightKey("CourseRefId");
cs.ToTable("StudentCourse");
});
}
上面的代码中,HasMany()方法和WithMany()方法,用来给Student和Course实体配置多对多关系。Map()方法包含一个Action类型的委托,这里我们传入lambda,来定制联接表。MapLeftKey()用来指定Student表中的主键名称(因为我们从Student实体开始配置,所以Student是左表),MapRightKey()用来配置Course表中的主键名称,ToTable用来指定联接表的名称。
常用注解配置
常用的注解配置
[key] : 主键(非空,不重复),想要自增的话设置ID属性
[Required] : 非空
[StringLength(4, MinimumLength = 2)] : 设置长度
[DataType(DataType.DateTime)] : 设置属性对应数据库中的属性
[ForeignKey("Student")] : 外键属性(通常在1-1或1-0中使用)
常用Fluent API
Has方法:
HasOptional:前者包含后者一个实例或者为null
HasRequired:前者(A)包含后者(B)一个不为null的实例
HasMany:前者包含后者实例的集合
With方法:
WithOptional:后者(B)可以包含前者(A)一个实例或者null
WithRequired:后者包含前者一个不为null的实例
WithMany:后者包含前者实例的集合
.MapKey :是指定外键名的
.HasForeignKey(s => s.AccommodationId) //外键
//配置多对多的中间关系表
this.HasMany(a => a.Trips).WithMany(t => t.Activitys).Map(m =>
{
m.ToTable("TripActivities"); //中间关系表表名
m.MapLeftKey("ActivityId"); //设置Activity表在中间表主键名
m.MapRightKey("TripIdentifier"); //设置Trip表在中间表主键名
});
EF6支持的注解
Key :主键
KeyAttribute : 意味着一个或多个属性确定一个实体
StringLengthAttribute :设置数据字段允许的最大长度与最小长度
MaxLengthAttribute : 指定属性中允许的数组或字符串数据的最大长度。
ConcurrencyCheckAttribute : 指定一个属性参与乐观锁并发检查
RequiredAttribute : 指定数据字段不为空
TimestampAttribute : 指定序列号,为属性加上临时时间戳 ~不太确定
ComplexTypeAttribute : 指定实体类为复杂类型
ColumnAttribute : 指定属性映射到数据库的哪一列(有属性),参照文档baidu去
TableAttribute : 指定实体类映射到那个表
InversePropertyAttribute : 指定表示同一关系的另一端的导航属性的反向属性。
ForeignKeyAttribute : 表示关系中用作外键的属性。
DatabaseGeneratedAttribute :指定数据库生成属性值的方式。
NotMappedAttribute : 表示应从数据库映射中排除属性或类。
CRUD
在EF中,所有的操作可以分为两类,一类是更新(增删改),一类是查找(查)。 更新主要是对数据库中的表数据做出更改、查主要是对数据库中的表进行查询。
设计这个就
增加(C)
增加一个实体很简单,主要就是对应数据库和实体之间的配置约束,当然了,要满足一个约束就要增加很多的限制条件,这无可厚非,但是一定要满足一个低耦合的规则,不要荣誉。
向数据库增加实体有两种方案,一个是增加一个实体,一个是增加一批实体,对应方法就是。
//增加一个特定的实体类
public Berths BerthsAddSave(Berths entity)
{
berths.Add(entity);
SaveChanges();
return entity;
}
//增加一批相同的实体。这样就将实体放入一个集合中,进行批量添加。
public IList<Berths> BerthsAddSave(IList<Berths> entity)
{
berths.AddRange(entity);
SaveChanges();
return entity;
}
添加得时候要注意细节,如主键不能重复引入,这里必须调用SaveChanges();来同步数据库的更改,这里的SaveChanges具有事务的概念,如果一次执行多个语句调用一个save,如果发生异常,则全部操作回滚,不会对数据库进行更改。
删除(R)
删除数据是一个麻烦且危险的事情,所以一般删除的时候不要再运行的时候删,可以标记一个flag,由DBA来统一删除。
与增加相同,删除也是调用一个方法.Remove(entity);这是删除一个实体,这个方法很特殊,他不能删除不是自己context维护的实体类,你想要删除,就得在数据库中找到那个实体类,然后将实体类返回,再调用Remove方法删除这个实体类,否则是不可以删除的。当然也可以使用状态删除
public Berths BerthsCSave(Berths entity)
{
berths.Remove(entity);
SaveChanges();
return entity;
}
//这里的查询当然可以封装成简洁的语法糖形式。
var queryable = entityContext.berths.Where(b => b.BNo == "13").FirstOrDefault();
entityContext.BerthsCSave(queryable);
状态删除
目的是节省一次查询数据库的操作,为了提升一些性能。利用5中状态的特性来删除
var berths = new Entity.Berths(){BNo == "13"};//自己new一个实体
//调用下面的语句,将游离态转换成未改变的状态。
entityContext.Entry(berths).State = System.Data.Entity.EntityState.Unchanged;
entityContext.BerthsCSave(berths);//这样就能删除了。
级联删除
删除的时候往往涉及着外键的调用,所以若果是回答问题等信息,如果要删除那个人,那么那个人的所有信息都应该被删除。这就是级联删除。CodeFirst默认开启级联删除,考虑到EF中的级联删除并不常用,所以可以在OnModelCreating方法中使用Fluent API关掉所有主外键关系的级联删除。
级联删除如果没有配置的话,如果删除主表实体,那么会报错,因为子表有在引用主表实体的字段。要想完成级联删除的话,就需要配置。但是这里的配置用注解配置并不能实现,只能用fluent API来实现。
// This method is called when the model for a derived context has been initialized,
// but before the model has been locked down and used to initialize the context.
// The default implementation of this method does nothing, but it can be overridden
// in a derived class such that the model can be further configured before it is
// locked down.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//在创建数据库模型的时候开启级联删除规则(针对所有)
//modelBuilder.Conventions.Add<OneToManyCascadeDeleteConvention>();
//在创建数据表的时候默认不开启级联删除,但是可以在以后的配置中单独为特定的表配置级联。 //modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
//可以不用上边的,在单独的实体中配置级联(针对单独)
modelBuilder.Entity<Student>()
.HasOptional(s=>s.Berths)
.WithRequired(y=>y.Student)
.WillCascadeOnDelete(true);
base.OnModelCreating(modelBuilder);
}
更改(u)
EF中会为每个 管理的 实体对象 创建一个代理包装类对象,该对会跟踪 实体对象 的状态和每个属性的状态;
一、通常使用EF更新的方式,先查询出要修改的数据,然后再修改新的值;实体对象被修改的属性 在 代理包装类对象里 的对应属性状态会被修改记录下修改状态,等到调用SaveChanges时,EF会遍历其管理的每个实体对象,并根据其 包装类对象 的状态,生成增删改查sql语句并执行;
此例中修改操作,会生成 修改的sql语句(注意:此处只为修改过的属性生成sql语句),最后执行。
//1.先查出来要进行修改的对象,此时返回的是个Book对象的一个代理对象(包装类对象)
Book book = context.Books.Where(b => b.BookID == 2).FirstOrDefault();
Console.WriteLine("修改之前");
Console.WriteLine(book.BookName);
//2.修改名称 修改了那些字段,EF就会生成那些字段的更新语句
//注意:此时修改的是代理类对象的属性,这些属性会设置内部的Book对象的属性,同时标记此属性已经被修改过,同时也标记此对象被修改过
book.BookName = "222";
book.Price = 12;
book.Remark = "修改后的";
//3.保持到数据库中
//注意:EF上下文会检查容器内部所有的对象,找到带有修改标记的对象,然后根据带有修改标记的属性,生成对应的查询语句 最后执行修改语句
context.SaveChanges();
Console.WriteLine("修改成功");
查询(D)
查询也没有什么可以注意的,就是要多谢linq与sql,注意两者的对应关系,这样就能找到响应的数据了。
var student = (from s in entityContext.students
where s.SNo == "20174232051"
select s).FirstOrDefault();
上述是最简单的linq查询,通过orm框架EF转换成对应的sql去数据库执行。
但是这里要注意,查询过程是延迟的,他不会在执行linq语句时对数据库进行查询,而是在真正的使用的时候才会查询数据库。
关联查询
当我查询一个类的时候,想将它引用表的数据也查询出来,这就需要Include方法,linq写法如下:
var student = (from s in entityContext.students
where s.SNo == "20174232051"
select s).Include(s => s.Berths).SingleOrDefault();
//这里的student表中有Bertis的实体,用Include方法将Bertis包含,就可以在查询student的时候也把Berths实体也查询出来。
Include是当前实体所包含的实体,如果想调用更深层,比如Berths还关联着实体,就可以调用ThenInclude()这个方法,来包含更深一层的实体。
普通的查询出来的实体默认是被dbcontext跟踪的,这无疑会有一些性能问题,有一些不必要的数据完全可以不跟踪,所以可以使用.AsNoTracking()这个方法来实现:
var student = (from s in entityContext.students
where s.SNo == "20174232051"
select s).Include(s => s.Berths).AsNoTracking();
连表查询
在ef中,如果实体中没有对应的包含关系(外键),则需要使用到连表查询,在ef中连表查询是简单的,首先是找到两个(多个)表的实体,然后关联字段,这里关联字段有两种方式,可以使用where的方式,也可以使用join的方式,在linq中,join默认是inner join查询:如
(from a in Context.Set<TB_UserBase>()
from bb in Context.Set<MAP_Auth_UserBase>()
from cc in Context.Set<TD_Auth>()
where a.Id == bb.UserBaseId && bb.AuthId == cc.Id
join b in base.Context.Set<TB_userInfo>() on a.Id equals b.UserBaseId
select new
{
a.Id,
a.UserName,
a.IsLogout,
b.NickName,
b.EMail,
cc.AuthName
}).ToList();
这样取出的数据是交集部分,在ef中,可以用匿名类来取出对应的字段,可以理解为一个虚拟表,但是不是虚拟表。如果我们想使用左连接。则需要用into的方式,如
(from a in Context.Set<TB_UserBase>()
from bb in Context.Set<MAP_Auth_UserBase>()
from cc in Context.Set<TD_Auth>()
where a.Id == bb.UserBaseId && bb.AuthId == cc.Id
join b in base.Context.Set<TB_userInfo>() on a.Id equals b.UserBaseId
into ec
from iec in ec.DefaultIfEmpty()
select new
{
a.Id,
a.UserName,
a.IsLogout,
iec.NickName,
iec.EMail,
cc.AuthName
}).ToList();
这样我们就能取出含有空的字段。也就是对应了sql中的左连接。
使用原生sql
//查询
var student = entityContext.students.SqlQuery("SELECT * FROM dbo.Blogs").ToList();
//非查询
int a = entityContext.Database.ExecuteSqlCommand("UPDATE dbo.Blogs SET Name = 'Another Name' WHERE BlogId = 1");
使用原生sql的时候要注意,防止sql注入。
事务与UOW(工作单元)
事务是数据库操作的基本单元,EF是默认支持事务的(使用的数据库要也支持)。
调用SaveChager()的时候,自动触发事务机制:
- 只要SaveChager()的一个sql操作失败,所有操作回滚
- 只有所有的操作都成功,事务才提交。
EF6学习中的高深名词及其含义
一、表达式树(Express)
在EF中,我们可以使用Linq来对数据库进行查询,那么这个过程是怎样实现的呢?
》》首先,我们编写的Linq会通过映射的方式变成sql,这个过程又会分解为:IQueryable继承IEumerable,而Linq使用Express继承LambadExpress,这样的lambad会被封装成一个表达式树,然后进行Lambad解析,解析变成sql语句,这个过程在声明的时候执行,解析出来的sql会在真正的调用的时候(如ToList(),singletodefult等方法执行的时候)再执行在数据库中。
》》表达式树不是Func,他记录着func的变化,当表达式树调用compile方法时,这是才能被使用,这时候也就是一个真正的func。
二、context的跟踪机制
当创建上下文对象的时候,你会继承condext,这个会跟踪你声明的dbset,也就是实体,当我要查询一个实体的时候,context会优先在已经跟踪的实体中查找,如果没有,则会在数据库中查找,并设置为跟踪状态,如果想要设置查找的实体不被跟踪,就会在查找的时候设置==.AsNoTracking()==,这个状态,这样早的好处就是提高了一些维护的性能,但是当多次使用该实体的时候可能会消耗更多的查找时间,所以使用的时候还是要结合实际使用。
三、EF的加载方式
1)延迟加载(Lazy Loading)
当实体第一次被读取时,相关数据不会被获取。但是,当你第一次尝试存取导航属性时,该导航属性所需的数据会自动加载。结果会使用多个查询发送到数据库——一次是读取实体本身,然后是每个相关的实体。DbContext类默认是使用延迟加载的。
使用延迟加载必须满足以下两个条件
**1、类是由Public修饰,不能是封闭类,也就是说,不能带有Sealded修饰符**
》因为public修饰是保证你的实体类是可以被外部访问的。
**2、导航属性标记为Virtual。**
》用virtual关键字是保证导航属性可以被重写。重写是保证延迟加载的关键。
延迟加载的作用:
- 1、拼凑条件一起提交,降低数据库交互次数,提高数据库的吞吐量,这也是延迟加载的优点。*
//student包含导航属性Berths
//当执行下面这条语句时,并不会执行sql语句,query是一个IQueryable类型的,他并没有查询数据库,而是一个sql。
var query = from s in entityContext.students
where s.SNo == "20174232051"
select s;
foreach (var item in query)
{
//这里执行sql。将符合条件的实体类加载到内存。
}
- 2、针对外键属性,EF只有在这个外键属性用到的时候才会去查询。(默认不会获取外键属性)
var student = (from s in entityContext.students
where s.SNo == "20174232053"
select s).FirstOrDefault();
//上面调用了FirstOrDefault()方法,这个会返回一个实体类型,也就是说执行了sql语句,并返回来实体类,并包装成了student实体。
Console.WriteLine(student.Berths.BNo);//调用Student实体类的导航属性,来获取对应的BNo属性。
//上面在调用的时候会自动的生成一个关联查询,这样就能获取到Berths的BNo属性。
这里就需要注意一下性能问题,如果使用延迟加载:
var student = (from s in entityContext.students
where s.SNo == "20174232053"
select s);
foreach (var item in query)
{
//很明显,每一次获取一个实体的导航属性都需要一次查询,在一个表的数据量很大时,可能会引起内存的问题。预先加载无疑是一个解决办法。
Console.WriteLine(student.Berths.BNo);
}
2)预先加载<_Eager Loading>_
使用Include方法关联预先加载的实体。
注:需引入:using System.Data.Entity;命名空间
预先加载是指一种实体类型的查询,它还会在查询中加载相关实体。这一是对延迟加载的一种解决方案。
var student = (from s in entityContext.students
where s.SNo == "20174232051"
select s).Include(s=>s.Berths);
当一个表中有多个级别的相关实体,就可以逐个级别的加载
// Load all blogs, all related posts, and all related comments.
var blogs1 = context.Blogs.Include(b => b.Posts.Select(p =>p.Comments)).ToList();
如果两张表记录很大(字段多,上百万条记录),采用Include()关联两张表效率会很低,因为:它除了要做笛卡尔积,还要把数据一次性查询出来。因此:在字段多,记录多的情况下,建议使用延迟加载。
在此需要说明的是:EF中有两种表关联的方法,一种是Join()方法,一种是Include()方法
Join()方法使用说明:两表不必含有外键关系,需要代码手动指定连接外键相等(具有可拓展性,除了值相等,还能指定是>,<以及其他对两表的相应键的关系),以及结果字段。
Include()方法说明:两表必须含有外键关系,只需要指定键名对应的类属性名即可,不需指定结果字段(即全部映射)。默认搜索某表时,不会顺带查询外键表,直到真正使用时才会再读取数据库查询;若是使用 Include(),则会在读取本表时把指定的外键表信息也读出来。
3)显式加载<_Explicit Loading>_(不推荐使用)
有点类似于延迟加载,只是你在代码中显式地获取相关数据。当您访问一个导航属性时,它不会自动加载。你需要通过使用实体的对象状态管理器并调用集合上的Collection.Load方法或通过持有单个实体的属性的Reference.Load方法来手动加载相关数据。
背景:关闭延迟加载后,单纯查询主表的数据,后面又想再次查询从表,这个时候就需要用到显示加载了.==》所以说:一般不用关闭延迟加载。
public ActionResult linqTOsql()
{
using (var db = new StudentContext())
{
var Elist = db.Scores.Where(t => t.StudentScore > 80);
foreach (Score item in Elist)
{
var model = db.Entry(item);//Entry: 获取给定实体的 System.Data.Entity.Infrastructure.DbEntityEntry<TEntity> 对象,以便提供对与该实体有关的信息的访问以及对实体执行操作的功能。
model.Collection(t => t.Student).Load();//Score中的Student导航属性 必须为泛型集合 查询/加载实体集合
//我们可以使用以下代码来加限制来避免全部加载导航的属性
// model.Collection(t => t.Student).Query().Where( 限制条件 ).Load();
foreach (Student A in item.Student)
{
}
}
}
return View();
}
4)性能注意事项
很好的一篇博客:https://www.cnblogs.com/chenwolong/p/UpYourEF.html
==如果你知道你立即需要每个实体的相关数据,预先加载通常提供最佳的性能。==因为单个查询发送到数据库并一次性获取数据的效率通常比在每个实体上再发出一次查询的效率更高。例如,在上面的示例中,假定每个系有十个相关的课程,预先加载会导致只有一个查询(join联合查询)往返于数据库。延迟加载和显式加载两者都将造成11个查询和往返。在高延迟的情况下,额外的查询和往返通常是不利的。
另一方面,在某些情况下使用延迟加载的效率更高。预先加载可能会导致生成SQL Server不能有效处理的非常复杂的联接查询。或者,如果您正在处理的是需要访问的某个实体的导航属性,该属性仅为实体集的一个子集,延迟加载可能比预先加载性能更好,因为预先加载会将所有的数据全部加载,即使你不需要访问它们。如果应用程序的性能是极为重要的,你最好测试并在这两种方法之间选择一种最佳的。
延迟加载可能会屏蔽一些导致性能问题的代码。例如,代码没有指定预先或显式加载但在处理大量实体并时在每次迭代中都使用了导航属性的情况下,代码的效率可能会很低(因为会有大量的数据库往返查询)。一个在开发环境下表现良好的应用程序可能会在移动到Windows Azure SQL数据库时由于增加了延迟导致延迟加载的性能下降。你应当分析并测试以确保延迟加载是否是适当的
