守则第一公约:
我们已经看到实体框架如何在上一节中创建来自域类的数据库表。在这里,我们将学习EF 6.x中默认的Code-First惯例。
什么是公约?
约定是默认规则集,当使用Code-First方法时,会自动配置基于域类的概念模型。这些EF 6.x Code-First约定在System.Data.Entity.ModelConfiguration.Conventions
命名空间中定义。
让我们来看看各种Code-First惯例的概述。
类型发现:
在前一节中,我们创建了一个上下文类,其中包含域类的DbSet <TEntity>类型属性。Code-First将在数据库中为上下文类中的所有DbSet属性创建表,如前所述。
EF还为上下文类中没有包含DbSet属性的域类创建数据库表,但它们作为其他域类中的引用属性包含在上下文类中的DbSet属性中。(即使引用的类型是在不同的程序集中定义的)
例如,以下学生类包括对教师类的引用。但是,上下文类不包括教师作为DbSet属性。
public class Student
{
public int StudentID { get; set; }
public string StudentName { get; set; }
public DateTime DateOfBirth { get; set; }
public byte[] Photo { get; set; }
public decimal Height { get; set; }
public float Weight { get; set; }
public Teacher Teacher { get; set; }
public Standard Standard { get; set; }
}
public class Teacher
{
public int TeacherId { get; set; }
public string TeacherName { get; set; }
}
以下是不包含DbSet类型属性的上下文类。
namespace EF_Code_First_Tutorials
{
public class SchoolContext: DbContext
{
public SchoolContext(): base()
{
}
public DbSet<Student> Students { get; set; }
public DbSet<Standard> Standards { get; set; }
}
}
因此,即使教师不是DbSet类型,EF也会将其包含在概念模型中,并为其创建数据库表,如下所示。
即使上下文类只包含作为DbSet属性的基类,Code-First也包括派生类。
类型发现的约定是:
- EF为上下文类中定义为DbSet属性的类型创建表。
- EF为包含在实体类型中的引用类型创建表,即使它们是在不同的程序集中定义的。
- 即使只将基类定义为DbSet属性,EF也会为派生类创建表。
主要关键公约:
在上一节中,我们已经看到EF自动在每个表中创建一个主键。默认情况下,如果属性名称是Id或<类名> Id(不区分大小写),则EF会为属性创建主键。主键属性的数据类型可以是任何东西,但是如果是数字或GUID,那么它将被配置为数据库中的标识列。
如果您已经定义了除Id或<ClassName> Id以外的关键属性,则将抛出ModelValidationException。例如,考虑以下标准类:
public class Student
{
public int StudID { get; set; }
public string StudentName { get; set; }
public DateTime DateOfBirth { get; set; }
public byte[] Photo { get; set; }
public decimal Height { get; set; }
public float Weight { get; set; }
}
在上面的例子中,学生类是包含StudId
属性而不是StudentId
或Id
。所以,EF会抛出以下异常,因为没有Key属性。
EntityFramework.dll中出现'System.Data.Entity.ModelConfiguration.ModelValidationException'EntityType'Student'
没有定义键。定义这个EntityType的关键。
使用DataAnnotations或Fluent API将StudId
属性配置为主键。
注意:每个实体类必须在实体框架中包含关键属性。
关系公约:
EF 6默认使用导航属性来推断一对多关系。访问“一对多关系章节”以获取更多信息。
注意: EF 6不包括一对一和多对多关系的默认约定。您需要使用Fluent API或DataAnnotation进行配置。
外键公约:
我们已经在上面看到Code First在遇到导航属性时会自动插入一个外键。建议在关系的依赖端包含一个外键属性。考虑下面的例子:
public class Student
{
public int StudentID { get; set; }
public string StudentName { get; set; }
public DateTime DateOfBirth { get; set; }
public byte[] Photo { get; set; }
public decimal Height { get; set; }
public float Weight { get; set; }
//Foreign key for Standard
public int StandardId { get; set; }
public Standard Standard { get; set; }
}
public class Standard
{
public int StandardId { get; set; }
public string StandardName { get; set; }
public IList<Student> Students { get; set; }
}
正如你在上面的代码中看到的,Student类包括外键StandardId,它是Standard类中的关键属性。
现在,Code First将在Students类中创建StandardId列,而不是Standard_StandardId列,如下所示。
请注意,上图中的StandardId外键不为空。这是因为int
数据类型不可为空。
代码首先根据外键的可空性推断关系的多重性。如果该属性可以为空,则该关系被注册为空。否则,关系被注册为NOT NULL。在上面的Student类中将StandardId属性的数据类型修改为int
to以Nullable<int>
在Students表中创建一个可为空的外键列。
复杂类型约定:
Code First为不包含key属性的类创建复杂类型,而且主键未使用DataAnnotation或Fluent API注册。
默认代码优先约定:
下表列出了默认代码的第一个约定:
默认约定为 | 描述 |
---|---|
表名 | <实体类名称> +' s'EF将创建数据库表,实体类名后缀为's' |
主键名称 | 1)Id 2)<Entity Class Name> +“Id”(不区分大小写) EF将为名为Id或<Entity Class Name> +“Id”的属性创建主键列(不区分大小写) |
外键属性名称 | 默认情况下,EF将查找与主体实体主键名称相同的外键属性。 如果外键属性不存在,则EF将使用<依赖的导航属性名称> +“_”+ <主体实体主键属性名称>在Db表中创建FK列, 例如,如果学生实体,EF将创建Standard_StandardId外键列在标准包含标准标识的情况下不包含外键属性 |
空列 | EF为所有引用类型属性和可空的原始属性创建空列。 |
不是空列 | EF为PrimaryKey属性和非空值类型属性创建NotNull列。 |
数据库列顺序 | EF将创建与实体类中的属性顺序相同的数据库列。但是,主键列会先移动。 |
映射到数据库的属性 | 默认情况下,所有属性将映射到数据库。使用[NotMapped]属性从数据库映射中排除属性或类。 |
级联删除 | 启用默认情况下,所有类型的关系。 |
下表列出了与SQL数据类型和主键列数据类型和长度映射的C#数据类型。
C#数据类型 | 相关的DB列数据类型 | PK列数据类型和长度 |
---|---|---|
INT | INT | int,标识列增加1 |
串 | 为nvarchar(最大) | 为nvarchar(128) |
十进制 | 十进制(18,2) | 十进制(18,2) |
浮动 | 真实 | 真实 |
字节[] | VARBINARY(最大) | VARBINARY(128) |
约会时间 | 约会时间 | 约会时间 |
布尔 | 位 | 位 |
字节 | TINYINT | TINYINT |
短 | SMALLINT | SMALLINT |
长 | BIGINT | BIGINT |
双 | 浮动 | 浮动 |
烧焦 | 没有映射 | 没有映射 |
为sbyte | 没有映射 (抛出异常) | 没有映射 |
目的 | 没有映射 | 没有映射 |
这是代码第一个约定的概述。这些约定可以使用DataAnnotation或Fluent API来重写。