Asp.Net MVC 模型(使用LINQ to SQL创建Model类) - Part.2

这篇教程的目的是解释一种为ASP.NET MVC应用程序创建模型类的方法。在这篇教程中,你会学习到如何利用Microsoft LINQ to SQL创建模型类并执行数据库访问。

在这篇教程中,我们创建了一个基本的Movie数据库应用程序。我们尽可能地用最快速和最简单的方法创建Movie数据库应用程序作为开始。我们直接从控制器动作中执行了所有的数据访问。

接下来,你会学习如何使用Repository模式。使用Repository模式需要更多的一点工作。然而,采用这个模式的优点是它允许你创建能够适应变化并且测试简单的应用程序。

1.什么是Model类

MVC模型包含了所有MVC视图或者MVC控制器没有包含的应用程序逻辑。特别地,一个MVC模型包含了所有的应用程序业务和数据访问逻辑。

你可以使用各种各样不同的技术来实现你的数据访问逻辑。举个例子,你可以使用Microsoft Entity Framework、NHibernate、Subsonic或者ADO.NET类来构建你的数据访问类。

在这篇教程中,我使用LINQ to SQL来查询和更新数据库。LINQ to SQL为你提供了一种与Microsoft SQL Server数据库进行交互的非常简单的方法。然而,理解ASP.NET MVC框架丝毫没有局限于LINQ to SQL是非常重要的。ASP.NET MVC与任何的数据访问技术都是兼容的。

2.创建一个Movie数据库

在这篇教程中,为了演示如何构建模型类――我们创建了一个简单的Movie数据库应用程序。第一步是创建一个新的数据库。在解决方案浏览器窗口的App_Data文件夹上点击右键,选择菜单项“添加(Add)”,“新建项(New Item)”。选择SQL Server Database模板,命名为MoviesDB.mdf,并且点击“添加(Add)”按钮(如图1)。

图1:添加一个新的SQL Server Database

在你创建这个新的数据库以后,可以通过在App_Data文件夹中双击MoviesDB.mdf文件来打开数据库。双击MoviesDB.mdf文件会打开“服务器资源管理器(Server Explorer)”窗口(如图2)。

当使用Visual Web Developer时,“服务器资源管理器”叫做“数据库资源管理器(Database Explorer)”。

图2:使用“服务器资源管理器”窗口

我们需要在数据库中添加一个表,这个表代表电影。右键点击Tables文件夹,并且选择菜单项“添加新表(Add New Table)”。选择这个菜单项会打开“表设计器(Table Designer)”(如图3)。

图3:表设计器

我们需要向我们的数据库表添加下面的列:

列名 数据类型 允许Null
Id Int False
Title Nvarchar(200) False
Director Nvarchar(50) False

你需要对Id列做两件特别的事情。首先,你需要通过在表设计器中选择列,并且点击钥匙图标,将Id列标识为主键列。当对数据库进行插入或者更新时,LINQ to SQL要求你指定主键列。

接下来,你需要通过将IsIdentity属性赋值为Yes值,来将Id列标记为Identity列(如图3)。每当你向表中添加一行新数据时,Identity列会自动地赋一个新的数字。

3.创建LINQ to SQL类

我们的MVC模型类将会包含LINQ to SQL类,这些LINQ to SQL类代表着tblMovie数据库表。创建LINQ to SQL最简单的方法就是右键点击Models文件夹,选择“添加(Add)”,“新建项(New Item)”,选择LINQ to SQL类模板,将类命名为Movie.dbml,并且点击“添加”按钮(如图4)。

图4:创建LINQ to SQL类

在我们创建好Movie LINQ to SQL 类之后,会立即出现“对象关系设计器(Object Relational Designer)”。你可以将数据库表从“服务器资源管理器(Server Explorer)”窗口中拖曳到“对象关系设计器”中,来创建代表着特定数据库表的LINQ to SQL类。我们需要添加tblMovie数据库表到“对象关系设计器”中(如图5)。

图5:使用“对象关系设计器”

默认情况下,“对象关系设计器”创建的类与你拖曳到设计器中的数据库表名完全相同。然而,我们不想管我们的类叫做tblMovie。因此,在设计器中点击类名,并且将类名改为Movie。

最后,记得点击“保存(Save)”按钮来保存LINQ to SQL类。否则,LINQ to SQL类将不会由“对象关系设计器”生成。

4.在控制器动作中使用LINQ to SQL

现在我们已经拥有了LINQ to SQL类,我们可以使用这些类从数据库中获取数据。在本节中,你会学习如何直接在控制器动作中使用LINQ to SQL类。我们将会在一个MVC视图中显示来自tblMovies数据库表中的电影列表。

首先,我们需要更改HomeController类。这个类可以在你应用程序中的Controllers文件夹中找到。修改这个类,使得它像代码清单1所示的那样。

代码清单1 - Controllers\HomeController.cs

using System.Linq;
using System.Web.Mvc;
using MvcApplication1.Models;

namespace MvcApplication1.Controllers{

     [HandleError]
     public class HomeController : Controller {

          public ActionResult Index(){

               var dataContext = new MovieDataContext();
               var movies = from m in dataContext.Movies
                    select m;
               return View(movies);
          }
     }
}

代码清单1中的Index()动作使用了LINQ to SQL DataContext类(MovieDataContext)来代表MoviesDB数据库。MovieDataContext类由Visual Studio“对象关系设计器”生成。

Index()动作执行了一个对DataContext的LINQ查询,用于从tblMovies数据库表中获取所有的电影。电影列表被赋给了一个叫做movies的本地变量。最后,电影列表通过视图数据传递给了视图。

为了显示电影列表,我们接下来需要修改Index视图。你可以在Views\Home\文件夹下找到Index视图。更新Index视图,让它和代码清单2中的视图相同。

代码清单2 - Views\Home\Index.aspx

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="MvcApplication1.Views.Home.Index" %>
<%@ Import Namespace="MvcApplication1.Models" %>
<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">

     <ul>
          <% foreach (Movie m in (IEnumerable)ViewData.Model)
          { %>
               <li> <%= m.Title %> </li>
          <% } %>
     </ul>
</asp:Content>

注意到修改后的Index视图在其顶部包含一个<%@import namespace%>指示符。这个指示符引入了MvcApplication1.Models命名空间。为了处理model类,我们需要这个命名空间,尤其是――视图中的Movie类。

代码清单2中的视图包含一个foreach循环,它遍历了所有由ViewData.Model属性所代表的项。显示了每一个movie的Title属性的值。

注意到ViewData.Model属性被强制转换为了一个IEnumerable。为了遍历ViewData.Model的内容,这个是必需的。另一个选择是创建一个强类型的视图。当你创建一个强类型的视图时,你将ViewData.Model属性强制转换为视图的后置代码类中的特定类型。

如果你在修改了HomeController类和Index视图后运行应用程序,你将会获得一个空白页。你之所以获得一个空白页,是因为在tblMovies数据库表中没有movie记录。

为了向tblMovies数据库表中添加记录,右键点击“服务器资源管理器”窗口(在Visual Web Developer中叫做“数据库资源管理器”)中的tblMovies数据库表,并且选择菜单项“显示表格数据(Show Table Data)”。你可以使用所显示的网格来插入movies记录(如图6)。

图6:插入电影

在你向tblMovies表中添加了一些数据库记录以后,并且运行应用程序,你将会看到图7所示的页面。所有的movie数据库记录都显示在了项目符号列表中。

图7:使用Index视图显示电影

5.使用Repository模式

在前面一节中,我们直接在一个控制器动作中使用了LINQ to SQL。我们直接从Index()控制器动作中使用了MovieDataContext类。对于一个简单的应用程序来说,这没有什么问题。然而,当你需要构建更加复杂的应用程序时,直接在控制器类中处理LINQ to SQL会造成一些问题。

在控制器类中使用LINQ to SQL会使以后切换数据访问技术时出现困难。举个例子,你可能想将Microsoft LINQ to SQL切换为使用Microsoft Entity Framework,来作为你的数据访问技术。在这种情况下,你需要在应用程序中重写每一个访问数据库的控制器。

在控制器类中使用LINQ to SQL也使得为应用程序创建单元测试更为困难。通常,在执行单元测试时,你不需要与数据库进行交互。你想要使用单元测试来测试你的应用程序逻辑,而非你的数据库服务器。

为了构建更加适应未来变化以及更加易于测试的MVC应用程序,你应该考虑使用Repository模式。当你使用Repository模式时,你会创建一个独立的repository类,它包含了所有的数据访问逻辑。

当你创建repository类时,你创建了一个接口,该接口代表着所有由repository类所使用的方法。在你的控制器中,你针对接口编写代码,而不是针对repository。通过这种方式,你以后可以使用不同的数据访问技术来实现repository。

代码清单3中的接口命名为了IMovieRepository,并且它包含了一个方法,叫做ListAll()。

代码清单3 - Models\IMovieRepository.cs

using System.Collections.Generic;
namespace MvcApplication1.Models{

     public interface IMovieRepository {
          IList<Movie> ListAll();
     }
}

代码清单4中的repository类实现了IMovieRepository接口。注意到它包含了一个叫做ListAll()的方法,该方法与IMovieRepository接口所要求的方法相对应。

代码清单4 - Models\MovieRepository.cs

using System.Collections.Generic;
using System.Linq;

namespace MvcApplication1.Models {
    public class MovieRepository : IMovieRepository {
        private MovieDataContext _dataContext;

        public MovieRepository() {
            _dataContext = new MovieDataContext();
        }

        #region IMovieRepository Members

        public IList<Movie> ListAll() {
            var movies = from m in _dataContext.Movies
            select m;
            return movies.ToList();
        }

        #endregion
    }
}

最后,代码清单5中的MoviesController类使用了Repository模式。它不再直接使用LINQ to SQL类。

代码清单5 - Controllers\MoviesController.cs

using System.Web.Mvc;
using MvcApplication1.Models;

namespace MvcApplication1.Controllers {

    public class MoviesController : Controller {
        private IMovieRepository _repository;

        public MoviesController() : this(new MovieRepository()) { }

        public MoviesController(IMovieRepository repository) {
            _repository = repository;
        }

        public ActionResult Index() {
            return View(_repository.ListAll());
        }
    }
}

注意到代码清单5中的MoviesController类有两个构造函数。第一个构造函数,无参数的构造函数,在你应用程序运行时调用。这个构造函数创建了一个MovieRepository类的实例,并把它传递给了第二个构造函数。

第二个构造函数只有一个参数:一个IMovieRepository参数。这个构造函数简单地将参数的值赋给了叫做_repository的类级字段。

MoviesController类利用了软件设计模式中称作依赖注入(Dependency Injection)的模式。特别地,它使用了构造函数依赖注入(Constructor Dependency Injection)。你可以通过阅读下面这篇由Martin Fowler所作的文章,来阅读更多关于这个模式的信息:

http://martinfowler.com/articles/injection.html

注意到MoviesController类中所有的代码(除了第一个构造函数)都与IMovieRepository接口进行交互,而不是实际的MovieRepository类。代码与一个抽象的接口交互,而不是与接口的具体实现交互。

如果你想要修改应用程序所使用的数据访问逻辑,那么你可以简单地用一个类实现IMovieRepository接口,该类使用不同的数据库访问技术。举个例子,你可以创建一个EntityFrameworkMovieRepository类,或者一个SubSonicMovieRepository类。因为控制器类针对接口编程,你可以向控制器类传递一个IMovieRepository的新实现,并且这个类将继续工作。

除此以外,如果你想要测试MoviesController类,那么你可以向HomeController传递一个伪movie Respository类。你可以使用一个实际上并没有访问数据库,但是包含了所有IMovieRepository接口所要求的方法的类来实现IMovieRepository。通过这种方式,你可以对MoviesController类进行单元测试,而不需要实际地访问一个真正的数据库。

6.小结

这篇教程的目的是演示如何利用Microsoft LINQ to SQL创建MVC模型类。我们解释了在ASP.NET MVC应用程序中显示数据库数据的两种策略。首先,我们直接在控制器动作中创建和使用了LINQ to SQL类。在控制器中使用LINQ to SQL类使你能够快速且容易地在MVC应用程序中显示数据库数据。

接下来,我们探索了一个稍微困难一点儿,但绝对是一个更好的途径来显示数据库数据。我们利用了Repository模式,并且将所有的数据库逻辑都放在了一个独立的repository类中。在我们的控制器中,我们编写的所有代码都是针对一个接口,而非一个实体类。Repository模式的优点是它允许我们轻松地在以后改变数据访问技术,并且允许我们容易地测试我们的控制器类。