Entity Framework - 第一个例子

让我们使用类定义一个非常简单的模型.我们只是在Program.cs文件中定义它们,但在实际应用程序中,您将把类拆分为单独的文件,并可能是一个单独的项目.以下是我们将使用Code First方法创建的数据模型.

Model Using Classes

创建模型

使用以下代码为Student类在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属性将包含所有的Enrollment实体.与该学生实体相关.

  • 导航属性通常定义为虚拟,以便他们可以利用某些实体框架功能,例如延迟加载.

  • 如果导航属性可以容纳多个实体(如多对多关系或一对多关系),则其类型必须是可以添加条目的列表,删除和更新,例如ICollection.

以下是课程类的实现.

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属性是导航属性.课程实体可以与任意数量的注册实体相关.

以下是Enrollment class和enum的实现.

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属性可以为空.

  • null的等级与零等级不同. Null表示成绩未知或尚未分配.

  • StudentID和CourseID属性是外键,相应的导航属性为Student和课程.

  • 注册实体与一个学生和一个课程实体相关联,因此该属性只能容纳一个学生和课程实体.

创建数据库上下文

为给定数据模型协调实体框架功能的主类是数据库上下文类,允许查询和保存数据.您可以通过从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,如下图所示.

SQL Express Instance

  • 这些只是默认约定,有多种方法可以更改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数据库服务器上的基本构造函数中作为参数传递的名称.

In以下代码,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文件中放置连接字符串.

  • 如果连接字符串的名称与您的上下文的名称(有或没有名称空间限定),当使用参数less constructor时,DbContext将找到它.

  • 如果连接字符串name与您的上下文的名称不同,然后您可以通过将连接字符串名称传递给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将在本地SQL Server上创建新的 EFMyContextDB 数据库或使用现有的 EFMyContextDB 数据库.

域类

到目前为止,我们只是让EF使用默认约定来发现模型,但有时我们的类不遵循约定和我们需要能够执行进一步的配置.但是,您可以通过配置域类来为EF提供所需的信息来覆盖这些约定.配置域类有两种选择 :

  • 数据注释

  • Fluent API

数据注释

DataAnnotations用于配置您的类,这将突出显示最常用的配置.许多.NET应用程序也理解DataAnnotations,例如ASP.NET MVC,它允许这些应用程序利用相同的注释进行客户端验证.

以下是使用的数据注释在学生班.

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; }
}