介紹

在Controller直接去使用ApplicationDbContext對資料庫進行處理不是好的做法,所以要把這種方式做改變,不要直接在Controller中直接對ApplicationDbContext進行資料操作,這裡有一個重要的觀念是”關注點分離(SOC)”。以下對”關注點分離”稍微解釋一下,資料操作就是一種關注點,而資料驗證、流程控制都是關注點,當把這些關注點都放在一起的話,會覺得很混亂,所以我們可以把對資料庫做操作的部分給抽離出來,這就叫做”關注點分離”。 把資料庫抽離出來,通常會使用一種設計模式”Repository Pattern”,而這個模式就是用來處理資料操作,資料操作基本上一定有”CRUD”也就是Create(建立)、Read(讀取)、Update(更新)和Delete(刪除),所以我們就去為每個Model類別建立專屬的Repository類別,每個Repository類別只專注於自己所負責的Model的資料操作,不會干涉到其他資料類別的操作。 建立Repository還有一個優點是可重複使用,像是取得某筆資料、取得全部資料,這些資料操作會經常重複使用,可以將這些重複性高的方法建立在Repository,所以如果取得的資料有變動的話,就只需要修改Repository的方法就可以了。 以下我們會先介紹在Controller直接對資料庫做操作的方式,再介紹加入Repository的方式。以Project這一個Model實作在Controller對資料庫操作,Structure這一個Model則是加入Repository後的資料庫操作。

HomeController

HomeController的程式內容是初學者最常寫的一種,在HomeController去new一個全域的_context出來,這樣的操作是可以達到資料操作的要求沒錯,但是每個Controller的Action都要這樣處理時,這樣的方式就會很累人,而且在控制流程的地方直接對資料庫進行處理也不是那麼恰當。

private readonly ApplicationDbContext _context = new ApplicationDbContext();
public ActionResult Index()
{
	var projects = _context.Project.ToList();
	var viewModel = new ProjectListViewModel();
	viewModel.ProjectList = projects;
	return View(viewModel);
}
public ActionResult Create()
{
	var viewModel = new ProjectViewModel();
	return View(viewModel);
}
public ActionResult Edit(Guid id)
{
	var viewModel = new ProjectViewModel();
	var model = _context.Project.Find(id);
	viewModel.Id = id;
	viewModel.Name = model.Name;
	return View("Create", viewModel);
}
[HttpPost]
public ActionResult Save(ProjectViewModel viewModel)
{
	if (ModelState.IsValid)
	{
		if (viewModel.Id == Guid.Empty)
		{
			var model = new Project();
			model.Id = Guid.NewGuid();
			model.Name = viewModel.Name;
			_context.Project.Add(model);
			_context.SaveChanges();
		}
		else
		{
			var model = _context.Project.Find(viewModel.Id);
			model.Name = viewModel.Name;
			_context.Project.AddOrUpdate(model);
			_context.SaveChanges();
		}
		return RedirectToAction("Index");
	}
	return View("Create", viewModel);
}
public ActionResult Delete(Guid id)
{
	var model = _context.Project.Find(id);
	_context.Project.Remove(model);
	_context.SaveChanges();
	return RedirectToAction("Index");
}

Home的Index

顯示Project表中的資料,如下圖:

@model MVC.ViewModels.ProjectListViewModel
@{
    ViewBag.Title = "顯示";
}
<h2>@ViewBag.Title.</h2>
<h4>顯示專案。</h4>
@Html.ActionLink("新增", "Create", "Home", new { @class = "btn btn-primary" })
<hr/>
<table class="table table-bordered table-striped">
    <thead>
    <tr class="alert-info">
        <th>專案名稱</th>
        <th>動作</th>
    </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.ProjectList)
        {
            <tr>
                <td>@item.Name</td>
                <td>
                    @Html.ActionLink("編輯", "Edit", new { id = item.Id }, new { @class = "btn btn-info" })
                    @Html.ActionLink("刪除", "Delete", new { id = item.Id }, new { @class = "btn btn-danger" })
                </td>
            </tr>
        }
    </tbody>
</table>

ProjectList

Home的Create

新增專案的頁面和編輯頁面共用,如下圖:

@model MVC.ViewModels.ProjectViewModel

@if (Model.Id == Guid.Empty)
{
    {
        ViewBag.Title = "新增";
    }
    <h2>新增</h2>

    <h4>新增專案。</h4>
}
else
{
    {
        ViewBag.Title = "編輯";
    }
    <h2>編輯</h2>

    <h4>編輯專案。</h4>
}
<hr/>
@using (Html.BeginForm("Save", "Home", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
	@Html.HiddenFor(x => x.Id)
    <div class="form-group">
        @Html.Label("專案名稱", new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Name, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" class="btn btn-default" value="送出" />
        </div>
    </div>
}

ProjectCreate

建立Repository

先建立Repository類別的介面,建立介面是為了避免依賴Repository類別,之後在Controller只要用介面來進行資料操作,不需要直接使用Repository類別。 在專案中的Model目錄下建立Interface目錄,並新增一個IStructrueRepository。

IStructureRepository.cs
namespace MVC.Models.Interface
{
    public interface IStructureRepository : IDisposable
    {
        void Create(Project instance);

        void Update(Project instance);

        void Delete(Project instance);

        Project Get(Guid projectId);

        IQueryable<Project> GetAll();

        void SaveChanges();
    }
}

建立Repository類別,並實作剛剛建立的Interface,最後還要實作IDisposable介面,讓資料操作後可以釋放資源。

StructureRepository.cs
namespace MVC.Models.Repository
{
    public class StructureRepository : IStructureRepository, IDisposable
    {
        protected ApplicationDbContext Context
        {
            get;
            private set;
        }

        public StructureRepository()
        {
            this.Context = new ApplicationDbContext();
        }

        public void Create(Structure instance)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }
            else
            {
                Context.Structure.Add(instance);
                this.SaveChanges();
            }
        }

        public void Update(Structure instance)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }
            else
            {
                Context.Entry(instance).State = EntityState.Modified;
                this.SaveChanges();
            }
        }

        public void Delete(Structure instance)
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }
            else
            {
                Context.Entry(instance).State = EntityState.Deleted;
                this.SaveChanges();
            }
        }

        public Structure Get(Guid id)
        {
            return Context.Structure.FirstOrDefault(x => x.Id == id);
        }

        public IQueryable<Structure> GetAll()
        {
            return Context.Structure.OrderBy(x => x.Id);
        }

        public void SaveChanges()
        {
            this.Context.SaveChanges();
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.Context != null)
                {
                    this.Context.Dispose();
                    this.Context = null;
                }
            }
        }
    }
}

在Controller中使用Repository

建立好StructureRepository後就是在StructureController中使用,我們在Controller中是用Interface類別來操作資料,所以在Controller會增加以下程式。

public class StructureController : Controller
{
	private IStructureRepository structureRepository;

	public StructureController()
	{
		this.structureRepository = new StructureRepository();
	}
}

StructureController

加入Repository的Controller,該如何做CRUD,請看以下程式。

public class StructureController : Controller
{
	private IStructureRepository structureRepository;

	public StructureController()
	{
		this.structureRepository = new StructureRepository();
	}

	// GET: Structure
	public ActionResult Index()
	{
		var structures = structureRepository.GetAll().ToList();
		var viewModel = new StructureListViewModel();
		viewModel.StructureList = structures;
		return View(viewModel);
	}

	public ActionResult Create()
	{
		var viewModel = new StructureViewModel();
		return View(viewModel);
	}

	public ActionResult Edit(Guid id)
	{
		var viewModel = new StructureViewModel();
		var model = structureRepository.Get(id);
		viewModel.Id = id;
		viewModel.Name = model.Name;

		return View("Create", viewModel);
	}

	[HttpPost]
	public ActionResult Save(StructureViewModel viewModel)
	{
		if (ModelState.IsValid)
		{
			if (viewModel.Id == Guid.Empty)
			{
				var model = new Structure();
				model.Id = Guid.NewGuid();
				model.Name = viewModel.Name;
				structureRepository.Create(model);
			}
			else
			{
				var model = structureRepository.Get(viewModel.Id);
				model.Name = viewModel.Name;
				structureRepository.Update(model);
			}
			return RedirectToAction("Index");
		}
		return View("Create", viewModel);
	}

	public ActionResult Delete(Guid id)
	{
		var model = structureRepository.Get(id);
		structureRepository.Delete(model);

		return RedirectToAction("Index");
	}
}

到這裡就完成了Repository的實作與應用,StructureController沒有直接對資料庫進行資料操作,而是透過Repository來進行資料的存取,在Controller的方法裡就不再需要考慮到對資料庫的存取操作。 如果將Project也寫成Repository,就會看出和Structure有重複的地方,像是基本的資料操作CRUD,只有操作的類別不同,其他幾乎都一樣,所以可以將相同的部分抽離,那我們在下一篇將抽出Repository理重複的部分。

程式碼已放上Github

參考資料:
http://kevintsengtw.blogspot.tw/2012/10/aspnet-mvc-part1.html