Repostitory Pattern

Repository Pattern 是一個用來切割資料存取層和商業邏輯層的模式
針對資料的存取提供了基本的新增、修改、刪除、查詢等操作
返回的對象應該為IQueryable以供商業邏輯做更進一步的處理
這樣的好處是商業邏輯不直接處理資料的存取,方便之後抽換資料存取層,也方便單元測試
以下用一個簡單的留言版當例子透過ADO.NET和Entity Framework來存取資料
首先準備好留言版的資料庫,資料表就簡單的用GuestbookId和Message就好


定義一個Guestbook的Entity
namespace WebApplication1.Models
{
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

[Table(“Guestbook”)]
public class Guestbook
{
[Key]
public int GuestbookId { get; set; }
public string Message { get; set; }
}
}

順便定義一個DbContext
namespace WebApplication1.Models
{
using System.Data.Entity;

public class GuestbookContext : DbContext
{
public DbSet<Guestbook> Guestbook { get; set; }
}
}

定義一個IGuestbookRepository介面
namespace WebApplication1.Repositories
{
using System;
using System.Linq;
using WebApplication1.Models;

public interface IGuestbookRepository : IDisposable
{
IQueryable<Guestbook> GetAll();
Guestbook Find(int id);
void Add(Guestbook model);
void Edit(Guestbook model);
void Delete(int id);
}
}

透過EntityFramework實作一個Repository
namespace WebApplication1.Repositories
{
using System;
using System.Data.Entity;
using System.Linq;
using WebApplication1.Models;

public class EFGuestbookRepository : IGuestbookRepository
{
private bool disposed = false;
private GuestbookContext db;

public EFGuestbookRepository()
{
this.db = new GuestbookContext();
}

public IQueryable<Guestbook> GetAll()
{
return this.db.Guestbook;
}

public Models.Guestbook Find(int id)
{
return this.db.Guestbook.Find(id);
}

public void Add(Guestbook model)
{
this.db.Guestbook.Add(model);
this.db.SaveChanges();
}

public void Edit(Guestbook model)
{
this.db.Entry(model).State = EntityState.Modified;
this.db.SaveChanges();
}

public void Delete(int id)
{
var model = new Guestbook { GuestbookId = id };
this.db.Entry(model).State = EntityState.Deleted;
this.db.SaveChanges();
}

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

private void Dispose(bool disposing)
{
if (disposed)
{
return;
}

this.disposed = true;

if (disposing)
{
this.db.Dispose();
}
}
}
}

透過ADO.Net實作一個Repository
namespace WebApplication1.Repositories
{
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using WebApplication1.Models;

public class ADONetGuestbookRepository : IGuestbookRepository
{
private bool disposed = false;
private SqlConnection objConn;
private SqlCommand objCmd;

public ADONetGuestbookRepository()
{
string strConn = ConfigurationManager.ConnectionStrings[“GuestbookContext”].ConnectionString;
objConn = new SqlConnection(strConn);
objConn.Open();
objCmd = new SqlCommand();
objCmd.Connection = objConn;
}

public IQueryable<Guestbook> GetAll()
{
List<Guestbook> list = new List<Guestbook>();
objCmd.CommandText = “SELECT FROM Guestbook”;
using (SqlDataReader objDtr = objCmd.ExecuteReader())
{
while (objDtr.Read())
{
Guestbook model = new Guestbook()
{
GuestbookId = Convert.ToInt32(objDtr[“GuestbookId”]),
Message = objDtr[“Message”].ToString(),
};

list.Add(model);
}
}

return list.AsQueryable();
}

public Models.Guestbook Find(int id)
{
Guestbook model = new Guestbook();
objCmd.CommandText = “SELECT
FROM Guestbook WHERE GuestbookId = @GuestbookId”;
objCmd.Parameters.AddWithValue(“@GuestbookId”, id);
using (SqlDataReader objDtr = objCmd.ExecuteReader())
{
if (objDtr.Read())
{
model.GuestbookId = Convert.ToInt32(objDtr[“GuestbookId”]);
model.Message = objDtr[“Message”].ToString();
}
}

return model;
}

public void Add(Guestbook model)
{
objCmd.CommandText = “INSERT INTO Guestbook(Message) VALUES(@Message)”;
objCmd.Parameters.AddWithValue(“@Message”, model.Message);
objCmd.ExecuteNonQuery();
}

public void Edit(Guestbook model)
{
objCmd.CommandText = “UPDATE Guestbook SET Message = @Message WHERE GuestbookId = @GuestbookId”;
objCmd.Parameters.AddWithValue(“@GuestbookId”, model.GuestbookId);
objCmd.Parameters.AddWithValue(“@Message”, model.Message);
objCmd.ExecuteNonQuery();
}

public void Delete(int id)
{
objCmd.CommandText = “DELETE FROM Guestbook WHERE GuestbookId = @GuestbookId”;
objCmd.Parameters.AddWithValue(“@GuestbookId”, id);
objCmd.ExecuteNonQuery();
}

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

protected virtual void Dispose(bool disposing)
{
if (this.disposed)
{
return;
}

this.disposed = true;

if (disposing)
{
if (objConn.State != ConnectionState.Closed)
{
objConn.Close();
}

this.objConn.Dispose();
this.objCmd.Dispose();
}
}
}
}

透過Scaffold產生Controller和View

加入控制器

產生的Controller裡面是直接透過Entity Framework存取資料

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Mvc;
using WebApplication1.Models;

namespace WebApplication1.Controllers
{
public class GuestbookController : Controller
{
private GuestbookContext db = new GuestbookContext();

    // GET: Guestbook
    public ActionResult Index()
    {
        return View(db.Guestbook.ToList());
    }

    // GET: Guestbook/Details/5
    public ActionResult Details(int? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        Guestbook guestbook = db.Guestbook.Find(id);
        if (guestbook == null)
        {
            return HttpNotFound();
        }
        return View(guestbook);
    }

    // GET: Guestbook/Create
    public ActionResult Create()
    {
        return View();
    }

    // POST: Guestbook/Create
    // 若要免於過量張貼攻擊,請啟用想要繫結的特定屬性,如需
    // 詳細資訊,請參閱 http://go.microsoft.com/fwlink/?LinkId=317598。
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create([Bind(Include = "GuestbookId,Message")] Guestbook guestbook)
    {
        if (ModelState.IsValid)
        {
            db.Guestbook.Add(guestbook);
            db.SaveChanges();
            return RedirectToAction("Index");
        }

        return View(guestbook);
    }

    // GET: Guestbook/Edit/5
    public ActionResult Edit(int? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        Guestbook guestbook = db.Guestbook.Find(id);
        if (guestbook == null)
        {
            return HttpNotFound();
        }
        return View(guestbook);
    }

    // POST: Guestbook/Edit/5
    // 若要免於過量張貼攻擊,請啟用想要繫結的特定屬性,如需
    // 詳細資訊,請參閱 http://go.microsoft.com/fwlink/?LinkId=317598。
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit([Bind(Include = "GuestbookId,Message")] Guestbook guestbook)
    {
        if (ModelState.IsValid)
        {
            db.Entry(guestbook).State = EntityState.Modified;
            db.SaveChanges();
            return RedirectToAction("Index");
        }
        return View(guestbook);
    }

    // GET: Guestbook/Delete/5
    public ActionResult Delete(int? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        Guestbook guestbook = db.Guestbook.Find(id);
        if (guestbook == null)
        {
            return HttpNotFound();
        }
        return View(guestbook);
    }

    // POST: Guestbook/Delete/5
    [HttpPost, ActionName("Delete")]
    [ValidateAntiForgeryToken]
    public ActionResult DeleteConfirmed(int id)
    {
        Guestbook guestbook = db.Guestbook.Find(id);
        db.Guestbook.Remove(guestbook);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            db.Dispose();
        }
        base.Dispose(disposing);
    }
}

}

做一點調整改用剛定義的IGuestbookRepository介面來存取資料

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Mvc;
using WebApplication1.Models;
using WebApplication1.Repositories;

namespace WebApplication1.Controllers
{
public class GuestbookController : Controller
{
private IGuestbookRepository db;

    public GuestbookController()
    {
        this.db = new EFGuestbookRepository();
    }

    // GET: Guestbook
    public ActionResult Index()
    {
        return View(this.db.GetAll());
    }

    // GET: Guestbook/Details/5
    public ActionResult Details(int? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        Guestbook guestbook = this.db.Find(id.Value);
        if (guestbook == null)
        {
            return HttpNotFound();
        }
        return View(guestbook);
    }

    // GET: Guestbook/Create
    public ActionResult Create()
    {
        return View();
    }

    // POST: Guestbook/Create
    // 若要免於過量張貼攻擊,請啟用想要繫結的特定屬性,如需
    // 詳細資訊,請參閱 http://go.microsoft.com/fwlink/?LinkId=317598。
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create([Bind(Include = "GuestbookId,Message")] Guestbook guestbook)
    {
        if (ModelState.IsValid)
        {
            this.db.Add(guestbook);
            return RedirectToAction("Index");
        }

        return View(guestbook);
    }

    // GET: Guestbook/Edit/5
    public ActionResult Edit(int? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        Guestbook guestbook = this.db.Find(id.Value);
        if (guestbook == null)
        {
            return HttpNotFound();
        }
        return View(guestbook);
    }

    // POST: Guestbook/Edit/5
    // 若要免於過量張貼攻擊,請啟用想要繫結的特定屬性,如需
    // 詳細資訊,請參閱 http://go.microsoft.com/fwlink/?LinkId=317598。
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit([Bind(Include = "GuestbookId,Message")] Guestbook guestbook)
    {
        if (ModelState.IsValid)
        {
            this.db.Edit(guestbook);
            return RedirectToAction("Index");
        }
        return View(guestbook);
    }

    // GET: Guestbook/Delete/5
    public ActionResult Delete(int? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        Guestbook guestbook = this.db.Find(id.Value);
        if (guestbook == null)
        {
            return HttpNotFound();
        }
        return View(guestbook);
    }

    // POST: Guestbook/Delete/5
    [HttpPost, ActionName("Delete")]
    [ValidateAntiForgeryToken]
    public ActionResult DeleteConfirmed(int id)
    {
        this.db.Delete(id);
        return RedirectToAction("Index");
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            db.Dispose();
        }
        base.Dispose(disposing);
    }
}

}

要切換存取資料的Repository只要在建構式中切換建立的類別就行了

public GuestbookController()
{
// this.db = new EFGuestbookRepository();
this.db = new ADONetGuestbookRepository();
}

透過DI的方式注入介面切換就更靈活了

public GuestbookController(IGuestbookRepository repository)
{
this.db = repository;
}

參考資料
IOC 控制反轉 & DI 依賴注入
Autofac 一種IOC容器