实体框架 - 第一个例子
让我们使用类定义一个非常简单的模型。我们只是在 Program.cs 文件中定义它们,但在实际应用程序中,您将把类拆分成单独的文件,甚至可能是单独的项目。以下是我们将使用 Code First 方法创建的数据模型。
创建模型
使用以下代码在 Program.cs 文件中为 Student 类添加以下三个类。
public class Student { public int ID { get; set; } public string LastName { get; set; } public string FirstMidName { get; set; } public DateTime EnrollmentDate { get; set; } public virtual ICollection<Enrollment> Enrollments { get; set; } }
ID 属性将成为与此类对应的数据库表的主键列。
Enrollments 属性是导航属性。导航属性包含与此实体相关的其他实体。
在本例中,Student 实体的 Enrollments 属性将包含与该 Student 实体相关的所有 Enrollment 实体。
导航属性通常定义为虚拟的,以便它们可以利用某些实体框架功能,例如延迟加载。
如果导航属性可以包含多个实体(如多对多或一对多关系),则其类型必须是可以添加、删除和更新条目的列表,例如 ICollection。
以下是 Course 类的实现。
public class Course { public int CourseID { get; set; } public string Title { get; set; } public int Credits { get; set; } public virtual ICollection<Enrollment> Enrollments { get; set; } }
Enrollments 属性是一个导航属性。一个 Course 实体可以与任意数量的 Enrollment 实体相关。
以下是 Enrollment 类和枚举的实现。
public enum Grade { A, B, C, D, F } 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; } }
EnrollmentID 属性将作为主键。
Grade 属性是一个枚举。Grade 类型声明后的问号表示 Grade 属性可空。
为空的成绩不同于零分。Null 表示成绩未知或尚未分配。
StudentID 和 CourseID 属性是外键,相应的导航属性是 Student 和 Course。
一个 Enrollment 实体与一个 Student 和一个 Course 实体相关联,因此该属性只能容纳一个 Student 和 Course 实体。
创建数据库上下文
协调给定数据模型的实体框架功能的主要类是数据库上下文类,它允许查询和保存数据。您可以通过从 DbContext 类派生并为我们模型中的每个类公开类型化的 DbSet
public class MyContext : DbContext { public virtual DbSet<Course> Courses { get; set; } public virtual DbSet<Enrollment> Enrollments { get; set; } public virtual DbSet<Student> Students { get; set; } }
以下是 Program.cs 文件中的完整代码。
using System.ComponentModel.DataAnnotations.Schema; using System.Data.Entity; using System.Linq; using System.Text; using System.Threading.Tasks; namespace EFCodeFirstDemo { class Program { static void Main(string[] args) {} } public enum Grade { A, B, C, D, F } 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 { public int ID { get; set; } public string LastName { get; set; } public string FirstMidName { get; set; } public DateTime EnrollmentDate { get; set; } public virtual ICollection<Enrollment> Enrollments { get; set; } } public class Course { public int CourseID { get; set; } public string Title { get; set; } public int Credits { get; set; } public virtual ICollection<Enrollment> Enrollments { get; set; } } public class MyContext : DbContext { public virtual DbSet<Course> Courses { get; set; } public virtual DbSet<Enrollment> Enrollments { get; set; } public virtual DbSet<Student> Students { get; set; } } }
上面的代码就是我们开始存储和检索数据所需的全部代码。让我们添加一些数据,然后检索它。以下是 main 方法中的代码。
static void Main(string[] args) { using (var context = new MyContext()) { // Create and save a new Students Console.WriteLine("Adding new students"); var student = new Student { FirstMidName = "Alain", LastName = "Bomer", EnrollmentDate = DateTime.Parse(DateTime.Today.ToString()) }; context.Students.Add(student); var student1 = new Student { FirstMidName = "Mark", LastName = "Upston", EnrollmentDate = DateTime.Parse(DateTime.Today.ToString()) }; context.Students.Add(student1); context.SaveChanges(); // Display all Students from the database var students = (from s in context.Students orderby s.FirstMidName select s).ToList<Student>(); Console.WriteLine("Retrieve all Students from the database:"); foreach (var stdnt in students) { string name = stdnt.FirstMidName + " " + stdnt.LastName; Console.WriteLine("ID: {0}, Name: {1}", stdnt.ID, name); } Console.WriteLine("Press any key to exit..."); Console.ReadKey(); } }
执行上述代码时,您将收到以下输出。
Adding new students Retrieve all Students from the database: ID: 1, Name: Alain Bomer ID: 2, Name: Mark Upston Press any key to exit...
现在想到的问题是,数据和数据库在哪里,我们在其中添加了一些数据然后从数据库中检索它。按照惯例,DbContext 已为您创建了一个数据库。
如果本地 SQL Express 实例可用,则 Code First 已在该实例上创建了数据库。
如果 SQL Express 不可用,则 Code First 将尝试使用 LocalDb。
数据库以派生上下文的完全限定名称命名。
在我们的例子中,SQL Express 实例可用,数据库名称为 EFCodeFirstDemo.MyContext,如下图所示。
这些只是默认约定,有多种方法可以更改 Code First 使用的数据库。
如上图所示,它已创建学生、课程和 Enrollments 表,每个表包含具有适当数据类型和长度的列。
列名和数据类型也与相应域类的属性相匹配。
数据库初始化
在上面的例子中,我们已经看到 Code First 自动创建了一个数据库,但是如果您想更改数据库和服务器的名称,让我们看看 Code First 如何在初始化数据库时决定数据库名称和服务器。请看下图。
您可以按照以下方式定义上下文类的基本构造函数。
- 无参数
- 数据库名称
- 连接字符串名称
无参数
如果您指定上下文类的基本构造函数而不带任何参数(如上例所示),则实体框架将在您的本地 SQLEXPRESS 服务器中创建一个名为 {Namespace}.{Context 类名} 的数据库。
在上面的示例中,自动创建的数据库名为 EFCodeFirstDemo.MyContext。如果您查看名称,您会发现 EFCodeFirstDemo 是命名空间,而 MyContext 是上下文类名,如以下代码所示。
public class MyContext : DbContext { public MyContext() : base() {} public virtual DbSet<Course> Courses { get; set; } public virtual DbSet<Enrollment> Enrollments { get; set; } public virtual DbSet<Student> Students { get; set; } }
数据库名称
如果您将数据库名称作为参数传递到上下文类的基构造函数中,则 Code First 将再次自动创建数据库,但这次名称将是作为参数传递到本地 SQLEXPRESS 数据库服务器上的基构造函数中的名称。
在下面的代码中,MyContextDB 被指定为基构造函数中的参数。如果运行您的应用程序,则将在您的本地 SQL 服务器中创建名为 MyContextDB 的数据库。
public class MyContext : DbContext { public MyContext() : base("MyContextDB") {} public virtual DbSet<Course> Courses { get; set; } public virtual DbSet<Enrollment> Enrollments { get; set; } public virtual DbSet<Student> Students { get; set; } }
连接字符串名称
这是告诉 DbContext 使用 SQL Express 或 LocalDb 以外的数据库服务器的简单方法。您可以选择将连接字符串放入 app.config 文件中。
如果连接字符串的名称与您的上下文名称匹配(无论是否带有命名空间限定),则在使用无参数构造函数时,DbContext 会找到它。
如果连接字符串名称与您的上下文名称不同,则可以通过将连接字符串名称传递给 DbContext 构造函数来告诉 DbContext 在 Code First 模式下使用此连接。
public class MyContext : DbContext { public MyContext() : base("name = MyContextDB") {} public virtual DbSet<Course> Courses { get; set; } public virtual DbSet<Enrollment> Enrollments { get; set; } public virtual DbSet<Student> Students { get; set; } }
在上面的代码中,上下文类连接字符串的片段被指定为基构造函数中的参数。
连接字符串名称必须以"name="开头,否则,它将被视为数据库名称。
此形式明确表示您希望在配置文件中找到连接字符串。如果未找到具有给定名称的连接字符串,则会引发异常。
<connectionStrings> <add name = "MyContextDB" connectionString = "Data Source =.;Initial Catalog = EFMyContextDB;Integrated Security = true" providerName = "System.Data.SqlClient"/> </connectionStrings>
app.config 中的连接字符串中的数据库名称是 EFMyContextDB。CodeFirst 将创建一个新的 EFMyContextDB 数据库或使用本地 SQL Server 上现有的 EFMyContextDB 数据库。
域类
到目前为止,我们只是让 EF 使用其默认约定来发现模型,但有时我们的类不遵循约定,我们需要能够执行进一步的配置。但您可以通过配置域类来覆盖这些约定,以向 EF 提供所需的信息。有两种选项可以配置您的域类 −
- 数据注释
- Fluent API
数据注释
DataAnnotations 用于配置您的类,它将突出显示最常用的配置。许多 .NET 应用程序(例如 ASP.NET MVC)也能理解 DataAnnotations,这些应用程序可以利用相同的注释进行客户端验证。
以下是学生班中使用的数据注释。
public class Enrollment { [Key] public int EnrollmentID { get; set; } public int CourseID { get; set; } public int StudentID { get; set; } public Grade? Grade { get; set; } [ForeignKey("CourseID")] public virtual Course Course { get; set; } [ForeignKey("ID")] public virtual Student Student { get; set; } }
Fluent API
大多数模型配置都可以使用简单的数据注释来完成。流畅的 API 是一种指定模型配置的高级方法,它涵盖了数据注释可以做的所有事情,此外还包含一些数据注释无法实现的更高级的配置。数据注释和流畅的 API 可以一起使用。
要访问流畅的 API,您需要覆盖 DbContext 中的 OnModelCreating 方法。现在让我们将学生表中的列名从 FirstMidName 重命名为 FirstName,如以下代码所示。
public class MyContext : DbContext { protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<Student>().Property(s ⇒ s.FirstMidName) .HasColumnName("FirstName"); } public virtual DbSet<Course> Courses { get; set; } public virtual DbSet<Enrollment> Enrollments { get; set; } public virtual DbSet<Student> Students { get; set; } }