Entity Framework的入门应用
一、数据模型
- Database First (数据库优先):先创建数据库表,然后自动生成EDM文件,EDM文件生成模型类
- Model First (模型优先):先创建Edm文件,Edm文件自动生成模型类和数据库;
- Code First(代码优先):自己写模型类,然后生成数据库,没有EDM。
这里我们现在 Code First,选择Code First不能在数据库中创建数据表,否则在我们执行数据迁移(Migrations)时会报错。
二、安装 Entity Framework
三、数据表的创建
在EntityFramework中对的数据模型的配置有两种模式,一种是 DataAnnotations(数据注释),另一个是 Fluent API(利用接口)。DataAnnotaions 配置相对简单些,Fluent API 可以配置些复杂的功能。
例如我们要创建用户表(不用执行,Code First全用代码管理数据库),Sql语句如下:
CREATE TABLE [dbo].[T_User]
(
[ID] [numeric](18, 0) NOT NULL IDENTITY,
[UserName] [nvarchar](128),
[PassWord] [nvarchar](128),
[UserType] [int] NOT NULL,
[Description] [nvarchar](512),
[Timestamp] [datetime] NOT NULL,
CONSTRAINT [PK_dbo.T_User] PRIMARY KEY ([ID])
)
我们先说用 DataAnnotations 创建用户对象:
namespace SqlEntityFramework
{
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.Spatial;
public enum UserType_e : int
{
Admin,
System,
User,
}
[Table("T_User")] //为User类指定对应的数据库中的表
public partial class User
{
[Key] //指定为主键
[Column(TypeName = "numeric", Order = 0)]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public decimal ID { get; set; }
[StringLength(128)]
[Column("UserName", Order = 1)] //为UserName属性指定表中的对应的名称
public string UserName { get; set; }
[StringLength(128)]
[Column(Order = 2)]
public string PassWord { get; set; }
[Column(Order = 3)]
public UserType_e UserType { get; set; }
[StringLength(512)]
[Column(Order = 4)]
public string Description { get; set; }
[Column(Order = 5)]
public DateTime Timestamp { get; set; }
}
}
常用的数据 特性有以下这些:参考MSDN
特性名称 | 描述 | 举例 |
Table | 指定类将映射到的数据库表(只能应用于类): 1.参数(Name),类映射到的表的名称; 2.Schema:类映射到表的架构; | [Table("Name")] |
Column | 表示属性将映射到的数据库列: 1.可以带一个字符串参数(Name),映射到的列的名称; 2.Order:属性映射到的列的从零开始的顺序; 3.TypeName:映射到的列的数据库提供程序特定数据类型; | [Column(TypeName = "numeric", Order = 0)] |
Key | 表示唯一标识实体的一个或多个属性: 1.指定属性为主键,可以给多个属性指定表示联合主键 | [Key] |
ForeignKey | 表示关系中用作外键的属性: 1.参数(Name),表示关联的导航属性或关联的外键属性的名称 | [ForeignKey("Name")] |
StringLength | 指定数据字段中允许的字符的最小长度和最大长度: 1.参数(MaximumLength)一个字符串,最大长度; 2.MinimumLength:一个字符串的最小长度; | [StringLength(128)] |
DatabaseGenerated | 指定数据库生成属性值的方式: 1.参数(DatabaseGeneratedOption)用于在数据库中生成的属性的值的模式 None:数据库不生成值 Identity:在插入行时,数据库将生成值 Computed:在插入或更新行时,数据库将生成值 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] |
Required | 指定数据字段值是必需的: 1.AllowEmptyStrings:是否允许为空字符串; 2.ErrorMessage:如果验证失败的错误消息; | [Required] |
MaxLength | 允许的数组或字符串数据的最大长度: 1.可以带一个字符串参数(Length),表示数组或字符串数据的最大允许长度; | [MaxLength] |
MinLength | 允许的数组或字符串数据的最小长度: 1.可以带一个字符串参数(Length),表示数组或字符串数据的最小允许长度; | [MinLength] |
NotMapped | 表示应从数据库映射中排除属性或类 | [NotMapped] |
ComplexType | 不用在一组类中描述域实体,然后将这些类分层以描述完整的实体(只能应用于类) | [ComplexType] |
注意:
- 在数据库中表名或者列名都不区分大小写的,而在C#代码中是区分大小写的;
- EF约定的主键是ID,所以可以不用再特殊指定Id为主键,若是其他的属性可以加上[Key];
- 每个对象模型都必须有一个主键;
- 一旦将模型更新到数据库后,已添加的特性就不要轻易更改(若有更改一定要进行迁移更新数据库),否则会出错;
- DatabaseGeneratedOption特性在创建表之后修改的话,并不能更新到数据库,不知道是不是EF版本的问题,若要修改需手动修改数据库(模型和数据库不同步会报错);
- Column.Order也只是在创建表时可以指定列的顺序,一旦创建好之后再改变顺序是不起作用的(只要不存在同样的序号改变值不会报错);
四、创建 DbContext
using System;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
namespace SqlEntityFramework
{
public partial class DBContext : DbContext
{
static string connectionString = @"data source=127.0.0.1;initial catalog=Bridge;persist security info=True;user id=sa;password=123;MultipleActiveResultSets=True;App=EntityFramework";
public DBContext()
: base(connectionString)
{
}
public virtual DbSet<User> T_User { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
}
}
}
这里可以自己定义连接字符串,也可以从App.config中获取,若从App.config中获取应该这么写:
using System;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
namespace SqlEntityFramework
{
public partial class DBContext : DbContext
{
public DBContext()
: base("name=DBContext")
{
}
public virtual DbSet<User> T_User { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
}
}
}
App.config中添加:
<connectionStrings>
<add name="DBContext" connectionString="data source=127.0.0.1;initial catalog=Bridge;persist security info=True;user id=sa;password=123;MultipleActiveResultSets=True;App=EntityFramework" providerName="System.Data.SqlClient" />
</connectionStrings>
五、 Fluent API
现在讲用Fluent API创建对象模型,在这里才说Fluent API,因为要在DBContext中定义接口。
1.Fluent API是重写 DbContext 的 OnModelCreating 方法:
using System;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
namespace SqlEntityFramework
{
public partial class DBContext : DbContext
{
public DBContext()
: base("name=DBContext")
{
}
public virtual DbSet<User> T_User { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().ToTable("T_User").HasKey(u => u.ID);
modelBuilder.Entity<User>().Property(u => u.ID).HasPrecision(10, 0).HasColumnType("numeric").HasColumnOrder(0).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<User>().Property(u => u.UserName).HasMaxLength(64).HasColumnName("UserName").HasColumnOrder(1);
modelBuilder.Entity<User>().Property(u => u.PassWord).HasMaxLength(64).HasColumnOrder(2);
modelBuilder.Entity<User>().Property(u => u.UserType).HasColumnOrder(3);
modelBuilder.Entity<User>().Property(u => u.Description).HasMaxLength(512).HasColumnOrder(4);
modelBuilder.Entity<User>().Property(u => u.Timestamp).HasColumnOrder(5);
}
}
}
2.也可以为每个数据模型编写映射对象:
namespace SqlEntityFramework
{
using System.Data.Entity.ModelConfiguration;
using System.ComponentModel.DataAnnotations.Schema;
public class UserMap : EntityTypeConfiguration<User>
{
public UserMap()
{
this.ToTable("T_User").HasKey(u => u.ID);
this.Property(u => u.ID).HasPrecision(10, 0).HasColumnType("numeric").HasColumnOrder(0).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.Property(u => u.UserName).HasMaxLength(64).HasColumnName("UserName").HasColumnOrder(1);
this.Property(u => u.PassWord).HasMaxLength(64).HasColumnOrder(2);
this.Property(u => u.UserType).HasColumnOrder(3);
this.Property(u => u.Description).HasMaxLength(512).HasColumnOrder(4);
this.Property(u => u.Timestamp).HasColumnOrder(5);
}
}
}
然后修改DBContext类:
using System;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration;
using System.Linq;
using System.Reflection;
namespace SqlEntityFramework
{
public partial class DBContext : DbContext
{
public DBContext()
: base("name=DBContext")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Assembly asm = Assembly.LoadFrom(Assembly.GetExecutingAssembly().CodeBase);
modelBuilder.Configurations.AddFromAssembly(asm);
base.OnModelCreating(modelBuilder);
}
}
}
六、数据迁移
https://docs.microsoft.com/zh-cn/ef/ef6/modeling/code-first/migrations/?redirectedfrom=MSDN
每当我们更改了代码中的数据库模型,例如修改了对象的数据类型,添加或者删除了某个列,都需要通过数据迁移更新到数据库,以此来改进应用程序的数据库架构。
1.启用上下文迁移
打开包管理器控制台并运行 Enable-Migrations 命令
运行成功会在项目文件下创建一个 Migrations文件夹,并有一个 Configuration.cs 文件
namespace SqlEntityFramework.Migrations
{
using System;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;
internal sealed class Configuration : DbMigrationsConfiguration<SqlEntityFramework.DBContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(SqlEntityFramework.DBContext 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.
}
}
}
2.添加迁移
控制台运行 Add-Migration InititalCreate 命令
运行成功后会在 Migrations文件夹自动创建了一个 带有InititalCreate名称的文件,文件名称一般都是 【时间_操作名】
namespace SqlEntityFramework.Migrations
{
using System;
using System.Data.Entity.Migrations;
public partial class InititalCreate : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.T_User",
c => new
{
ID = c.Decimal(nullable: false, precision: 18, scale: 0, identity: true, storeType: "numeric"),
UserName = c.String(maxLength: 64),
PassWord = c.String(maxLength: 128),
UserType = c.Int(nullable: false),
Description = c.String(maxLength: 512),
Timestamp = c.DateTime(nullable: false),
})
.PrimaryKey(t => t.ID);
}
public override void Down()
{
DropTable("dbo.T_User");
}
}
}
- 这里将ID的scale改为0,因为自增列不能是小数。
- Up方法就表示在你更新时要执行的方法,而Down表示你要退回时执行的方法,后续会说到。
- 你也可以在这两个方法里面自己编写需要执行的语句。
3.更新到数据库
没有执行这一部之前,所有的更改都没有提交到数据库,所以一旦提交到数据库后就不要轻易删除上一步生成的DbMigration文件
控制台运行 Update-Database -verbose 命令 (可以不加上 -verbose,加上这个可以在控制台打印出执行的sql语句)
- 通过执行的sql语句我们可以发现除了创建了我们自己的表以外,还创建了一个名为 __MigrationHistory 的表,该表就是存储我们每次迁移后更新到数据库的信息;
- __MigrationHistory 表中的数据是和代码中的迁移文件一一对应的,也不要轻易的删除;
- 每添加一次迁移就要提交到数据库后才能进行下一次的迁移;
4. 迁移文件
每个迁移文件除了基本的代码外,还包括一个designer文件和一个resx文件
// <auto-generated />
namespace SqlEntityFramework.Migrations
{
using System.CodeDom.Compiler;
using System.Data.Entity.Migrations;
using System.Data.Entity.Migrations.Infrastructure;
using System.Resources;
[GeneratedCode("EntityFramework.Migrations", "6.4.4")]
public sealed partial class InititalCreate : IMigrationMetadata
{
private readonly ResourceManager Resources = new ResourceManager(typeof(InititalCreate));
string IMigrationMetadata.Id
{
get { return "202101060641456_InititalCreate"; }
}
string IMigrationMetadata.Source
{
get { return null; }
}
string IMigrationMetadata.Target
{
get { return Resources.GetString("Target"); }
}
}
}
- IMigrationMetadata.Id 就对应了__MigrationHistory 表中的 MigrationId
- 每次修改数据模型,EF是怎么知道我们改了哪呢?它就是通过比对__MigrationHistory表中所有的数据进行校验是否发生了改变
5.迁移到特定版本(包括降级)
控制台运行 Update-Database –TargetMigration: InititalCreate 命令表示要迁移到 InititalCreate,就会执行 InititalCreate之后添加的迁移的Down方法。
如果想要一直回退到空数据库,可使用 Update-Database –TargetMigration: $InitialDatabase 命令 。