EF 学习系列二 数据库表的创建和表关系配置(Fluent API、Data Annotations、约定)
上一篇写了《Entity Farmework领域建模方式 3种编程方式》,现在就Code First 继续学习
1、数据库表的创建
新建一个MVC的项目,在引用右击管理NuGet程序包,点击浏览搜索EF安装,我这里主要是EF6.0 以上的学习 所以都安装6.0 以上的版本
接下来在Model文件夹下面创建一个Customer类
public class Customer { public int ID { get; set; } public string Name { get; set; } public int Age { get; set; } public string Email { get; set; } public DateTime AddTime { get; set; } }
在创建一个继承EF上下文的类XXDBContext,(个人习惯XX是我的名字拼音缩写)此上下文是数据库交互的一个中间桥梁,我们称之为会话,并且为为一个模型公开一个DbSet。默认情况下EF链接LocalDB本地数据库(需要安装LocalDB实例),我还是手动通过EF上下文派生类的构造函数来配置数据库链接。下面我注释的是数据库初始化策略。我这里就选择始终创建数据库,后面用到配置表关联与字段的配置。
public class WYDBContext:DbContext { public WYDBContext(string ConnectionName) : base(ConnectionName) { } public WYDBContext():base("SqlConn") { //默认的初始化器。这种初始化器在第一次运行程序时会创建数据库,再次运行不会再创建新的数据库。但是如果我们改变了领域类,运行程序时会抛出一个异常 //Database.SetInitializer(new CreateDatabaseIfNotExists<WYDBContext>()); //如果领域类发生了改变,删除以前的数据库,然后重建一个新的。采用这种初始化器不用再担心领域类改变影响数据库架构的问题。 //Database.SetInitializer(new DropCreateDatabaseIfModelChanges<WYDBContext.cs>()); //每次运行程序都会删除以前的数据库,重建新的数据库。如果在开发过程中每次都想使用最新的数据库,那么可以采用这种初始化器。 Database.SetInitializer(new DropCreateDatabaseAlways<WYDBContext>()); //禁用数据库初始化策略 //Database.SetInitializer<WYDBContext>(null); } public DbSet<Customer> Customer { get; set; } }
webconfig配置
<system.web> <compilation debug="true" targetFramework="4.6.1" /> <httpRuntime targetFramework="4.6.1" /> </system.web> <!--数据库连接--> <connectionStrings> <!--数据库连接ef字符串--> <add name="SqlConn" connectionString="Data Source=地址;Initial Catalog=数据库名;Persist Security Info=True;User ID=用户名;Password=密码;MultipleActiveResultSets=True" providerName="System.Data.SqlClient" /> </connectionStrings>
现在如果直接启动项目数据库是不会被创建的,只有调用到才会创建,在Home控制器的Index中调用,启动就生成了数据库
public ActionResult Index() { using (var db =new WYDBContext()) { db.Customer.ToList(); } return View(); }
2、 三者约定之 Code First约定(三者优先级 Fluent API > Data Annotations > 约定)
上面可已看出表Customer自己生成了主键ID。所谓约定,类似于C#中的接口,它是一个规范或者规则。使用Code First基于类定义通过约定来配置概念模型并以此为规则,约定就是基本规则。
Code First根据模型中定义的ID(不区分大小写),或者是以类名加ID的属性推断这样的属性为ID,如果为int或者guid类型,那么主键映射成标识列(自增长)。
Model下面在创建一个订单Order类一个客户有多个订单一个订单只能属于某一个客户这样客户与订单的关系就是一对多
public class Order { public int ID { get; set; } public string Name { get; set; } public decimal Price { get; set; } public string Remark { get; set; } public int CustomerID { get; set; } /// <summary> /// 订单对应的客户信息 /// </summary> public virtual Customer Customer { get; set; } } public class Customer { public int ID { get; set; } public string Name { get; set; } public int Age { get; set; } public string Email { get; set; } public DateTime AddTime { get; set; } /// <summary> /// 客户对应的订单信息 /// </summary> public virtual IList<Order> Order { get; set; } }
数据库上下文WYDBContext加上 public DbSet<Order> Order { get; set; } 刚刚加的订单类,运行起来 如果数据库删除不了的 自己闪一下 (在navicat 里面使用会这样,我就换在SSMS里面用)
它也生成了表与表的对应关系,然而string类型的你会发现字段都是max这肯定不行。接下来看Data Annatations 配置
3、三者约定之 Data Annotations
Data Annotations我的理解就是在字段类名上面加特性注解来控制字段属性的 栗子如下 还是Order与Customer两张表 记得添加命名空间using System.ComponentModel.DataAnnotations;跟using System.ComponentModel.DataAnnotations.Schema;
public class Customer { /// <summary> /// ID /// </summary> [Key]//标识次列为主键 [Column("Zj", Order = 0, TypeName = "int")]//列名Zj,数据库序号0,类型int [Required()]//不允许为空 [Display(Name = "Zj")]//显示名称,这里大多都是中文 后面视图@Html.DisplayNameFor(item=> model.Name)用到 显示的 public int Zj { get; set; } /// <summary> /// 姓名 /// </summary> [Column("NameWYY", TypeName = "nvarchar")]//我加了WYY看效果 [StringLength(50, ErrorMessage = "{0}长度不能超过50个字符")] [Display(Name = "姓名")] public string Name { get; set; } /// <summary> /// 年龄 /// </summary> [Column("Age", TypeName = "int")] [Display(Name = "年龄")] public int? Age { get; set; }//加了?允许为null /// <summary> /// 邮箱 /// </summary> [Column("Email", TypeName = "nvarchar")] [StringLength(50, ErrorMessage = "{0}长度不能超过50个字符")] [Display(Name = "电子邮箱")] public string Email { get; set; } /// <summary> /// 日期 /// </summary> [Column("AddTime", TypeName = "datetime2")]//如果不定义datetime2添加DateTime.Now就会报错哦 [Display(Name = "添加日期")] [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:yyyy-MM-dd}")]//日期格式化 public DateTime AddTime { get; set; } /// <summary> /// 客户对应的订单信息 /// </summary> public virtual IList<Order> Order { get; set; } } // [NotMapped]//表不映射到数据库 [Table("Tb_Order")]//表名 public class Order { /// <summary> /// ID /// </summary> [Key] [Column("ID", Order = 0, TypeName = "int")] [Required()] [Display(Name = "ID")] public int ID { get; set; } /// <summary> /// 名称 /// </summary> [Column("Name", TypeName = "nvarchar")] [StringLength(50, ErrorMessage = "{0}长度不能超过50个字符")] [Display(Name = "名称")] public string Name { get; set; } /// <summary> /// 价格 /// </summary> [Column("Price")] [Display(Name = "价格")] public decimal? Price { get; set; } /// <summary> /// 备注 /// </summary> [StringLength(3000)]//长度约束 [Column("Remark", TypeName = "nvarchar")]//我加了WYY看效果 [Display(Name = "备注")] public string Remark { get; set; } /// <summary> /// 客户ID /// </summary> [ForeignKey("Customer")]//外键 public int CustomerID { get; set; } /// <summary> /// 订单对应的客户信息 /// </summary> [ForeignKey("CustomerID")]//外键 public virtual Customer Customer { get; set; } /// <summary> /// 不映射字段 /// </summary> [NotMapped]//不映射到数据库 public string XXX { get; set; } }
4、三者约定之 Fluent API
这个就要在派生类重写OnModelCreating了 少一点 的表还可以在里面设置各个字段多了还是映射Map在模型表里面写,在OnModelCreatiing注册模型类就可以了;
public DbSet<Customer> Customer { get; set; } public DbSet<Order> Order { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { //TODO 配置映射 modelBuilder.Entity<Customer>().ToTable("CSTo");//数据库表名 modelBuilder.Entity<Customer>().HasKey(x => x.Zj);//主键 modelBuilder.Entity<Customer>().Property(x=>x.AddTime).HasColumnType("DATETIME2");//时间 modelBuilder.Entity<Customer>().Property(x=>x.Age).IsOptional();//为null //HasColumnType("DATETIME2(7)")这种写是错的 modelBuilder.Entity<Customer>().Property(x => x.Name).IsRequired().HasColumnType("varchar").HasMaxLength(66);//不为空,类型,长度 //默认情况下不会生成复数的表 如Orders modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); modelBuilder.Configurations.Add(new OrderMap());//注册 base.OnModelCreating(modelBuilder); } // [NotMapped]//表不映射到数据库 [Table("Tb_Order")]//表名 public class Order { ///字段 } public class OrderMap : EntityTypeConfiguration<Order> { public OrderMap() { //对应数据库表名 this.ToTable("ORd"); //一个订单必须对应有一个客户,客户一对多(订单) 用户表里面的 CustomerID this.HasRequired(p => p.Customer).WithMany(p => p.Order).HasForeignKey(p => p.CustomerID); this.HasKey(k => k.ID);//主键 this.Property(p => p.Name).HasColumnType("VARCHAR").HasMaxLength(50).IsRequired();//Name字段属性(varchar,长度50,不为null) this.Property(p => p.Remark).HasColumnType("VARCHAR").HasMaxLength(5000).IsOptional();//Remark(varchar,长度5000,null) this.Property(p => p.Price).HasColumnName("pp");//列名 } }
C#的数值类型对应数据库如下
●C#中的 int类型默认映射后对应数据库中的int类型。
● C#中的double类型默认映射后对应数据库中的float类型
●C#中的float类型默认映射后对应数据库中的real类型。
●C#中 的decimal类型默认映射后对应数据库中的decimal(18,2)类型
●C#中 的Int64类型默认映射后对应数据库中的bigint类型。
一般都是用Data Anntations 跟默认的约定好久没用记录一下 用到又来拿 Fluent API也是好久没复习了 哈哈 有时间在看看书