четверг, 26 мая 2022 г.

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

 

Последнее обновление: 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>

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

Комментариев нет:

Отправить комментарий

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

  Последнее обновление: 1.11.2015         Одним из наиболее часто используемых паттернов при работе с данными является паттерн 'Репозито...