ASP.NET MVC 中理解 Entity Framework Code First 方法的绝对初学者教程






4.88/5 (48投票s)
本文将讨论 Entity Framework Code First 方法。
引言
本文将讨论 Entity Framework Code First 方法。我们将了解 Code First 方法的优点。我们还将使用 Code First 方法创建一个 ASP.NET MVC 示例应用程序。
背景
在开始本文之前,我们需要了解现代应用程序架构方式的转变。传统上,我们一直在设计和开发以数据为中心的应用程序(数据驱动设计)。这意味着我们过去常常思考需要什么数据来满足我们的业务需求,然后我们从数据库架构自下而上地构建我们的软件。这种方法仍然适用于许多应用程序,对于此类应用程序,我们应该使用 Entity Framework Database First 方法。 
设计或架构应用程序的另一种方法是使用以领域为中心的方法(领域驱动设计)。在这种方法中,我们从解决特定业务问题所需的实体和模型的角度进行思考。现在,如果其中一些模型需要持久化,我们可以将它们保存在数据库或任何数据存储中。在这种方法中,我们以这样一种方式设计我们的模型,即它们可以存储/持久化在任何地方。换句话说,我们创建持久化无关的模型并单独编写持久化逻辑。Entity Framework Code First 方法是用于使用领域中心方法创建应用程序模型,然后可以稍后持久化它们。
因此,Entity Framework Code First 方法使我们能够为我们的模型编写普通旧 CLR 对象(POCO),然后通过为我们的模型类定义DbContext类,让我们将它们持久化到数据存储中。使用这种方法的一些好处是
- 支持领域驱动设计的能力。
- 能够更快地开始开发(无需等待数据库准备就绪和成熟)。
- 模型类更清晰,因为模型中没有(或非常少)与持久化相关的代码。
- 持久化层可以更改,而不会对模型产生任何影响。
注意:本文适用于从未接触过 Code First 方法或不了解此方法的绝对初学者。因此,本文只包含介绍性信息和代码,以解释概念并帮助读者入门。本文不会讨论与 Code First 方法相关的高级主题和最佳实践。
使用代码
让我们尝试通过创建一个小型演示应用程序来了解如何实现 Code First 方法。我们将创建的应用程序是一个小型图书信息网站,它将
- 显示图书列表。
- 显示所选图书的详细信息。
- 添加新图书。
- 删除图书。
- 编辑图书信息。
- 用户还应该能够为任何给定的图书添加评论。
根据上述问题定义,我们需要 2 个模型。一个用于Book,另一个用于Reviews。首先,让我们添加对 Entity Framework 的nuget包引用。
 
现在我们已经准备好开始开发我们的应用程序。
创建 POCO
现在让我们继续创建这些模型,而无需担心持久化问题。
public class Book
{
    public int BookID { get; set; }
    public string BookName { get; set; }
    public string ISBN { get; set; }
}
public class Review
{
    public int ReviewID { get; set; }
    public int BookID { get; set; }
    public string ReviewText { get; set; }
}
在我们的模型类中,我们定义了我们想要保留在模型中的属性。但是,我们到目前为止还没有做一些事情。这些模型之间存在一对多关系。因此,我们需要在模型中考虑到这一点。此外,如果我们要向数据库生成模块提示持久化信息,例如表名、键列等。我们也可以在模型类中这样做。因此,经过这些更改后,我们的模型类将如下所示。
[Table("Books")] // Table name
public class Book
{
    [Key] // Primary key
    public int BookID { get; set; }
    public string BookName { get; set; }
    public string ISBN { get; set; }
    // This is to maintain the many reviews associated with a book entity
    public virtual ICollection<Review> Reviews { get; set; }
}
[Table("Reviews")] // Table name
public class Review
{
    [Key]
    public int ReviewID { get; set; }
    [ForeignKey("Book")]
    public int BookID { get; set; }
    public string ReviewText { get; set; }
    // This will keep track of the book this review belong too
    public virtual Book Book { get; set; }
}
创建上下文类
现在我们已经准备好模型类。接下来,我们需要创建一个DBContext对象,它将负责对这些模型执行所有 CRUD 操作。让我们继续为这些模型创建DBContext类。
public class BooksDbContext : DbContext
{
    public DbSet<Book> Books { get; set; }
    public DbSet<Review> Reviews { get; set; }
}
上面定义的上下文类能够对Book和Review模型执行 CRUD 操作,因为它为它们都定义了DbSet。
设置数据库和位置
现在 DbContext 类已经准备就绪,我们唯一剩下要问的问题是这些数据将存储在哪里。自古以来,我们一直使用ConnectionStrings来指定与数据库的连接。在这种情况下,我们也可以使用ConnectionString来指定DbContext类应该使用的数据库的位置和元数据。让我们看看如果我们需要在 App_Data 文件夹中创建数据库,ConnectionString会是什么样子
<connectionStrings>
    <add name="BooksDbContext" connectionString="data source=.\SQLEXPRESS;attachdbfilename=|DataDirectory|\sampleDb.mdf;integrated security=True;user instance=True;multipleactiveresultsets=True;" providerName="System.Data.SqlClient" />
</connectionStrings>
这里需要注意的重要一点是,connectionString的名称与我们创建的DbContext类的名称相同。如果我们将connectionString的名称与DbContext类保持相同,则相应的DbContext类将使用connectionString来持久化数据。这符合“约定优于配置”的原则。但这也具有灵活性,因此我们可以为 connectionString 赋予自定义名称,即,如果我们需要为connectionString赋予任何其他名称或将某个已定义的connectionString与DbContext类一起使用,那么我们需要在DbContext类的基类构造函数中传入connectionString名称。
测试 Code First 模型和上下文
为了测试上述定义的模型,让我们创建一个简单的 ASP.NET MVC 控制器,它将对Book实体执行 CRUD 操作。 
public class BooksController : Controller
{
    BooksDbContext context = new BooksDbContext();
    public ActionResult Index()
    {
        List books = context.Books.ToList();
        return View(books);
    }
    public ActionResult Details(int id)
    {
        Book book = context.Books.SingleOrDefault(b => b.BookID == id);
            
        if (book == null)
        {
            return HttpNotFound();
        }
        return View(book);
    }
    public ActionResult Create()
    {
        return View();
    }
    [HttpPost]
    public ActionResult Create(Book book)
    {
        if (ModelState.IsValid)
        {
            context.Books.Add(book);
            context.SaveChanges();
            return RedirectToAction("Index");
        }
        return View(book);
    }
    public ActionResult Edit(int id)
    {
        Book book = context.Books.Single(p => p.BookID == id);
        if (book == null)
        {
            return HttpNotFound();
        }
        return View(book);
    }
    [HttpPost]
    public ActionResult Edit(int id, Book book)
    {
        Book _book = context.Books.Single(p => p.BookID == id);
        if (ModelState.IsValid)
        {
            _book.BookName = book.BookName;
            _book.ISBN = book.ISBN;
            context.SaveChanges();
            return RedirectToAction("Index");
        }
        return View(book);
    }
    public ActionResult Delete(int id)
    {
        Book book = context.Books.Single(p => p.BookID == id);
        if (book == null)
        {
            return HttpNotFound();
        }
        return View(book);
    }
    [HttpPost]
    public ActionResult Delete(int id, Book book)
    {
        Book _book = context.Books.Single(p => p.BookID == id);
        context.Books.Remove(_book);
        context.SaveChanges();
        return RedirectToAction("Index");
    }
    protected override void Dispose(bool disposing)
    {
        context.Dispose();
        base.Dispose(disposing);
    }
}
 
在这个模型中,我们使用 Context 类对Book实体执行 CRUD 操作。(要了解如何使用DbContext类执行 CRUD 操作,请参阅:绝对初学者的 Entity Framework 简介[^]) 
添加视图
让我们使用 ASP.NET MVC 视图生成向导为这个控制器生成强类型脚手架视图。(要了解有关 ASP.NET MVC 中默认脚手架控制器和视图的更多信息,请参阅:ASP.NET MVC for Web Forms 开发人员的绝对初学者教程[^])
图书列表
 
所选图书详情
 
创建新图书条目
 
 
编辑图书条目
 
删除图书
让我们运行并测试应用程序
这样,我们就拥有了一个使用 Entity Framework Code First 方法对 Books 实体进行 CRUD 操作的应用程序。让我们尝试运行这个应用程序。首次运行时,它不会显示任何数据,但它会在connectionString中指定的位置创建一个新数据库。
 
 
创建新图书条目
 
 
所选图书详情
 
 
编辑图书条目
 
 
删除图书
 
 
图书列表
管理实体关系 - 添加图书评论
让我们修改Book详细信息视图,以显示与该书相关的所有评论。
 
 
现在我们将为评论创建一个控制器,以便将评论添加到图书中。目前,我们只在此控制器中实现Create方法。
public class ReviewsController : Controller
{
    BooksDbContext context = new BooksDbContext();
    public ActionResult Create(int id)
    {            
        Book book = context.Books.SingleOrDefault(b => b.BookID == id);
        ViewBag.bookName = book.BookName;
        Review review = new Review();
        review.BookID = id;
        return View(review);
    }
    [HttpPost]
    public ActionResult Create(Review review)
    {
        try
        {
            if (ModelState.IsValid)
            {
                Book book = context.Books.SingleOrDefault(b => b.BookID == review.BookID);
                book.Reviews.Add(review);
                context.SaveChanges();
                return RedirectToAction("Details", "Books", new { id = book.BookID });
            }
            return View();
        }
        catch
        {
            return View();
        }
    }
}
现在我们将添加一个简单的视图来为图书添加评论。
 
 
一旦添加,这些评论将在图书详情页面上显示。
 
 
现在我们有了一个简单的应用程序,其中包含两个实体,它们之间具有一对多关系,并使用 Entity Framework Code First 方法将数据持久化到数据库中。
注意:在构建示例项目之前,请打开nuget包管理器并恢复缺少的包。
值得关注的点:
本文是为对 Code First 方法一无所知的绝对初学者编写的。本文向读者介绍了一些关于什么是 Code First 方法以及如何使用 Code First 方法实现一个简单的 ASP.NET MVC 应用程序的介绍性说明。本文未讨论但对于此主题仍然非常重要的是,当我们更改模型时数据库架构的更新,即数据库迁移。
注意:本文中我们没有使用存储库和工作单元等模式,因为文章的主要目的是向读者介绍 Entity Framework 的 Code First 方法。大多数有经验的程序员会觉得本文非常基础且帮助不大。但我希望这对于所有 Entity Framework Code First 的绝对初学者来说是有益的。
历史
- 2013 年 11 月 18 日:第一版




