这篇文章中我们建造一个电影数据库管理程序,在这里我们以一种极快极简单的方式来创建电影数据库管理程序,在控制层中直接对数据库进行操作。
接着我们学习使用Reporsitory模式,使用Repository模式需要我们额外化些力气,但它可以使我们的程序更容易测试,更好地应对变化。
一、什么是模型类
MVC模型层包含除了视图层和控制层之外的所有的逻辑,MVC模型层包含所有的商业逻辑和数据访问逻辑
我们可以使用各种不同的技术来实现数据访问逻辑。如:我们可以使用Microsoft Entity Framework,NHibernate,Subsonic或ADO.NET类来建造。
在这篇文章中,我们使用Linq to Sql来实现对数据库的查询和更新。Linq to Sql为我提供了操作Sql Server数据库的非常简单的接口,但不要以为ASP.NET MVC框架只能绑定Linq to Sql一起使用,ASP.NET MVC兼容于任何数据访问技术。
二、创建电影资料数据库
在这篇文章中,为了演示如何创建模型类,我们建立一个简单的电影数据库管理程序。
第一步创建一个新的数据库。在解决方案文件夹中右击App_Data文件夹选择“Add”-“New Item”,在弹出的对话框中,选择Sql Server Database,命名为MoviesDB.mdf。点击Add按钮,如图所示
《图1》
当我们创建新数据库后,我们在App_Data文件夹中双击MoviesDB.mdf文件,打开SqlServer Explorer如下图所示
《图2》
现在我们向数据库中添加表以保存电影信息。在SqlServer Explorer中右击Tables文件夹,选择“Add”-“New Table”,打开数据库设计界面,如图所示
《图3》
我们需要对Id列做两件事,首选需要把Id列标记为主键列,Linq to Sql在对数据进行insert和update操作时需要指定主键列信息,其次需要把Id列标记为自增长列。
三、创建Linq to Sql类
我们本程序的的MVC模型层中包含对tblMovie数据库的Linq to Sql类。创建Linq to Sql 类的简单方法是:右击Models文件夹,选择“Add”-“New Item”,在弹出的对话框中选择Linq to Sql Classes,并将其命名为Move.dbml,点击“Add”按钮。
《图4》
创建完Linq to Sql类后,会显示Object Relational Designer。我们从Server Explorer窗口中拖动表到Object Relation Designer上,这时会在设计器上现示数据表。
《图5》
默认情况下,当我们拖一个表到Object Relation Designer上的时候,Object Relation Designer会创建一个对应的类。如果我们不想让我们的类名与表同名(tblMovie),在设计器中点击类名把它改成你想要的即可。
最后记得点击保存按钮,以生成并保存我们的Linq to Sql类,否则Object Relation Designer不会生成Linq to Sql类。
四、在控制器动作中使用Linq to Sql
现在我们有了Linq to Sql类,我们可以使用这些类从数据库检索数据。在这一节中我们学习:如何在控器动作中使用Linq to Sql类直接访问数据库,并在视图层显示tblMovies表的信息。
首先,我们修改HomeController类,这可以在应用程序Controllers文件夹中找到。把类的代码改成如下形式:
Listing 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);
}
}
}
在上面的代码的Index()动作中,我们使用Linq to Sql的DataContext(MovieDataContext类)操作MovieDB数据库。MovieDataContext类是由Object Relation 设计器自动生成的类。
Linq查询是使用DataContext检索tblMovies表中所有数据。电影列表数据被赋给局部变量movies,最后movies列表通过viewdata传输给视图层。
要显示所有的电影信息,我们需要修改应用程序Views/Home/Index视图。代码如下:
Listing 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>
上面的代码中包含<%@ Import Namespace="MvcApplication1.Models" %>,它导入MvcApplication1.Models命名空间,MvcApplication1.Models是我们模型类所在的命名空间。
上面的代码有一个foreach循环迭代ViewData.Model中的每一项,每一部电影的标题就显示出来了。
我们可以看到,在上面的代码中,我们把ViewData.Model强制转换为 IEnumerable类型。只有这样我们才能对ViewData.Model返回的数据进行迭代。
如果我们现在运行程序会发现是个空白页面,因为我们还没有向数据库添加数据,在Server Explorer窗口中右击tblMovies表,在弹出菜单中选择“Show Table Data”,然后输入如图的数据。
《图6》
当我们向数据库的表中添加完记录后,再运行程序的时候,我们会发现所有的电影数据都显示在列表中。如图所示。
《图7》
五、使用Repository模式
在前面部分中,我们在控制器动作中直接使用Linq to Sql类。在简单的应用程序中我们这种做法并没有什么问题 ,但对于复杂的程序,这种直接在控制器中写Linq to Sql操作数据库的做法会给我们带来不少的麻烦。
在控制器中直接编写Linq to Sql的这种做法,在将来我们变更数据库访问技术的时候会变得很困难。比如说,我们将来可以会使用Microsoft Entity Framework来替换Microsoft Linq to Sql来访问数据库,一旦这样做,那我们需要去修改每个控制器的代码。
另外在控制器中直接编写Linq to Sql代码会使我们很难为应用程序编写单元测试。一般地,当我们进行单元测试的时候,并不需要直接与数据库交互,因为我们想要测试的是我们的程序逻辑,而不是测试数据库服务器。
为了使MVC应用程序更易维护,更易测试,我们应考虑使用Repository模式。
我们创建一个接口,该接口中定义了对数据库操作所必需的方法声明。然后我们编写类,在类中实现接口中的方法,采取某种数据库访问技术操作数据库。在控制器中,我们基于接口编写代码,这样我们就可以在将来变更数据库访问技术,而不需要修改控制器了。
这里我们编写了一个接口IMovieRepository,该接口中定义了一个方法ListAll();
Listing 3 – Models\IMovieRepository.cs
using System.Collections.Generic;
namespace MvcApplication1.Models
{
public interface IMovieRepository
{
IList<Movie> ListAll();
}
}
然后编写一个类MovieRepository实现IMovieRepository接口,在该类中实现方法ListAll()。
Listing 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
}
}
最后,我们在控制器MoviesController中使用Repository 模式,而不是直接使用Linq to Sql类操作数据库。
Listing 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());
}
}
}
上面的代码中,MoviesController类有两个构造函数,第一个构造函数是个无参构造函数。该构造函数被调用的时候,创建一个MovieRepository类的实例,并把这个实例传递给第二个构造函数。
第二个构造函数有一个IMovieRepository型的构造参数。这个构造函数就是把传进来的参数赋给成员变量_repository。
MoviesController控制器的这种做法是为了实现依赖注入模式而设计的,即我们经常说的“构造依赖注入”。关于依赖注入的相关内空请参见Martin Fowler的文章
http://martinfowler.com/articles/injection.html
还需要引起我们注意的是,在 MoviesController类中,我们是与IMovieRepository接口进行交互,而不是直接与MovieRepository类进行交互。这就是我们常说的“要依赖于抽象,而不依赖于具体。”
当我们想修改数据库访问代码的时候,我们只需要编写另一个类,该类也实现IMovieRepository接口。假设,我们创建了EntityFrameworkMovieRepository类或SubSonicMovieRepository类,因为我们控制器是基于接口编码的,所以我们在构造控制器的时候传入其中上面两个类的一个实例,那控制器和相关的类就要以配合工作了。
如果我们想对MoviesController类进行测试的话,我们可以传一个fake类到MoviesController控制器。这个fake类也是实现IMovieRepository接口,但并没有真正访问数据库。这样我们就可以只对MoviesController类进行测试,而没必要使用真实的数据访问逻辑。
总结
这篇文章演示了如何使用Linq to Sql创建MVC模型类。
首先,我们需要创建Linq to Sql类,然后直接在控制器动作中使用这些类。这种做法可以使我们快速简单地在MVC应用程序中显示数据库中的数据。
然后,我们又把问题稍微复杂化了一些,但数据显示更灵活,程序的弹性更大。这是因为我们使用了Repository模式。这种模式把所有的数据库访问逻辑全放在一个模型类中。在控制器中我们只对接口进行编程,这更能够为我们将来更改数据访问方式带来很大的方便 ,并使程序更易测试。