明确 EF 建立的数据库和对象之间的关系
EF 也是一种 ORM 技术框架, 将对象模型和关系型数据库的数据结构对应起来, 开发人员不在利用 sql 去操作数据相关结构和数据.
以下是 EF 建立的数据库和对象之间关系
关系数据库 | 对象 |
数据库 | DbContext 类 |
表 | DbContext 中的 DbSet< 实体类名 & gt; |
表间的关联 | 实体类之间的关联 |
字段 | 实体类的公有属性 |
单条数据 | 单个实体类的对象 |
约束(主键、外键默认值) | 实体类中的特性 |
了解 EDM( 实体数据模型)
EF 使用概念模型, 映射和存储模型. 三个模型来描述映射关系
概念模型 (.csdl) ︰ 即为直接使用的对象类, 并包含它们之间的关系.
存储模型 (.ssdl) ︰ 数据库设计结构, 包括表, 视图, 存储的过程和他们的关系和键.
映射 (.msl) ︰ 包含将概念模型 (对象类) 映射到存储模型 (关系数据库) 的信息.
CodeFirst : 实体结构发生变化, 如何更新数据库结构?
数据准备
- public class Place
- {
- [Key]
- public int PlaceID { get; set;}
- public string Provice { get; set; }
- public string City { get; set; }
- // 导航属性
- public List<People> Population { get; set; }
- }
- public class People
- {
- [Key]
- public int PeopleID{ get; set; }
- public string Name{ get; set; }
- public int Age{ get; set;}
- // 外键, 对应导航属性对象中的标识
- [ForeignKey("Place")]
- public int PlaceID{ get; set;}
- // 导航属性
- public Place Place { get; set; }
- }
创建 DbContext 派生类
- public class TestDB:DbContext
- {
- public TestDB():base("name=Test") { }
- public DbSet<Place> Place { get; set; }
- public DbSet<People> People { get; set; }
- }
执行一次代码
- class Program
- {
- static void Main(string[] args)
- {
- using (var context = new TestDB())
- {
- context.Place.Add(new Place {
- PlaceID = 2,
- Provice="jiangsu",
- City="wuxi"
- });
- context.SaveChanges();
- }
- }
- }
发现在数据库已经建好了相应的表, 这时在 people 类增加 Sex 属性, 随便增加一条数据执行代码, 报了如下错, 很显然 EF 并不会帮我们更新数据库结构:
有两种情况处理这种情况, 手动和自动迁移方式去更新数据库
自动迁移
1, 选择所在的项目, 在 VS 的程序包管理控制台输入命令(Tools 菜单中打开 Package Manager Console):
enable-migrations -EnableAutomaticMigrations
2, 执行完毕, 项目目录中多出一个名为 Migrations 的文件夹, 里面有个 Configuration.cs 文件, 打开它看到如下代码:
- internal sealed class Configuration : DbMigrationsConfiguration<EF1.Model.TestDB>
- {
- public Configuration()
- {
- AutomaticMigrationsEnabled = true;
- ContextKey = "EF1.Model.TestDB";
- }
- protected override void Seed(EF1.Model.TestDB context)
- {
- // This method will be called after migrating to the latest version.
- // You can use the DbSet<T>.AddOrUpdate() helper extension method
- // to avoid creating duplicate seed data.
- }
- }
ContextKey 属性指定了要执行迁移的 DbContext, 以便多个 DbContext 共存迁移不会产生冲突.
在迁移过程成功后会执行 Seed 方法, 可以利用这个方法添加一些初始化数据
在删除属性的时候, 会迁移失败, 提示操作会造成数据丢失, 可以在构造函数中添加如下代码, 忽略数据丢失.
- public Configuration()
- {
- AutomaticMigrationsEnabled = true;
- AutomaticMigrationDataLossAllowed = true;
- ContextKey = "EF1.Model.TestDB";
- }
3. 以上步骤已开启支持迁移, 每次更新实体结构时, 在项目执行前, 需要在管理控制台输入以下命令先更新数据库
update-database
或是在 DbContext 构造函数中添加以下代码, 每次将自动迁移至最新的数据库 Schema
- public class TestDB:DbContext
- {
- public TestDB():base("name=Test") {
- //TestDB:dbContext;
- //Test: 连接字符串名称
- // 迁移至最新版本
- Database.SetInitializer(new MigrateDatabaseToLatestVersion<TestDB, Configuration>("Test"));
- }
- public DbSet<Place> Place { get; set; }
- public DbSet<People> People { get; set; }
- }
手动迁移
两种迁移方式基本相同, 只是相比自动迁移, 手动迁移会记录每次更新的情况, 允许回滚数据库到某个指定版本, 适合于团队开发.
1. 一样要先开启支持迁移, 并吧生成的 Configuration 构造函数中 AutomaticMigrationsEnabled 置为 false, 表示不使用自动迁移.
2. 每次更改实体结构或映射配置时, 在程序包管理控制台运行以下代码
Add-Migration ChangeSet1
会自动在前面生成的 Migrations 文件下生成一个 ChangeSet1 迁移文件
类的内容就是对于我们所更改的实体结构或映射配置, EF 迁移要执行的内容, 以下就是我增加了一个 Phone 属性, 运行 Add-Migration ChangeSet1 所生成的. 此迁移文件后面可以用来回滚到某个版本
- public partial class ChangeSet1 : DbMigration
- {
- public override void Up()
- {
- AddColumn("dbo.People", "Phone", c => c.String());
- }
- public override void Down()
- {
- DropColumn("dbo.People", "Phone");
- }
- }
3. 成功迁移文件后, 运行下面代码更新至最新的迁移文件对应的版本.
Update-Database
或是回滚到指定版本
Update-Database -TargetMigration ChangeSet1
多种方式的增删改查
基础知识
上下文是根据检测实体的 EntityState 枚举状态, 来执行相应的增 / 删 / 改操作.
EntityState 枚举状态如下:
Detached: 对象存在, 但未由对象服务跟踪. 在创建实体之后, 但将其添加到对象上下文之前, 该实体处于此状态;
Unchanged: 对象添加到上下文中后, 还未被修改过;
Added: 对象已添加到对象上下文, 还没有调用 SaveChanges() 方法;
Deleted: 将对象从上下文中删除;
Modified: 对象已更改, 还没有调用 SaveChanges() 方法;
DBcontext 类中的方法:
Entry: 将对象加入 EF 容器, 并获取当前实体对象的状态管理对象.
Set<T>: 获取实体相应的 DbSet 类对象, 如 context.Set<Student>().Attach(student);
DbSet 类
Attach: 是把一个已存在于数据库中, 但没有被 dbContext 跟踪的对象, 以 EntityState.Unchanged 状态附加到 dbCotext 中, 对于已有相同 key 的对象存在于 EF Context 的情况, 如果这个已存在对象状态为 Unchanged 则不进行任何操作, 否则将其状态更改为 Unchanged.
Add: 将一个已存在于数据库中的对象, 以 Added 实体状态添加到 EF Context 中.
Remove: 将一个已存在于 EF Context 中的对象标记为 Deleted, 当 SaveChanges(已经存在于 EF Context)时, 这个对象对应的数据库条目被删除.
Find: 按主键去获取一个实体, 首先在 EF Context 中查找是否有被缓存过的实体, 如果查找不到再去数据库查找, 如果数据库中存在则缓存到 EF Context 并返回, 否则返回 null.
AsNoTracking: 无跟踪查询, 不受 EFcontext 管理, 所以查询出来的数据不能做修改, 对于只查询显示的功能, 加上 AsNoTracking 可以提升效率.
.....
增加
基本方式
- static void Main(string[] args)
- {
- using (var context = new TestDB())
- {
- context.Place.Add(new Place
- {
- PlaceID = 8,
- Provice = "jiangsu",
- City = "wuxi",
- });
- context.SaveChanges();
- }
- }
Entry 方式
附: 用 attch 附加到上下文保存无效, 因为附加后的状态是 unchange, 需要使用 ChangeObjectState 方法更改实体状态, 或是再次使用 Entry 获取状态管理对象更新
- static void Main(string[] args)
- {
- using (var context = new TestDB())
- {
- var obj = new Place()
- {
- PlaceID =9,
- Provice = "jiangsu",
- City = "wuxi",
- };
- // 加入 EF 容器, 并获取当前实体对象的状态管理对象
- var entity = context.Entry<Place>(obj);
- entity.State = System.Data.Entity.EntityState.Added;
- context.SaveChanges();
- }
- }
更新
先查询后更新:
- static void Main(string[] args)
- {
- using (var context = new TestDB())
- {
- var query = context.Place.Where(p => p.PlaceID == 9).FirstOrDefault();
- query.City = "suzhou";
- context.SaveChanges();
- }
- }
- Entry
- using (var context = new TestDB())
- {
- var obj = new Place
- {
- PlaceID=9,
- City="changzhou"
- };
- // 将 obj 加入到上下文, 并去获取实体对象的状态管理对象
- var entity = context.Entry<Place>(obj);
- // 置为未被修改过
- entity.State = System.Data.Entity.EntityState.Unchanged;
- // 设置该对象的 City 属性为修改状态, 同时 entity.State 由 Unchanged-> Modified 状态
- entity.Property("City").IsModified = true;
- context.SaveChanges();
- }
- Attach
- using (var context = new TestDB())
- {
- var obj = new Place
- {
- PlaceID=9
- };
- // 将 obj 附加到上下文, 此时实体状态为 Unchanged
- var newobj=context.Place.Attach(obj);
- // 这时 City 属性为修改状态, 同时 entity.State 自动由 Unchanged-> Modified 状态
- newobj.City = "yangzhou";
- context.SaveChanges();
- }
删除
常规操作: 先查询后删除
- using (var context = new TestDB())
- {
- var queryObj = context.Place.Where(q=>q.PlaceID==8).FirstOrDefault();
- // 当前的实体对象已置为 Deleted 状态
- context.Place.Remove(queryObj);
- context.SaveChanges();
- }
- Entry
- using (var context = new TestDB())
- {
- var obj = new Place
- {
- PlaceID = 6
- };
- // 将 obj 附加到上下文, 并获取实体对象的状态管理对象
- var entity =context.Entry<Place>(obj);
- // 将状态置为 Deleted
- entity.State = System.Data.Entity.EntityState.Deleted;
- context.SaveChanges();
- }
- Attach
- using (var context = new TestDB())
- {
- var obj = new Place
- {
- PlaceID = 7
- };
- // 将 obj 附加到上下文, 此时实体状态为 Unchanged
- var newobj = context.Place.Attach(obj);
- context.Place.Remove(newobj);
- context.SaveChanges();
- }
查询
对于简单查询, 不需要进行修改操作, 建议使用 AsNoTracking, 无跟踪查询来提升效率
- using (var context = new TestDB())
- {
- var obj = context.Place.Where(p => p.PlaceID == 9).AsNoTracking().ToList();
- }
Find 方法 : 之前一直使用 lambda 查询(where,FirstOrDefault), 有一个问题就是不管我们要查询的实体是否被 dbconext 缓存, 都会去查询数据库, 而使用 Find 通过主键来查询, 会首先在 EF Context 中查找是否有被缓存过的实体, 如果查找不到才去数据库查找
- using (var context = new TestDB())
- {
- var obj = context.Place.Find(9);
- }
注: 更详细的 EF 查询学习会在查漏补缺(二) 数据加载 中继续记录
来源: https://www.cnblogs.com/qiuguochao/p/10123743.html