Entity Framework
前期准备
- 自动属性
- var
- 对象、集合初始化器
- 匿名类
- 拓展方法
- lambda表达式
介绍
托管于非托管代码
C,c++编译 –》dll动态链接库 –》 二进制机器码,直接交给操作系统运行
C# 编译dll(程序集) –》 IL –》CLR 即时编译JIT –》 二进制机器码
介绍EF
ADO NET Entity Framework是以ADO NET为基础所发展出来的对象关系对应O/R Mapping(ORM)解决方案
什么是0 / R Mapping
广义上,ORM指的是面向对象的对象模型和关系型数据库的数据结构之间的相互转换。
狭义上,ORM可以被认为是,基于关系型数据厍的数据存储,实现一个虚拟的面向对象的数据访问接口。理想情况下,基于这样一个面向对象的接口,持久化一个00对象应该不需要要了解任何关系型数据库存储数据的实现细节。
O(object 面向对象的对象模型)M(mapping xml文件)R(related 关系表)
database first
添加新项–》数据–》ADO.NET实体数据模型–》生成操作数据库上下文对象,model.cs,designer设计器
1 | public partial class GraduationProjectEntities : DbContext |
Context:上下文,entity framework所有有关数据库的操作都封装在此类中
访问表两种方式dbContext.TreeDetail
或者dbContext.Set<TreeDetail>()
创建好的edmx文件以xml形式打开
1 | <!-- SSDL content --> |
EF增删改
插入
1 | //创建访问数据库统一入口 |
更新
1 | var dbContext = new Entities(); |
删除
1 | var dbContext = new Entities(); |
dbContext.Entry(person).State = System.Data.Entity.EntityState.Deleted;标记原理:
在ef保存改变里面的最后几步里面会遍历所有标记为删除,插入,更新的实体,将他们都执行相应的sql
1 | /// <summary>接受在对象上下文中对对象所做的所有更改。</summary> |
报错:
对一个或多个实体的校验失败:一般是插入的数据格式与期望格式不匹配
EF生成流程
ef封装操作 –》 ADO.NET操作–》sql–》执行
查询 linq –》 生成expression–》执行ADO.NET操作–》sql–》执行
与ADO.NET性能差距主要在封装,但相对于非常快的代码执行,ef性能差距主要表现在expression生成的sql代码有时候会有很大性能问题
model first
添加新项–》数据–》空ef设计器模型–》编辑设计器
另一个用于数据库设计的工具powerdesign
创建实体Person,PersonInfo,College,Subject
明确表之间对应关系:一对一,一对多,多对多
设计器里面添加三条关系,将Person与其他表联系起来:一个人一个信息,多个学院对应多个人,多个学科对应多个人
针对一对一,一对多关系很简单就是在表里面增加一条外键即可,但是多对多如何实现:
虽然设计器里面只是简单的用—-线连接了起来但是,生成的数据库中会增加一个两张表主键的对应关系的表,使用根据模型生成数据库,执行生成的sql(注意保存数据,表会被drop掉),可以看到存有两张表主键的对应关系的表
数据库变化了,可以在设计器中右键从数据库更新模型
包含外键的数据插入
通过设置一个人对应多个学科可以看到Person.cs里面多了
1 | public virtual ICollection<Subject> Subject { get; set; } |
Subject集合
1 | var dbEntities = new luox78_成绩管理系统Entities(); |
插入有外键的数据共有2种方法:
通过entity里面的集合添加
将关联实体里面的外键属性set成对应的主键
Linq查询
sql:select * (1)from Person(2) where name=“luox78”(3)
真正的执行顺序应该是231,linq里面一方面为了更符合操作,一方面为了能点出属性,先写from最后select
1 | var details = from d in dbContext.TreeDetail |
基本上所有的类sql语句都可以转换成拓展方法的形式,等同于
1 | var details = dbContext.TreeDetail.Where(d => d.pingtaiziyuanhao == "11111111111"); |
IQueryable接口即linq实现机制
上文说道linq查询,返回值类型,我用的var,实际上是IQueryable
1 | [ ] |
Expression:为linq编译时生成的表达式
ElementType:Expression返回类型
Provider:获取与此数据源关联的查询提供程序(对应数据源的驱动方法),linq可以提供对不同的集合类型查询,就是Provider实现的
延迟加载
对于像
var details = from d in dbContext.TreeDetail
where d.pingtaiziyuanhao == "11111111111"
select d;
的linq语句,只是在编译时将detail中的属性填充满,真正存在数据库中的数据此时并没有取出
只有在使用到details时,才会执行sql查询
情况一:
1 | var dbContext = new GraduationProjectEntities(); |
只有在foreach时才会查询
问题
1 | foreach (var treeDetail in list) |
两次foreach会执行两遍查询:将IQueryable集合转换成List集合List<TreeDetail> list = detail.ToList();
集合类型:本地型集合(数据存储在内存中),离线型集合(IQueryable,数据并没有分配至内存)
所以讲IQueryable转换成list实际就已经取出数据至内存中了
分页
1 | var detail = dbContext.TreeDetail.Take(100).OrderBy(t => t.pingtaiziyuanhao).Skip(100*(2-1)); |
ef帮我们实现了相应的sql编写,查看sql语句可以看到就是通过row_number()实现的分页sql
方法链的where,select顺序没有限制,因为最后会编译成相应表达式