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

Создадим простейший слой для работы с базой данных, который отвечал бы всем нашим требованиям. Для начала возьмем простую структуру базы данных (рис. 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, использование, сохранение изменений и разрушение объекта.

Более 800 000 книг и аудиокниг! 📚

Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением

ПОЛУЧИТЬ ПОДАРОК