Паттерн 'Репозиторий' в ASP.NET

Последнее обновление: 1.11.2015

Одним из наиболее часто используемых паттернов при работе с данными является паттерн 'Репозиторий'. Репозиторий позволяет абстрагироваться от конкретных подключений к источникам данных, с которыми работает программа, и является промежуточным звеном между классами, непосредственно взаимодействующими с данными, и остальной программой.

Допустим, у нас есть одно подключение к базе данных MS SQL Server. Однако, что если в какой-то момент времени мы захотим сменить подключение с MS SQL на другое - например, к бд MySQL или MongoDB. При стандартном подходе даже в небольшом приложении, осуществляющем выборку, добавление, изменение и удаление данных, нам бы пришлось сделать большое количество изменений. Либо в процессе работы программы в зависимости от разных условий мы хотим использовать два разных подключения. Таким образом, репозиторий добавляет программе гибкость при работе с разными типами подключений.

Например, у нас в программе есть следующий класс модели, с которой мы работаем:

public class Book
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Author { get; set; }
    public int Price { get; set; }
}

И также имеется следующий класс контекста данных:

public class BookContext : DbContext
{
    public BookContext() : base("DefaultConnection")
    { }
    public DbSet<Book> Books { get; set; }
}

Пусть в файле web.config у меня определено два подключения:

<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>

Первое подключение используется контекстом данных для работы с бд MS SQL Server. Второе подключение - подключение к БД MongoDB.

Теперь определим интерфейс репозитория:

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();  // сохранение изменений
}

В данном случае мы создали обобщенный интерфейс, так как он более гибкий, по сравнению с обычной. Хотя так как у нас работа идет только с классом Book, можно было бы сделать и необобщенный вариант:

interface IRepository : IDisposable
{
    IEnumerable<Book> GetBookList();
    Book GetBook(int id);
    void Create(Book item);
    void Update(Book item);
    void Delete(int id);
    void Save();
}

На случай, если контекст данных будет подразумевать освобождение или закрытие подключений, интерфейс репозитория применяет интерфейс IDisposable.

Теперь создадим непосредственную реализацию для работы с MS SQL Server:

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);
    }
}

Почти все методы SQLBookRepository работают с контекстом данных, который создается в конструкторе класса.

Теперь применим репозиторий в контроллере:

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);
    }
}

В данном случае в всех методах контроллера мы работаем не с методами конкретного класса, а с методами интерфейса IRepository. И только в конструкторе контроллера мы определяем непосредственный тип репозитория: db = new SQLBookRepository();. Таким образом, мы практически избавляемся от зависимости к определенному типу подключения.

Но у меня в файле web.config определено еще подключение к MongoDB. И теперь создадим репозиторий, который будет использовать это подключение:

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() {}
}

Для работы с MongoDB используется другой API, однако, так как класс MongoBookRepository применяет тот же интерфейс репозитория, то общий набор методов у этого класса будет то же, что и у SqlBookRepository. Поэтому при необходимости можно просто заменить в контроллере тип репозитария:

public HomeController()
{
    db = new MongoBookRepository();
}

А весь остальной код будет тем же, что и раньше. Таким образом, мы можем уйти от явной привязки к одному хранилищу данных и тем самым повысить гибкость приложения.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850