- 实体框架教程
- 实体框架 - 首页
- 实体框架 - 概述
- 实体框架 - 架构
- 实体框架 - 环境设置
- 实体框架 - 数据库设置
- 实体框架 - 数据模型
- 实体框架 - DbContext
- 实体框架 - 类型
- 实体框架 - 关系
- 实体框架 - 生命周期
- 实体框架 - 代码优先方法
- 实体框架 - 模型优先方法
- 实体框架 - 数据库优先方法
- 实体框架 - 开发方法
- 实体框架 - 数据库操作
- 实体框架 - 并发
- 实体框架 - 事务
- 实体框架 - 视图
- 实体框架 - 索引
- 实体框架 - 存储过程
- 实体框架 - 脱机实体
- 实体框架 - 表值函数
- 实体框架 - 原生SQL
- 实体框架 - 枚举支持
- 实体框架 - 异步查询
- 实体框架 - 持久化
- 实体框架 - 投影查询
- 实体框架 - 命令日志
- 实体框架 - 命令拦截
- 实体框架 - 空间数据类型
- 实体框架 - 继承
- 实体框架 - 迁移
- 实体框架 - 渴望加载
- 实体框架 - 延迟加载
- 实体框架 - 显式加载
- 实体框架 - 验证
- 实体框架 - 跟踪更改
- 实体框架 - 彩色实体
- 实体框架 - 代码优先方法
- 实体框架 - 第一个示例
- 实体框架 - 数据注解
- 实体框架 - Fluent API
- 实体框架 - 种子数据库
- 实体框架 - 代码优先迁移
- 实体框架 - 多个DbContext
- 实体框架 - 嵌套实体类型
- 实体框架资源
- 实体框架 - 快速指南
- 实体框架 - 有用资源
- 实体框架 - 讨论
实体框架 - 数据注解
DataAnnotations 用于配置类,这些类将突出显示最常用的配置。DataAnnotations 也被许多 .NET 应用程序(如 ASP.NET MVC)理解,这允许这些应用程序利用相同的注解进行客户端验证。DataAnnotation 属性会覆盖默认的 CodeFirst 约定。
System.ComponentModel.DataAnnotations 包括以下影响列的可空性或大小的属性。
- Key
- Timestamp
- ConcurrencyCheck
- Required
- MinLength
- MaxLength
- StringLength
System.ComponentModel.DataAnnotations.Schema 命名空间包含以下影响数据库模式的属性。
- Table
- Column
- Index
- ForeignKey
- NotMapped
- InverseProperty
Key
实体框架依赖于每个实体都具有一个键值,它用于跟踪实体。Code First 依赖的约定之一是如何暗示每个 Code First 类中的哪个属性是键。
约定是查找名为“Id”的属性或将类名和“Id”组合在一起的属性,例如“StudentId”。
该属性将映射到数据库中的主键列。
Student、Course 和 Enrollment 类遵循此约定。
现在假设 Student 类使用 StdntID 而不是 ID。当 Code First 找不到与该约定匹配的属性时,由于实体框架要求您必须具有键属性,因此它将抛出异常。您可以使用 key 注解来指定哪个属性用作 EntityKey。
让我们看一下包含 StdntID 的 Student 类的以下代码,但它不遵循默认的 Code First 约定。因此,为了处理这个问题,添加了一个 Key 属性,这将使其成为主键。
public class Student {
[Key]
public int StdntID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
当您运行应用程序并在 SQL Server 资源管理器中查看数据库时,您将看到主键现在是 Students 表中的 StdntID。
实体框架也支持复合键。复合键也是主键,由多个属性组成。例如,您有一个 DrivingLicense 类,其主键是 LicenseNumber 和 IssuingCountry 的组合。
public class DrivingLicense {
[Key, Column(Order = 1)]
public int LicenseNumber { get; set; }
[Key, Column(Order = 2)]
public string IssuingCountry { get; set; }
public DateTime Issued { get; set; }
public DateTime Expires { get; set; }
}
当您有复合键时,实体框架要求您定义键属性的顺序。您可以使用 Column 注解来指定顺序。
Timestamp
Code First 将以与 ConcurrencyCheck 属性相同的方式处理 Timestamp 属性,但它还将确保 Code First 生成的数据库字段不可为空。
更常见的是使用 rowversion 或 timestamp 字段进行并发检查。
您可以使用更具体的 TimeStamp 注解而不是使用 ConcurrencyCheck 注解,只要属性的类型是字节数组即可。
您在一个给定类中只能有一个 timestamp 属性。
让我们看一个简单的例子,将 TimeStamp 属性添加到 Course 类中 -
public class Course {
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
[Timestamp]
public byte[] TStamp { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
如上例所示,Timestamp 属性应用于 Course 类的 Byte[] 属性。因此,Code First 将在 Courses 表中创建一个名为 TStamp 的 timestamp 列。
ConcurrencyCheck
ConcurrencyCheck 注解允许您标记一个或多个属性,以便在用户编辑或删除实体时在数据库中用于并发检查。如果您一直在使用 EF 设计器,这与将属性的 ConcurrencyMode 设置为 Fixed 相一致。
让我们看一个 ConcurrencyCheck 如何工作的简单示例,将其添加到 Course 类中的 Title 属性中。
public class Course {
public int CourseID { get; set; }
[ConcurrencyCheck]
public string Title { get; set; }
public int Credits { get; set; }
[Timestamp, DataType("timestamp")]
public byte[] TimeStamp { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
在上面的 Course 类中,ConcurrencyCheck 属性应用于现有的 Title 属性。现在,Code First 将在更新命令中包含 Title 列以检查乐观并发,如下面的代码所示。
exec sp_executesql N'UPDATE [dbo].[Courses] SET [Title] = @0 WHERE (([CourseID] = @1) AND ([Title] = @2)) ',N'@0 nvarchar(max) ,@1 int,@2 nvarchar(max) ',@0=N'Maths',@1=1,@2=N'Calculus' go
Required 注解
Required 注解告诉 EF 特定属性是必需的。让我们看一下以下 Student 类,其中 Required id 添加到 FirstMidName 属性中。Required 属性将强制 EF 确保该属性包含数据。
public class Student {
[Key]
public int StdntID { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
如上例所示,Required 属性应用于 FirstMidName 和 LastName。因此,Code First 将在 Students 表中创建 NOT NULL FirstMidName 和 LastName 列,如下面的图像所示。
MaxLength
MaxLength 属性允许您指定其他属性验证。它可以应用于域类的字符串或数组类型属性。EF Code First 将根据 MaxLength 属性中指定的大小设置列的大小。
让我们看一下以下 Course 类,其中 MaxLength(24) 属性应用于 Title 属性。
public class Course {
public int CourseID { get; set; }
[ConcurrencyCheck]
[MaxLength(24)]
public string Title { get; set; }
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
当您运行上述应用程序时,Code First 将在 CourseId 表中创建一个名为 Title 的 nvarchar(24) 列,如下面的图像所示。
当用户设置包含超过 24 个字符的 Title 时,EF 将抛出 EntityValidationError。
MinLength
MinLength 属性还允许您指定其他属性验证,就像您使用 MaxLength 一样。MinLength 属性也可以与 MaxLength 属性一起使用,如下面的代码所示。
public class Course {
public int CourseID { get; set; }
[ConcurrencyCheck]
[MaxLength(24) , MinLength(5)]
public string Title { get; set; }
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
如果将 Title 属性的值设置为小于 MinLength 属性中指定长度或大于 MaxLength 属性中指定长度的值,则 EF 将抛出 EntityValidationError。
StringLength
StringLength 也允许您指定其他属性验证,如 MaxLength。唯一的区别是 StringLength 属性只能应用于域类的字符串类型属性。
public class Course {
public int CourseID { get; set; }
[StringLength (24)]
public string Title { get; set; }
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
实体框架还会验证 StringLength 属性的属性值。如果用户设置包含超过 24 个字符的 Title,则 EF 将抛出 EntityValidationError。
Table
默认的 Code First 约定创建与类名类似的表名。如果您让 Code First 创建数据库,并且还希望更改它正在创建的表名。然后 -
您可以将 Code First 与现有数据库一起使用。但并非总是类名与数据库中表名匹配的情况。
Table 属性覆盖此默认约定。
EF Code First 将为给定的域类创建一个具有 Table 属性中指定名称的表。
让我们看一个以下示例,其中类名为 Student,并且根据约定,Code First 假设这将映射到名为 Students 的表。如果不是这种情况,您可以使用 Table 属性指定表名,如下面的代码所示。
[Table("StudentsInfo")]
public class Student {
[Key]
public int StdntID { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
现在您可以看到 Table 属性将表指定为 StudentsInfo。生成表时,您将看到表名 StudentsInfo,如下面的图像所示。
您不仅可以指定表名,还可以使用 Table 属性指定表的架构,如下面的代码所示。
[Table("StudentsInfo", Schema = "Admin")]
public class Student {
[Key]
public int StdntID { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
您可以在上面的示例中看到,表使用 admin 架构指定。现在 Code First 将在 Admin 架构中创建 StudentsInfo 表,如下面的图像所示。
Column
它也与 Table 属性相同,但 Table 属性覆盖表行为,而 Column 属性覆盖列行为。默认的 Code First 约定创建与属性名类似的列名。如果您让 Code First 创建数据库,并且还希望更改表中的列名。然后 -
Column 属性覆盖默认约定。
EF Code First 将为给定的属性创建一个具有 Column 属性中指定名称的列。
让我们看一个以下示例,其中属性名为 FirstMidName,并且根据约定,Code First 假设这将映射到名为 FirstMidName 的列。
如果不是这种情况,您可以使用 Column 属性指定列名,如下面的代码所示。
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
[Column("FirstName")]
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
您可以看到 Column 属性将列指定为 FirstName。生成表时,您将看到列名 FirstName,如下面的图像所示。
Index
Index 属性是在 Entity Framework 6.1 中引入的。如果您使用的是早期版本,则本节中的信息不适用。
您可以使用 IndexAttribute 在一个或多个列上创建索引。
将属性添加到一个或多个属性将导致 EF 在创建数据库时在数据库中创建相应的索引。
在大多数情况下,索引可以使数据检索更快更高效。但是,在表或视图上使用过多的索引可能会对其他操作(如插入或更新)的性能产生负面影响。
索引是 Entity Framework 中的新功能,您可以通过减少从数据库查询数据所需的时间来提高 Code First 应用程序的性能。
您可以使用 Index 属性向数据库添加索引,并覆盖默认的 Unique 和 Clustered 设置,以获得最适合您方案的索引。
默认情况下,索引将命名为 IX_<属性名称>
让我们看一下下面的代码,其中在 Course 类中为 Credits 添加了 Index 属性。
public class Course {
public int CourseID { get; set; }
public string Title { get; set; }
[Index]
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
您可以看到 Index 属性已应用于 Credits 属性。生成表时,您将在索引中看到 IX_Credits。
默认情况下,索引是非唯一的,但您可以使用IsUnique命名参数指定索引应该是唯一的。以下示例介绍了一个唯一的索引,如下面的代码所示。
public class Course {
public int CourseID { get; set; }
[Index(IsUnique = true)]
public string Title { get; set; }
[Index]
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
ForeignKey
Code First 约定将处理模型中最常见的关联关系,但某些情况下需要帮助。例如,更改 Student 类中键属性的名称会导致与其关联的 Enrollment 类出现问题。
public class Enrollment {
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
public class Student {
[Key]
public int StdntID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
在生成数据库时,Code First 会看到 Enrollment 类中的 StudentID 属性,并根据约定(它与类名加“ID”匹配)将其识别为对 Student 类的外键。但是,Student 类中没有 StudentID 属性,而是 StdntID 属性在 Student 类中。
解决此问题的办法是在 Enrollment 中创建一个导航属性,并使用 ForeignKey 数据注释来帮助 Code First 理解如何在两个类之间构建关系,如下面的代码所示。
public class Enrollment {
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
public virtual Course Course { get; set; }
[ForeignKey("StudentID")]
public virtual Student Student { get; set; }
}
您现在可以看到 ForeignKey 属性已应用于导航属性。
NotMapped
根据 Code First 的默认约定,每个支持的数据类型的属性,并且包含 getter 和 setter 的属性都将在数据库中表示。但这并非始终适用于您的应用程序。NotMapped 属性会覆盖此默认约定。例如,您可能在 Student 类中有一个名为 FatherName 的属性,但不需要存储它。您可以将 NotMapped 属性应用于 FatherName 属性,您不希望在数据库中创建该属性的列,如下面的代码所示。
public class Student {
[Key]
public int StdntID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
[NotMapped]
public int FatherName { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
您可以看到 NotMapped 属性已应用于 FatherName 属性。生成表时,您将看到数据库中不会创建 FatherName 列,但它存在于 Student 类中。
对于没有 getter 或 setter 的属性,Code First 不会创建列,如下面的 Student 类 Address 和 Age 属性示例所示。
InverseProperty
当您在类之间有多个关系时,使用 InverseProperty。在 Enrollment 类中,您可能希望跟踪谁注册了 Current Course 和 Previous Course。让我们为 Enrollment 类添加两个导航属性。
public class Enrollment {
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
public virtual Course CurrCourse { get; set; }
public virtual Course PrevCourse { get; set; }
public virtual Student Student { get; set; }
}
同样,您还需要在这些属性引用的 Course 类中添加。Course 类具有返回到 Enrollment 类的导航属性,其中包含所有当前和以前的注册信息。
public class Course {
public int CourseID { get; set; }
public string Title { get; set; }
[Index]
public int Credits { get; set; }
public virtual ICollection<Enrollment> CurrEnrollments { get; set; }
public virtual ICollection<Enrollment> PrevEnrollments { get; set; }
}
如果外键属性未包含在特定类中,则 Code First 会创建 {类名} _{主键} 外键列,如上述类所示。生成数据库时,您将看到以下外键。
如您所见,Code First 无法自行匹配两个类中的属性。Enrollments 的数据库表应该有一个用于 CurrCourse 的外键和一个用于 PrevCourse 的外键,但 Code First 将创建四个外键属性,即
- CurrCourse _CourseID
- PrevCourse _CourseID
- Course_CourseID 和
- Course_CourseID1
要解决这些问题,您可以使用 InverseProperty 注释来指定属性的对齐方式。
public class Course {
public int CourseID { get; set; }
public string Title { get; set; }
[Index]
public int Credits { get; set; }
[InverseProperty("CurrCourse")]
public virtual ICollection<Enrollment> CurrEnrollments { get; set; }
[InverseProperty("PrevCourse")]
public virtual ICollection<Enrollment> PrevEnrollments { get; set; }
}
如您所见,通过指定它属于 Enrollment 类的哪个引用属性,在上面的 Course 类中应用了 InverseProperty 属性。现在,Code First 将生成一个数据库并在 Enrollments 表中仅创建两个外键列,如下面的图像所示。
我们建议您逐步执行以上示例,以便更好地理解。