上一篇直接使用Code-first建模方式,其实EF提供了三种数据建模方式
1 database-first 数据库优先,如果你有一个已经设计好的数据库,使用这种方式,然后你的数据模型需要配合数据库表来,我没弄过,不说太多
2 model-first 喜欢GUI可视化界面进行数据库建模的,使用这种方式,我没弄过
3 code -first 代码最大,一定要记住,使用这种方式,心里就不要想着数据库,一定要按照我model的设计来给我生成我需要的表结构,我是不会对数据库委屈求全的,EF一定能够满足我的要求,几乎不存在只有数据能做而EF不能做的情况。
大多数的开发人员肯定都会SQL,然后ORM框架又一出现,那我们对他的学习态度是什么样的呢?我经常问自己,ADO.NET能做的为什么要去弄EF,所以很有可能我用EF搞不定的情况就去依赖ADO。这样可能两种方式同时使用,就乱了。
现在我的学习目标就是,我什么都要用EF搞。
数据库三种建模方式,EF的架构也是三层,等会我来弄的数据库初始化策略也是三种方式,呵呵!
EF的架构随便说一下
Conceptual Schema Define Layer (CSDL) 概念架构定义层 对象的世界(开发人员面向这一层)
Mapping Schema Layer (MSL) 映射架构层 映射
Storage Schema Define Layer (SSDL) 存储架构定义层 数据的世界(这一层专门和数据库打交道)
这篇博客主要讲讲我用EF进行一些配置时遇到的问题,我的目的是能够设计一个差不多可以使用的数据模型出来,问题说完了,后面弄个简单的东西出来
数据库初始化策略
public class EFDbContext:DbContext { public EFDbContext() { // 如果数据库不存在就创建 //Database.SetInitializer(new CreateDatabaseIfNotExists<EFDbContext>()); // 总是创建数据库 //Database.SetInitializer(new DropCreateDatabaseAlways<EFDbContext>()); // 如果model变了就创建数据库 //Database.SetInitializer(new DropCreateDatabaseIfModelChanges<EFDbContext>()); //禁用数据库初始化策略 Database.SetInitializer<EFDbContext>(null); }
这个需要在FEDbContext构造函数中配置。那这个数据库初始化时做什么的呢?为什么突然这个一个东西让你去配置呢?其实我并不太明白,我试了一下,我使用总是创建数据库这个配置,当我每次运行程序,并在Student表中添加一条记录。然后我再看我的student表,之前的数据都没了。
所以我觉得这个东西必须要显示禁止掉。我觉得每次运行程序就初始化数据库这一功能比较适合我现在对EF的学习阶段,可能我的配置没弄好,model 设计有问题,那么初始化数据库可以。
其中也碰到一个问题,当我配置成总是创建新数据库时,我在运行的时候报错了
他说我的数据库正在使用,我就有点搞不懂了。我也不知道数据库在什么时候算是使用中的状态…… 难道是要把数据连接断开?不可能吧,可能是我的查询tab页没关闭?嗯,有可能,我看看这确实还查着数据呢
关了查询,就行了。这个get到了,新建查询就说明数据库在被使用
一些具体的配置
现在来看一下我弄的一些具体的配置
我新建一个sutdent类,里面有一个只读属性,然后更新到数据库中,并没有创建这个只读属性对应的字段。我觉得很满意,有些东西你不想让他映射到数据库中也可以配置
说一下配置的方式,Data Annotations、Fluent API 第一种就是我用特性的方式在属性上或者类型上加上描述,像这种[POST]、[Key]、[Required];Fluent API fluent流利的意思。就是通过写配置代码的方式来。定义配置都要重写DbContext的一个方法OnModelCreating中进行
一般的做法,如果说我要在model的那些属性上加上什么最大长度、范围、非空……的那些校验,用Data Annotations的方式显然要好一些
public class EFDbContext:DbContext { protected override void OnModelCreating(DbModelBuilder dbModelBuilder) { // 在这里加上你自己的配置,包括一些全局配置、自定义配置这些 // onModelCreating 在model创建的时候它配置告诉它,这没毛病 // 下面这个是个全局配置,所有model中,属性为“Id”的就设置为主键 //dbModelBuilder.Configurations. //dbModelBuilder.Properties() // .Where(x => x.Name == "Id") // .Configure(p => p.IsKey()); // 下面这个我为Book指定主键为“BooId” dbModelBuilder.Entity<Book>().HasKey(x => x.BooID); } }
我们还需要了解一下EF中的默认配置,来看主键的默认配置
EF中默认的主键为“ID” 或者 类名+“ID”,不区分大小写,比如我现在的这个Book类
namespace _20190106 { public class Book { public string BooID { get; set; } public string Name { get; set; } public virtual ICollection<Catalog> Catalogs { get; set; } } }
我的ID为BooID,没有满足他的默认规则,那么我在进行数据迁移的时候,就报错,他说我的Book没有定义主键
表名复数契约
如果你没有对表名复数契约做配置的话, 那么数据库中创建的表名都会是复数形式,看,我的表名全是复数形式的
比较令我想不到的是,person成了people,让我猛然一下子感受到英文的讲究……不过,这种东西还是按照我的习惯来吧,决定权给我吧,所以,加上一句,禁用掉
protected override void OnModelCreating(DbModelBuilder dbModelBuilder) { //删除复数表名契约 //dbModelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); }
EF修改表明生成的SQL语句是这样的,可以跟着他学习一下
EXECUTE sp_rename @objname = N'dbo.Catalog', @newname = N'Catalogs', @objtype = N'OBJECT' IF object_id('[PK_dbo.Catalog]') IS NOT NULL BEGIN EXECUTE sp_rename @objname = N'[PK_dbo.Catalog]', @newname = N'PK_dbo.Catalogs', @objtype = N'OBJECT' END EXECUTE sp_rename @objname = N'dbo.Library', @newname = N'Libraries', @objtype = N'OBJECT' IF object_id('[PK_dbo.Library]') IS NOT NULL BEGIN EXECUTE sp_rename @objname = N'[PK_dbo.Library]', @newname = N'PK_dbo.Libraries', @objtype = N'OBJECT' END
来看看复杂类型,我定义一个person类,一个VO对象(value object ) 里面有个属性是类类型的,我的意思就是觉得Adress这三个属性可以归为一组
public class Person { public string Id { get; set; } public string Name { get; set; } public Address Address { get; set; } } public class Address { public string Province { get; set; } public string City { get; set; } public string Area { get; set; } }
然后表是这样的,是我想要的,你也可以配置,映射成单独的一张表
然后我们看看一对多的关系,有包含关系的类,映射会成什么样子
图书馆类,里面有很多书,virtual声明的成为导航属性,具体我说不太清楚。其他的配置我都没有添加,默认生成的表就是这样的。
可能这样还是不行如果我又一个订单类Order,里面有产品的集合List<Product> 那么他默认给我创建的Product中会有一个order的外键
这样应该是不行的,产品表应该是独立的,那退货单呢,不是又要加一个外键?所以这样设计不太好。
这是涉及到一对多的关系了,这里我还没有弄到,一对一,一对多,多对多的结构是重点,我后面去弄。
继承关系
类里面肯定少不了继承关系啊,所以这里稍微说一下,因为这个我还没弄到这里。
每个表都有ID,有的还有会AddTime 、IsDelete,那么可以抽出一个基类,让其他的类去继承
public class BaseEntity { public string ID { get { return Guid.NewGuid().ToString(); } set { } } public DateTime AddTime { get { return DateTime.Now; } set { } } public bool IsDelete { get { return false; } set { } } }
然后我写一个Teacher类,继承BaseEntity
public class Teacher : BaseEntity { public string Name { get; set; } public string Tel { get; set; } public string Subject { get; set; } }
我什么都不配置,然后我执行迁移,将数据模型映射到数据库,接着运行程序,Main方法中添加一个Teacher实例,并查询打印出来。
报错
未经处理的异常: System.InvalidOperationException: The value of a property that is part of an object's key does not match the corresponding property value stored in the ObjectContext. This can occur if properties that are part of the key return inconsistent or incorrect values or if DetectChanges is not called after changes are made to a property that is part of the key.
然后看看数据库的表结构
他没有将BaseEntity创建为一张表,从基类继承过来的属性全部在这个Teacher里面,再看看表里面有没有数据
表里面是有数据的。为什么我查询打印却报错呢?
我想是这样的,查询出来的数据字段包括基类的属性+子类的属性,然后它在映射的时候发现我是用子类来接收的,而子类又没有显示定义基类的那些属性,导致条目不一致,就报错了。
2019年1月9日16:44:15 改正 :No,我这里错了,如果基类需要生成默认值,应该这样写。
原因应该是这样的,类里面映映射到数据库中的属性,不能够自定义,只能是干净的get;set;
上面报错,EF调用基类属性中的get方法时,就会报错,所以,把这个生成默认值放到构造函数里面
public abstract class BaseEntity { //public string Id { get { return Guid.NewGuid().ToString(); } set { } } public BaseEntity() { this.Id = Guid.NewGuid().ToString(); this.IsDelete = false; this.AddTime = DateTime.Now; this.LastUpdateTime = DateTime.Now; } [Required] [StringLength(36)] public string Id { get; set; } [Required] public bool IsDelete { get; set; } [Required] public DateTime AddTime { get; set; } [Required] public DateTime LastUpdateTime { get; set; } }
关于继承也是重中之重,继承也有三种方式,后面学了再说。
我现在我做的就是设计两个类,一个Bug类,一个Developer开发者类,我的这个设计肯定不合理
Bug类
public class Bug { public string ID { get; set; } public string Name { get; set; } public string Description { get; set; } // 提交时间 public DateTime SubmitDate { get; set; } // 完成时间,修复时间 public DateTime? CompleteDate { get; set; } // bug状态 public BugStatusEnum BugStatusEnum { get; set; } // bug产生时的系统环境 public SystemEnvironment VO_SystemEnvironment { get; set; } // 只读字段 public string BugStatusEnumText { get {return Enum.GetName(typeof(BugStatusEnum), BugStatusEnum); } } // 系统级别字段,非业务领域字段 public DateTime AddTime { get; set; } public bool IsDelete { get; set; } } // 系统环境 public class SystemEnvironment { // 操作系统 public string OS { get; set; } // 可用内存 public double AvailableMemory { get; set; } // 可用磁盘 public double AvailableDisk { get; set; } }
枚举
public enum BugStatusEnum { 已认领 = 10, 未认领 = 20, 已修改 = 30 }
开发者类
// 开发人员 public class Developer { public string ID { get; set; } public string Name { get; set; } public virtual ICollection<Bug> Bugs { get; set; } }
配置代码
public class EFDbContext : DbContext { public EFDbContext() { // 数据库初始化 // Database.SetInitializer(new DropCreateDatabaseAlways<EFDbContext>()); } public DbSet<Bug> Bugs { get; set; } public DbSet<Developer> Developers { get; set; } protected override void OnModelCreating(DbModelBuilder dbModelBuilder) { dbModelBuilder.Entity<Bug>().ToTable("tb_Bugs"); // 指定表名 dbModelBuilder.Entity<Bug>().HasKey(x => x.ID); // 显示指定ID为主键 dbModelBuilder.Entity<Bug>().Property(p => p.Name).HasMaxLength(100).IsRequired(); // 设置Name属性最长为50,非空 dbModelBuilder.Entity<Bug>().Property(p => p.ID).HasMaxLength(36).IsRequired(); dbModelBuilder.Entity<Bug>().Property(p => p.Description).HasMaxLength(500).IsRequired(); // 这里的链式调用是不是感觉到Fluent了? // 移除表明复数契约 dbModelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); } }