-
Code First 约定
在前一篇中,我们已经知道了EF Code-First怎样从模型类(domain classes)中创建数据库表,下面,我们开始学习默认的Code-First约定。
什么是约定?
约定就是在Code-First模式中自动配置模型类的默认规则,Code-First约定定义在System.Data.Entity.ModelConfiguration.Conventions
命名空间
让我们来看看各种约定的概述
类型发现(Type Discovery):
在前一篇中,我们创建了一个context类并在其里面添加DbSet<T>属性,T为我们想要操作的模型类。Code-First会包括任何在这个类中的引用类型,就算这个引用类型的定义在其他不同集合中也是如此。
举个例子,下面的Student实体类有引用了Teacher类的属性,然而context并没有包含Teacher的DbSet属性。
public class Student { public 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 Teacher() { } public int TeacherId { get; set; } public string TeacherName { get; set; } }
context并没有包含Teacher的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; } } }
所以,即使Teacher类没有被包含在context的一个DbSet中,Code-First依然会创建一个Teachers表,如下图所示
即使context仅包含基类(base class)作为DbSet属性,Code-First也会包含它的派生类(derived class)
总结,类型发现(type discovery)约定是:
- Code-First 包含的类型作为一个DbSet属性被定义在context类中(Code-First includes types defined as a DbSet property in context class.)
- Code-First 包含的引用类型被包含在实体类型中,即使它被定义在不同的集合里(Code-First includes reference types included in entity types even if they are defined in different assembly.)
- Code-First 包含了派生类,即使只有它的基类作为DbSet属性被定义(Code-First includes derived classes even if only the base class is defined as DbSet property.)
-
主键(Primary Key) 约定
在上一篇中,我们看见Code-First自动在每张表里创建主键。这里的主键约定是:Code-First会自动把属性名称为Id或者<class name>Id(不区分大小写)的属性创建为主键,主键属性的数据类型可以是任何类型,但是如果主键属性的类型是数字或者GUID,则会将其定义成一个标识列(identity column)。
如果你已经定义了键属性是除了Id或者<ClassName>Id的其他名称,则会抛出一个ModelValidationException异常,考虑如下代码
public class Standard { public Standard() { } public int StdId { get; set; } public string StandardName { get; set; } public IList<Student> Students { get; set; } } }
如上所示,Standard类定义了StdId作为键属性,Entity Framework将抛出如下异常:
‘System.Data.Entity.ModelConfiguration.ModelValidationException‘ occurred in EntityFramework.dll
EntityType ‘Standard‘ has no key defined. Define the key for this EntityType.
如果你非要定义StdId作为主键,那你必须使用DataAnnotations或者Fluent API去配置它成为主键,我们将在后面的章节学习到。
-
关系(Relationship) 约定
Code First使用导航属性(navigation property)在两个实体之间推断关系,导航属性是一种简单的引用类型或者集合类型。举个例子,我们在Student类中定义Standard导航属性,在Stardard类中定义ICollention<Student>导航属性,所以Code First可以自动在数据库中的Standards表和Students表之间创建一对多的关系并在Students表中插入Standard_StandardId外键列。
public class Student { public 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; } //Navigation property public Standard Standard { get; set; } } public class Standard { public Standard() { } public int StandardId { get; set; } public string StandardName { get; set; } //Collection navigation property public IList<Student> Students { get; set; } }
上面的实体使用Standard_StandardId作为外键创建如下关系
因此,默认的code first关系约定会自动插入外键,用<navigation property Name>_<primary key property name of navigation property type>这种格式,比如
Standard_StandardId
-
外键(Foreign key) 约定
我们在上面看到了通过导航属性,Code Frist可以自动插入外键。但这里建议在关系末尾包含一个独立的外键属性。考虑如下代码:
public class Student { public 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 Standard() { } public int StandardId { get; set; } public string StandardName { get; set; } public IList<Student> Students { get; set; } }
正如我们看到的,Student类包含了外键StandardId,而StandardId是Standard类的主键,现在,Code First将会在Students表中创建一个StandardId列来代替Standard_StandardId列,如下所示
注意:StandardId外键不能为空,因为int数据类型不可为空
Code First 根据不可为空的外键推断多重关系,除非外键属性为空然后关系被注册为空,否则,外键属性不为空(NOT NULL)。也可以把Student类StandardId属性的数据类型从int修改成Nullable<int>来创建一个外键可为空的Students表。
-
复杂类型(Complex type) 约定
Code First 给类创建不包含键属性,而且主键没有被注册的复杂类型,这个时候使用DataAnnotation或者Fluent API。
-
默认Code-First 约定表
Default Convention For | Description |
---|---|
Table Name | <Entity Class Name> + ‘s‘ EF will create DB table with entity class name suffixed by ‘s‘ |
Primary key Name | 1) Id 2) <Entity Class Name> + "Id" (case insensitive) EF will create primary key column for the property named Id or <Entity Class Name> + "Id" (case insensitive) |
Foreign key property Name | By default EF will look for foreign key property with the same name as principal entity primary key name. If foreign key property does not exists then EF will create FK column in Db table with <Dependent Navigation Property Name> + "_" + <Principal Entity Primary Key Property Name> e.g. EF will create Standard_StandardId foreign key column into Students table if Student entity does not contain foreignkey property for Standard where Standard contains StandardId |
Null column | EF creates null column for all reference type properties and nullable primitive properties. |
Not Null Column | EF creates NotNull columns for PrimaryKey properties and non-nullable value type properties. |
DB Columns order | EF will create DB columns same as order of properties in an entity class. However, primary key columns would be moved first. |
Properties mapping to DB | By default all properties will map to database. Use [NotMapped] attribute to exclude property or class from DB mapping. |
Cascade delete | Enabled By default for all types of relationships. |
下面的表列出了C#数据类型映射到SQL的数据类型,和主键列的数据类型以及长度
C# DataType | Related DB Column DataType | PK Column DataType & Length |
---|---|---|
int | int | int, Identity column increment by 1 |
string | nvarchar(Max) | nvarchar(128) |
decimal | decimal(18,2) | decimal(18,2) |
float | real | real |
byte[] | varbinary(Max) | varbinary(128) |
datetime | datetime | datetime |
bool | bit | bit |
byte | tinyint | tinyint |
short | smallint | smallint |
long | bigint | bigint |
double | float | float |
char | No mapping | No mapping |
sbyte | No mapping (throws exception) |
No mapping |
object | No mapping | No mapping |
这是篇对code first约定的概述,这里的约定也能用DataAnnotation和Fluent API重写。在EF6.0里,你也可以使用自定义约定。
学完这篇,相信大家也对“约定大于配置”这种设计理念也有感知了吧^_^
下一篇我们将学习怎样初始化数据库,嗯,先睡觉了