Последнее обновление: 1.11.2015
Одним из наиболее часто используемых паттернов при работе с данными является паттерн 'Репозиторий'. Репозиторий позволяет абстрагироваться от конкретных подключений к источникам данных, с которыми работает программа, и является промежуточным звеном между классами, непосредственно взаимодействующими с данными, и остальной программой.
Допустим, у нас есть одно подключение к базе данных MS SQL Server. Однако, что если в какой-то момент времени мы захотим сменить подключение с MS SQL на другое - например, к бд MySQL или MongoDB. При стандартном подходе даже в небольшом приложении, осуществляющем выборку, добавление, изменение и удаление данных, нам бы пришлось сделать большое количество изменений. Либо в процессе работы программы в зависимости от разных условий мы хотим использовать два разных подключения. Таким образом, репозиторий добавляет программе гибкость при работе с разными типами подключений.
Например, у нас в программе есть следующий класс модели, с которой мы работаем:
1 2 3 4 5 6 7 8 | public class Book{ public int Id { get; set; } public string Name { get; set; } public string Author { get; set; } public int Price { get; set; }}<div class="open_grepper_editor" title="Edit & Save To Grepper"></div> |
И также имеется следующий класс контекста данных:
1 2 3 4 5 6 7 | public class BookContext : DbContext{ public BookContext() : base("DefaultConnection") { } public DbSet<Book> Books { get; set; }}<div class="open_grepper_editor" title="Edit & Save To Grepper"></div> |
Пусть в файле web.config у меня определено два подключения:
1 2 3 4 5 6 | <connectionStrings> <add name="DefaultConnection" connectionString="Data Source=(LocalDB)\v11.0;AttachDbFilename='|DataDirectory|\Bookstore.mdf';Integrated Security=True" providerName="System.Data.SqlClient"/> <add name="MongoDb" connectionString="server=127.0.0.1;database=bookstore" /> </connectionStrings><div class="open_grepper_editor" title="Edit & Save To Grepper"></div> |
Первое подключение используется контекстом данных для работы с бд MS SQL Server. Второе подключение - подключение к БД MongoDB.
Теперь определим интерфейс репозитория:
1 2 3 4 5 6 7 8 9 10 11 | interface IRepository<T> : IDisposable where T : class{ IEnumerable<T> GetBookList(); // получение всех объектов T GetBook(int id); // получение одного объекта по id void Create(T item); // создание объекта void Update(T item); // обновление объекта void Delete(int id); // удаление объекта по id void Save(); // сохранение изменений}<div class="open_grepper_editor" title="Edit & Save To Grepper"></div> |
В данном случае мы создали обобщенный интерфейс, так как он более гибкий, по сравнению с обычной. Хотя так как у нас работа идет только с классом Book, можно было бы сделать и необобщенный вариант:
1 2 3 4 5 6 7 8 9 10 | interface IRepository : IDisposable{ IEnumerable<Book> GetBookList(); Book GetBook(int id); void Create(Book item); void Update(Book item); void Delete(int id); void Save();}<div class="open_grepper_editor" title="Edit & Save To Grepper"></div> |
На случай, если контекст данных будет подразумевать освобождение или закрытие подключений, интерфейс репозитория применяет интерфейс IDisposable.
Теперь создадим непосредственную реализацию для работы с MS SQL Server:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | public class SQLBookRepository : IRepository<Book>{ private BookContext db; public SQLBookRepository() { this.db = new BookContext(); } public IEnumerable<Book> GetBookList() { return db.Books; } public Book GetBook(int id) { return db.Books.Find(id); } public void Create(Book book) { db.Books.Add(book); } public void Update(Book book) { db.Entry(book).State = EntityState.Modified; } public void Delete(int id) { Book book = db.Books.Find(id); if(book!=null) db.Books.Remove(book); } public void Save() { db.SaveChanges(); } private bool disposed = false; public virtual void Dispose(bool disposing) { if(!this.disposed) { if(disposing) { db.Dispose(); } } this.disposed = true; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }}<div class="open_grepper_editor" title="Edit & Save To Grepper"></div> |
Почти все методы SQLBookRepository работают с контекстом данных, который создается в конструкторе класса.
Теперь применим репозиторий в контроллере:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | public class HomeController : Controller{ IRepository<Book> db; public HomeController() { db = new SQLBookRepository(); } public ActionResult Index() { return View(db.GetBookList()); } public ActionResult Create() { return View(); } [HttpPost] public ActionResult Create(Book book) { if(ModelState.IsValid) { db.Create(book); db.Save(); return RedirectToAction("Index"); } return View(book); } public ActionResult Edit(int id) { Book book = db.GetBook(id); return View(book); } [HttpPost] public ActionResult Edit(Book book) { if (ModelState.IsValid) { db.Update(book); db.Save(); return RedirectToAction("Index"); } return View(book); } [HttpGet] public ActionResult Delete(int id) { Book b = db.GetBook(id); return View(b); } [HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) { db.Delete(id); return RedirectToAction("Index"); } protected override void Dispose(bool disposing) { db.Dispose(); base.Dispose(disposing); }}<div class="open_grepper_editor" title="Edit & Save To Grepper"></div> |
В данном случае в всех методах контроллера мы работаем не с методами конкретного класса, а с методами интерфейса IRepository. И только в конструкторе контроллера мы определяем непосредственный тип репозитория: db = new SQLBookRepository();. Таким образом, мы практически избавляемся от зависимости к определенному типу подключения.
Но у меня в файле web.config определено еще подключение к MongoDB. И теперь создадим репозиторий, который будет использовать это подключение:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | using System;using System.Collections.Generic;using System.Linq;using System.Web;using System.Web.Mvc;using MongoDB.Bson;using MongoDB.Driver;using MongoDB.Driver.Builders;using System.Configuration;public class MongoBookRepository : IRepository<Book>{ MongoClient client; MongoServer server; MongoDatabase database; public MongoBookRepository() { var con = new MongoConnectionStringBuilder( ConfigurationManager.ConnectionStrings["MongoDb"].ConnectionString); client = new MongoClient(con.ConnectionString); server = client.GetServer(); database = server.GetDatabase(con.DatabaseName); } public MongoCollection<Book> Books { get { return database.GetCollection<Book>("books"); } } public IEnumerable<Book> GetBookList() { return Books.FindAll(); } public Book GetBook(int id) { return Books.FindOneById(id); } public void Create(Book book) { try { int id = 0; if (Books.Count() > 0) id = Books.FindAll().Max(x => x.Id); book.Id = ++id; Books.Insert(book); } catch { } } public void Update(Book book) { try { Books.Save(book); } catch { } } public void Delete(int id) { try { var query = Query<Book>.EQ(e => e.Id, id); if (query != null) { Books.Remove(query); } } catch { } } public void Save() {} public void Dispose() {}}<div class="open_grepper_editor" title="Edit & Save To Grepper"></div> |
Для работы с MongoDB используется другой API, однако, так как класс MongoBookRepository применяет тот же интерфейс репозитория, то общий набор методов у этого класса будет то же, что и у SqlBookRepository. Поэтому при необходимости можно просто заменить в контроллере тип репозитария:
1 2 3 4 5 | public HomeController(){ db = new MongoBookRepository();}<div class="open_grepper_editor" title="Edit & Save To Grepper"></div> |
А весь остальной код будет тем же, что и раньше. Таким образом, мы можем уйти от явной привязки к одному хранилищу данных и тем самым повысить гибкость приложения.
Комментариев нет:
Отправить комментарий