Entity Framework之Code First
Code First使用步骤
-
添加类创建实体模型,并创建导航属性,设置实体模型之间关系。
public class Grade{ public int GradeId{get;set;} public string GradeName{get;set;} //导航属性 public virtual List<Studnet> Student {get;set;} } public class Student{ public int StudentId{get;set;} public string StudentName{get;set;} public int Age{get;set;} //导航属性 public virtual Grade Grede {get;set;} }
-
安装Entity Framework包
- 通过NuGet在线安装Entity Framework包
- 使用“程序包管理器控制台”通过“install-package EntityFramework”命令安装
-
创建dbContext上下文
-
首先在配置文件中(app.config)中配置数据库链接字符串,代码如下:
<configuration> <connectionStrings> <add name="name" connectionString="Data Source=.;Initial Catalog=Test;Persist Security Info=true;User ID=sa;Password=sa"; providerName="System.Data.SqlClient"/> </connectionStrings> </configuration> <!-- 必须指定providerName属性 Initial Catalog:为数据库赋值名称 Persist Security Info=true:属性的意思是表示是否保存安全信息,其实可以简单的理解为"ADO在数据库连接成功后是否保存密码信息" -->
-
接下来创建dbContext类,此类继承System.Data.Entity.DbContext类,其构造函数调用了积累带参数的构造函数,用来读取配置文件中的数据库链接字符串。
public class DemoContext:DbContext { public DemoContext():base("Demo1") { //数据库不存在时创建数据库 Database.SetInitializer<DemoContext>(new CreateDatabaseIfNotExists<DemoContext>()); //模型改变时重新创建数据库 Database.SetInitializer<DemoContext>(new DropCreateDatabaseIfModelChanges<DemoContext>()); //每次启动应用程序时创建数据库 Database.SetInitializer<DemoContext>(new DropCreateDatabaseAlways<DemoContext>()); //从不创建数据库 Database.SetInitializer<DemoContext>(null); } public DbSet<Grade> Grade { get; set; } public DbSet<Student> Studnent { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new StudentConfig()); modelBuilder.Configurations.Add(new GradeConfig()); } }
-
-
对数据进行操作
接下来就可以像Database first方式一样,使用dbContext对象对数据库进行操作了。
Code First的映射规则
默认约定
-
关于表和字段名的约定
Code Fisrt约定表名使用实体类名的复数形式来命名表明,属性映射的列使用与类中属性一致的名字命名
-
关于主键的约定
Code First默认规定将命名为Id或[类名]Id的属性是为类的主键,如果是int类型或GUID类型同时会设为标识列。
-
关于字符串属性的约定
Code First将String 类型属性映射为不限长度的非空列中,默认数据类型为nvarchar(max),且允许为空。
-
关于布尔属性的约定
Code First将bool类型属性映射为bit类型,且不允许为空。
-
关于外键的约定
对于外键,如果在类中添加了引用类型,而这个引用类型也在Entity Framework的上下文中,EF会把这种属性成为导航属性。
一旦EF在类中检索到导航属性,就会寻找对应的外键。EF会认为属性名+Id或者类名+Id可能就是外键属性,如果找到名称一致且类型与导航属性目标类的主键类型一致,则认为是外键。如果类型不一致,EF则会认为该类设置有误。
如果没有找到符合要求的属性,EF会自己添加一个外键属性。另外,对于不同的关系也有一些不同的约定。
- 一对一关系:要求导航属性双方都应该具有外键配置。
- 一对多关系:要求多的一方设置外键。同时如果在一方这边设置了集合类型的导航属性,那么EF会自动到目标类中寻找外键属性。
- 多对多关系:会生成一个中间关系表。
修改映射规则
-
数据注解
数据注解方式是通过给类或者属性增加特性来解决的,需要引入两个命名空间:
-
System.ComponentModel.DataAnnotations命名空间下的特性是控制表中列的属性的。
包括:Key、Required、MinLength、MaxLength、StringLength、Timestamp、ConcurrencyCheck等
-
System.ComponentModel.DataAnnotations.Schema命名空间下的特性是控制数据库结构的。
包括:Table、Column、ForeignKey、NotMapped等
特性 说明 Key 声明主键 Required 非空声明 MinLength/MaxLength 设置string类型的最大长度和最小长度,数据库对应Nvarchar StringLenth 设置string类型的长度,数据库对应Nvarchar TimeStamp 将byte[]类型设置为timestamp类型 ConcurrencyCheck 并发检查,执行Update操作时,会检查并发性(乐观锁) Table 给代码中类的属性换一个名来映射数据库表中的列名,(还可以设置表的架构名称[Table(“myAddress”,Schema=“admin”)]) Column 给代码种类的属性换一个名来映射数据库中表的列名(还可以设置列的类型、列在表中的显示顺序[Column(“myAddressName”,Order=1,TypeName=“varchar”)]) ForeignKey 设置外键 NotMapped 类中的列名不在数据库表中映射生成。(还可以只设置get属性或者只设置set属性,在数据库中也不映射) 数据注解示例:
[Table("Students")]//设置类映射的数据库表名 public class Student{ [Key]//主键声明 public string StudentId{get;set;} [required]//非空声明 [MaxLength(20),MinLength(10)]//设置最大长度和最小长度 public string StudentName{get;set;} [Column("Address",Order=1,TypeName="varchar")]//设置映射数据库中表的列名、顺序、类型 public string StudentAddress{get;set;} [NotMapped]//不映射列 public int Age{get;set;} public int GradeId{get;set;} [ForeignKey("GradeId")]//设置外键 public virtual Grade Grade{get;set;} }
-
-
Fluent API
使用Fluent ApI方式,需要重写DbContext类中的OnModelCreating()方法来实现。
配置 Fluent API 说明 实体相关配置 HasIndex() 实体的索引 HasKey() 实体的主键 HasMany() 1对多的或者多对多关系 HasOptional() 一个可选的关系,这样配置会在数据库中生成一个可控的外键 HasRequired() 一个必要的关系,这样配置会在数据库中生成一个不能为空的外键 Ignore() 实体或者尸体的属性不映射到数据库 Map() 设置一些优先的配置 ToTable 为实体设置表名 配置 Fluent API方法 说明 属性相关配置 IsRequired() 属性不能为空 IsOptional() 可选的,在数据库中生成可控的列 HasDatabaseGeneratedOption() 配置数据库中对应列的值怎样生成的,如计算、自增等 HasColumnType() 配置数据库中对应列的数据类型 HasColumnName() 配置数据库中对应列的列名 IsConcurrencyToken() 配置数据库中对应列用于乐观并发检测 Fluent API示例:
//数据上下文类 public class MySchoolContext:DbContext { public MySchoolContext():base("MySchool") { } //对象集合 public Dbset<Grade> Grade{get;set;} public DbSet<Student> Student{get;set;} protect override coid OnModelCreating(DbModelBuilder modelBuilder) { //获取Student类的配置 var stuConfig=modelbuilder.Entity<Student>(); stuConfig To Table("Student"); stuConfig.HasKey(s=>s.StudentNo); stuConfig.Property(s=>s.Name).HasColumnName("StudentName") .HasMaxLength(20).IdRequired(); stuConfig.Property(s=>s.Address).HasMaxLength(50); stuConfig.Ignore(s=>s.Age); base.OnModelCreating(modelbuilder); } }
但实际在开发中,项目中有许多实体类,这就不便于所有Fluent API配置写在OnModelCreating()方法中,我们可以为每个实体类创建一个单独的配置类。
//Student的配置类 public class StudentConfig:EntityTypeConfiguration<Studnet> { public StudentConfig() { this.ToTable("Student"); this.HasKeys(s=>s.StudentNo); this.Property(s=>s.Name).HasColumnName("StudentName") .HasMaxLength(20).IsRequired(); this.Property(s=>s.Address).HasMaxLenth(50); this.Ignore(s=>s.Age); } } //DbContext类的OnModelCreateing()方法 protected override void OnModelCreating(DbModelBuilder modelBuilder) { //加载配置文件 modelBuilde.Configurations.Add(new StudentConfig()); }
关系
-
一对一关系
如果我们要将两个类的配置为一对一的关系,则两个类中都要配置相应的引用属性,使用数据注解确定主从表。
使用Fluent API方式
modelBuilder.Entity<Photo>().HasRequired(p=>p.StudentOf)..WithOptional(p=>p.Photo);
-
一对多关系
Code First能通过一些引用属性、导航属性等检测到模型之间的关系,自动为我们生成外键。EF会将符合以下规则命名的导航属性识别为外键。
- 目标类型的键名
- 目标类型名称+目标类型键名称
- 导航属性名称+目标类型名称
使用数据注解方式在实体类中添加外键特。
使用Fluent API方式
modelBuilder.Entity<Student>().HasRequired(s=>s.StudentId).WithMany(g=>g.Students).HasForeignKey(s=>s.GradeId);
-
多对多关系
如果两个类中,各自都是结合类型的导航属性指向另一个类,Code First会认为这两个类之间是多对多关系。
使用Fluent API方式
modelBuilder.Entity<Teacher>().HasMany(t=>t.Course).WithMany(t=>t.Teachers).Map(m=>{ m.ToTable("TeacherCourse"); m.MapLeftKey("TeacherId"); m.MapRightKey("CourseId"); });
或者
modelBuilder.Entity<Course>().HasMany(c=>c.Teachers).WithMany(t=>t.Course).Map(m=>{ m.ToTalbe("TeacherCousers"); m.MapLeftKey("CourseId"); m.MapRightKey("TeacherId"); });
数据迁移
在使用Entity Framework的Code First是,如果模型发生了变化,就会删除数据库,根据模型重新创建数据库,这样,数据库中原先的数据会丢失。“数据迁移”就是解决这个问题。
数据迁移是在“程序包管理器控制台”中通过数据迁移命令实现的。
自动数据迁移
在Migrations文件夹中打开Configuration.cs文件,将AutomaticMigrationsEnabled = false;改为true为自动迁移,false为手动迁移。(手动数据迁移可以回滚,防止出现意外丢失数据)
-
启动自动数据迁移
AutomaticMigrationsEnabled=true;
-
执行迁移
Update-Database (自动迁移只执行这个)
手动数据迁移
-
启用数据迁移
命令:Enable-Migration
执行该命令,Entity Framework首先会检查数据库是否存在,然后会在项目中创建一个Migrations文件夹及继承一个自DbMigrationsCOnfiguration的Configuration类。
Enable-Migrations只需执行一次,在以后的数据迁移中就不需要执行了.
-
创建迁移
命令: Add-Migration 名称
执行上面命令后,会在文件夹Migrations下自动生成一个以时间和名称组合作为文件名的类(类名为迁移的名称).
生成的迁移类中有两个方法,分别为Up()和Down()。
在Up()中,记录了需要升级的修改,在执行迁移时会调用这个方法完成和数据库的同步。
在Down()中,是为了回滚修改而设计的,如果用户希望恢复到某一个迁移点,程序会自动根据已经执行的迁移,判断回滚哪些迁移,执行他们的Down()方法.
-
执行迁移
命令:Update-Database [-参数]
执行命令不带参数,会执行所有迁移,如果指定迁移直接加上名称,相当于版本回滚操作.
如果参数为-Verbose可以查看升级的明细,即同步数据库需要执行的SQL语句.
如果需要回滚操作,在操作完成后,需要将版本之后的迁移使用remove-migration命令删除.例如,有版本 1,2,3的三个数据迁移,此时想回到版本1,就直接Update-database 1,执行完成后,数据库中就已经更新到1版本了,然后在执行两次remove-migration把2和3的迁移文件删除
-
删除迁移
命令:remove-migration
用来移除最后一次迁移。