Книга: ASP.NET MVC Framework

Реализация слоя данных

Реализация слоя данных

Создадим простейший слой для работы с базой данных, который отвечал бы всем нашим требованиям. Для начала возьмем простую структуру базы данных (рис. 3.5).


Рис. 3.5. Структура базы данных

У нас есть три таблицы: заказчики, заказы и товары. Каждая из таблиц содержит набор свойственных ей полей, так в таблице Products (товары) есть поле isAvailible, которое показывает, доступен ли товар в настоящее время. У таблицы Orders (заказы) есть поля count — количество товара в штуках и orderDateTime — дата и время оформления заказа. Таблица Customers (заказчики) содержит информацию о заказчике.

Для реализации хранилищ и сервисов нам необходимы интерфейсы данных, определим их так, как показано в листинге 3.1.

Листинг 3.1. Интерфейсы данных

public interface ICustomer {
  Guid CustomerId { set; get; }
  string Name { set; get; }
  string Phone { set; get; }
  string Address { set; get; }
}
public interface IOrder {
  Guid OrderId { set; get; }
  Guid CustomerId { set; get; }
  Guid ProductId { set; get; }
  int Count { set; get; }
  DateTime OrderDateTime { set; get; }
}
public interface IProduct {
  Guid ProductId { set; get; }
  string Name { set; get; }
  bool IsAvailible { set; get; }
  bool Cost { set; get; }
}

Воспользуемся мастером создания модели LINQ для SQL, чтобы сгенерировать классы для работы с базой данных. Посмотрим на сгенерированный код для таблицы Customers (приведен только фрагмент кода):

public partial class Customer : INotifyPropertyChanging,
                                INotifyPropertyChanged
{
  private System.Guid _customerId;
  private string _name;
  private string _address;
  private string _phone;
  private EntitySet<Order> _Orders;
  public Customer()
  {
    // код
  }
  [Column(Storage="_customerId",
    DbType="UniqueIdentifier NOT NULL",
    IsPrimaryKey=true)] public System.Guid customerId {
      get { return this._customerId; }
      set { // код }
    }
  [Column(Storage="_name",
    DbType="NVarChar(250) NOT NULL", CanBeNull=false)]
  public string name {
    get { return this._name; }
    set { // код }
  }
  [Column(Storage="_address",
    DbType="NVarChar(1024) NOT NULL",
    CanBeNull=false)]
  public string address {
    get { return this._address; }
    set { // код }
  }
  [Column(Storage="_phone", DbType="NVarChar(250)")]
  public string phone {
    get { return this._phone; }
    set { // код }
  }
  [Association(Name="Customer_Order",
    Storage="_Orders", ThisKey="customerId",
    OtherKey="customerId")]
  public EntitySet<Order> Orders {
    get { return this._Orders; }
    set { this._Orders.Assign(value); }
  }
}

Полученный код примечателен тем, что класс Customer является partial-классом, а это значит, что мы можем легко расширить его, и все прочие классы, для поддержки наших интерфейсов. Создадим частичные классы для реализации интерфейсов на базе LINQ для SQL так, как показано в листинге 3.2.

Листинг 3.2. Частичные классы с реализацией интерфейсов

public partial class Customer : ICustomer {
  public Guid CustomerId {
    get { return customerId; }
    set { customerId = value; }
  }
  public string Name {
    get { return name; }
    set { name = value; }
  }
  public string Phone {
    get { return phone; }
    set { phone = value; }
  }
  public string Address {
    get { return address; }
    set { address = value; }
  }
}
public partial class Order : IOrder {
  public Guid OrderId {
    get { return orderId; }
    set { orderld = value; }
  }
  public Guid Customerld {
    get { return customerId; }
    set { customerId = value; }
  }
  public Guid ProductId {
    get { return productId; }
    set { productId = value; }
  }
  public int Count {
    get { return count; }
    set { count = value; }
  }
  public DateTime OrderDateTime {
    get { return orderDateTime; }
    set { orderDateTime = value; }
  }
}
public partial class Product : IProduct {
  public Guid ProductId {
    get { return productId; }
    set { productId = value; }
  }
  public string Name {
    get { return name; }
    set { name = value; }
  }
  public bool IsAvailable {
    get { return isAvailable; }
    set { isAvailable = value; }
  }
  public decimal Cost {
    get { return cost; }
    set { cost = value; }
  }
}

На этом этапе существует еще одна полезная возможность, которую предлагает инъекция дополнительного кода: вы можете назначать имена для свойств интерфейса, не привязываясь к именам, которые определены в базе данных. Скажем, для поля cost таблицы Products мы могли бы задать другое название, например, ProductCost.

После реализации интерфейсов создадим простейшие хранилища и сервисы, для этого сначала объявим их интерфейсы:

public interface ICustomerRepository {
  ICustomer GetCustomerById(Guid customerId);
  IEnumerable<ICustomer> GetCustomersByProduct(Guid productId);
}

Хранилище для заказчиков позволит выбирать заказчика по идентификатору и выбирать всех заказчиков, связанных с определенным товаром.

public interface IOrderRepository {
  IOrder GetOrderById(Guid orderId);
  IEnumerable<IOrder> GetCustomerOrders(Guid customerId);
}

Хранилище для заказов позволит выбирать заказ по идентификатору и список заказов определенного заказчика.

public interface IProductRepository {
  IProduct GetProductById(Guid productId);
  IEnumerable<IProduct> GetAvailableProducts();
  IEnumerable<IProduct> GetProductListByName(string name);
}

Хранилище для товаров позволит найти товар по идентификатору, список товаров по наименованию и список товаров, которые доступны в данный момент.

Реализация данных хранилищ не составляет труда (листинг 3.3).

Листинг 3.3. Реализация хранилищ

public class CustomerRepository : ICustomerRepository {
  private readonly MyDatabaseDataContext _dataBase;
  public CustomerRepository(MyDatabaseDataContext db)
  {
    if (db == null)
      throw new ArgumentNullException("db");
    _dataBase = db;
  }
  public ICustomer GetCustomerById(Guid customerId)
  {
    if (customerId == Guid.Empty)
      throw new ArgumentException("customerId");
    return _dataBase.Customers
       .SingleOrDefault(x => x.customerId == customerId);
  }
  public IEnumerable<ICustomer> GetCustomersByProduct(Guid productId) {
    if (productId == Guid.Empty)
      throw new ArgumentException("customerId");
    return _dataBase.Orders
      .Where(x => x.productId == productId)
      .Select<Order, ICustomer>(x => x.Customer).Distinct();
  }
}
public class OrderRepository : IOrderRepository {
  private readonly MyDatabaseDataContext _dataBase;
  public OrderRepository(MyDatabaseDataContext db)
  {
    if (db == null)
      throw new ArgumentNullException("db");
    _dataBase = db;
  }
  public IOrder GetOrderByld(Guid orderld)
  {
    if (orderId == Guid.Empty)
      throw new ArgumentException("orderId");
    return _dataBase.Orders
      .SingleOrDefault(x => x.orderId == orderId);
  }
  public IEnumerable<IOrder> GetCustomerOrders(Guid customerId)
  {
    if (customerId == Guid.Empty)
      throw new ArgumentException("customerId");
    return _dataBase.Orders
      .Where(x => x.customerId == customerId)
      .Select<Order, IOrder>(x => x);
  }
}
public class ProductRepository : IProductRepository {
  private readonly MyDatabaseDataContext _dataBase;
  public ProductRepository(MyDatabaseDataContext db)
  {
    if (db == null)
      throw new ArgumentNullException("db");
    _dataBase = db;
  }
  public IProduct GetProductById(Guid productId)
  {
    if (productId == Guid.Empty)
      throw new ArgumentException("productId");
    return _dataBase.Products
      .SingleOrDefault(x => x.productId == productId);
  }
  public IEnumerable<IProduct> GetAvailableProducts()
  {
    return _dataBase.Products.Where(x => x.isAvailable)
      .Select<Product, IProduct>(x => x);
  }
  public IEnumerable<IProduct> GetProductListByName(string name)
  {
    if (string.IsNullOrEmpty(name))
      throw new ArgumentException("name");
    return _dataBase.Products
      .Where(x => x.name.Contains(name))
      .Select<Product, IProduct>(x => x);
  }
}

Далее создадим сервисы, которые будут помогать нам манипулировать данными. Сначала определим интерфейсы сервисов:

public interface ICustomerService {
  ICustomer Create(string name, string phone, string address);
  void Delete(ICustomer customer);
  void Update(ICustomer customer, string name,
             string phone, string address);
}
public interface IOrderService {
  IOrder Create(ICustomer customer, IProduct product, int count,
      DateTime orderDateTime);
  void Delete(IOrder order);
  void Update(IOrder order, ICustomer customer,
      IProduct product, int count, DateTime orderDateTime);
}
public interface IProductService {
  IProduct Create(string name, bool isAvailable, decimal cost);
  void Delete(IProduct product);
  void Update(IProduct product, string name,
          bool isAvailable, decimal cost);

Реализуем интерфейсы сервисов, как показано в листинге 3.4, для класса customerService. Чтобы уменьшить количество кода, приводить реализацию для других классов мы не будем, для остальных классов определяем методы Create, Delete и Update точно таким же образом.

Листинг 3.4. Реализация интерфейсов сервисов

public class CustomerService : ICustomerService {
  private readonly MyDatabaseDataContext _database;
  public CustomerService(MyDatabaseDataContext db)
  {
    if (db == null)
      throw new ArgumentNullException("db");
    _database = db;
  }
  public ICustomer Create(string name, string phone, string address)
  {
    if (String.IsNullOrEmpty(name))
      throw new ArgumentNullException("name");
    Customer customer = new Customer()
             {
               CustomerId = Guid.NewGuid(),
               Address = address,
               Phone = phone,
               Name = name
             };
    _database.Customers.InsertOnSubmit(customer);
    return customer;
  }
  public void Delete(ICustomer customer)
  {
    if (customer == null)
      throw new ArgumentNullException("customer");
    _database.Customers.DeleteOnSubmit((Customer)customer);
  }
  public void Update(ICustomer customer, string name,
                     string phone, string address)
  {
    if (customer == null)
      throw new ArgumentNullException("customer");
    if (String.IsNullOrEmpty(name))
      throw new ArgumentNullException("name");
    customer.Name = name;
    customer.Phone = phone;
    customer.Address = address;
  }
}

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

public class UnitOfWork : IDisposable {
  private readonly MyDatabaseDataContext _database;
  public MyDatabaseDataContext DataContext {
    get
    {
      return _database;
    }
  }
  private bool _disposed;
  public UnitOfWork()
  {
    _database = new MyDatabaseDataContext();
  }
  public void Commit()
  {
    _database.SubmitChanges();
  }
  public void Dispose()
  {
    Dispose(true); GC.SuppressFinalize(this);
  }
  private void Dispose(bool disposing) {
    if (!this._disposed)
    {
      if (disposing)
      {
        _database.Dispose();
      }
      _disposed = true;
    }
  }
}

Этот класс реализует паттерн UnitOfWork, который описывает реализацию атомарного действия, в данном случае создание контекста базы ORM, использование, сохранение изменений и разрушение объекта.

Оглавление книги


Генерация: 1.133. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз