实体框架 – 第一个示例
实体框架 – 第一个示例
让我们使用类定义一个非常简单的模型。我们只是在 Program.cs 文件中定义它们,但在实际应用程序中,您会将类拆分为单独的文件,并且可能是一个单独的项目。以下是我们将使用 Code First 方法创建的数据模型。
创建模型
使用以下学生类代码在 Program.cs 文件中添加以下三个类。
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 使用的数据库。
-
正如您在上图中所看到的,它创建了学生、课程和注册表,每个表都包含具有适当数据类型和长度的列。
-
列名和数据类型也与相应域类的属性匹配。
数据库初始化
在上面的例子中,我们看到了 Code First 自动创建了一个数据库,但是如果你想更改数据库和服务器的名称,让我们看看 Code First 在初始化数据库时如何决定数据库名称和服务器。看看下面的图表。
您可以通过以下方式定义上下文类的基构造函数。
- 无参数
- 数据库名称
- 连接字符串名称
无参数
如果如上例所示指定上下文类的基本构造函数而没有任何参数,则实体框架将在本地 SQLEXPRESS 服务器中创建一个名为 {Namespace}.{Context class name} 的数据库。
在上面的示例中,自动创建的数据库名为 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 提供所需的信息来覆盖这些约定。有两个选项可以配置您的域类 –
- 数据注释
- 流利的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; } }
流利的API
大多数模型配置可以使用简单的数据注释来完成。fluent API 是一种指定模型配置的高级方法,它涵盖了数据注释可以做的所有事情,此外还有一些数据注释无法实现的更高级配置。数据注释和 fluent API 可以一起使用。
要访问 fluent 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; } }