Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен
Шрифт:
Интервал:
Закладка:
{
Context = context;
_disposeContext = false;
}
protected BaseRepo(DbContextOptions<ApplicationDbContext> options) : this(new
ApplicationDbContext(options))
{
_disposeContext = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private bool _isDisposed;
protected virtual void Dispose(bool disposing)
{
if (_isDisposed)
{
return;
}
if (disposing)
{
if (_disposeContext)
{
Context.Dispose();
}
}
_isDisposed = true;
}
~BaseRepo()
{
Dispose(false);
}
На свойства DbSet<T> класса ApplicationDbContext можно ссылаться с использованием метода Context.Set<T>(). Создайте открытое свойство по имени Table типа DbSet<T> и установите его начальное значение в конструкторе:
public DbSet<T> Table { get; }
protected BaseRepo(ApplicationDbContext context)
{
Context = context;
Table = Context.Set<T>();
_disposeContext = false;
}
Реализация метода SaveChanges()
Класс BaseRepo имеет метод SaveChanges(), который вызывает переопределенную версию SaveChanges() и демонстрирует обработку специальных исключений. Добавьте в класс BaseRepo показанный ниже код:
public int SaveChanges()
{
try
{
return Context.SaveChanges();
}
catch (CustomException ex)
{
// Подлежит надлежащей обработке -- уже зарегистрировано в журнале.
throw;
}
catch (Exception ex)
{
// Подлежит регистрации в журнале и надлежащей обработке.
throw new CustomException("An error occurred updating the database", ex);
}
}
Реализация общих методов чтения
Следующий комплект методов возвращает записи с применением операторов LINQ. Метод Find() принимает первичный ключ (ключи) и сначала выполняет поиск в ChangeTracker. Если сущность уже отслеживается, тогда возвращается отслеживаемый экземпляр, иначе запись извлекается из базы данных.
public virtual T? Find(int? id) => Table.Find(id);
Дополнительные два метода Find() расширяют базовый метод Find(). Приведенный далее метод демонстрирует извлечение записи, но без ее добавления в ChangeTracker, используя AsNoTrackingWithldentityResolution(). Добавьте в класс показанный ниже код:
public virtual T? FindAsNoTracking(int id) =>
Table.AsNoTrackingWithIdentityResolution().FirstOrDefault(x => x.Id == id);
Другая вариация удаляет из сущности фильтры запросов и затем применяет сокращенную версию (пропускающую метод Where()) для получения FirstOrDefault(). Добавьте в класс следующий код:
public T? FindIgnoreQueryFilters(int id) =>
Table.IgnoreQueryFilters().FirstOrDefault(x => x.Id == id);
Методы GetAll() возвращают все записи из таблицы. Первый метод извлекает их в порядке, поддерживаемом в базе данных, а второй по очереди обрабатывает все фильтры запросов:
public virtual IEnumerable<T> GetAll() => Table;
public virtual IEnumerable<T> GetAllIgnoreQueryFilters()
=> Table.IgnoreQueryFilters();
Метод ExecuteQuery() предназначен для выполнения хранимых процедур:
public void ExecuteQuery(string sql, object[] sqlParametersObjects)
=> Context.Database.ExecuteSqlRaw(sql, sqlParametersObjects);
Реализация методов добавления, обновления и удаления
Далее понадобится добавить блок кода, который будет служить оболочкой для соответствующих методов добавления, обновления и удаления, связанных со специфичным свойством DbSet<T>. Параметр persist определяет, выполняет ли хранилище вызов SaveChanges() сразу же после вызова методов добавления, обновления и удаления. Все методы помечены как virtual, чтобы сделать возможным дальнейшее переопределение. Добавьте в класс показанный ниже код:
public virtual int Add(T entity, bool persist = true)
{
Table.Add(entity);
return persist ? SaveChanges() : 0;
}
public virtual int AddRange(IEnumerable<T> entities, bool persist = true)
{
Table.AddRange(entities);
return persist ? SaveChanges() : 0;
}
public virtual int Update(T entity, bool persist = true)
{
Table.Update(entity);
return persist ? SaveChanges() : 0;
}
public virtual int UpdateRange(IEnumerable<T> entities, bool persist = true)
{
Table.UpdateRange(entities);
return persist ? SaveChanges() : 0;
}
public virtual int Delete(T entity, bool persist = true)
{
Table.Remove(entity);
return persist ? SaveChanges() : 0;
}
public virtual int DeleteRange(IEnumerable<T> entities, bool persist = true)
{
Table.RemoveRange(entities);
return persist ? SaveChanges() : 0;
}
Есть еще один метод удаления, который не следует этому шаблону. Для выдачи операции удаления он использует EntityState, что часто происходит при работе с ASP.NET Core с целью сокращения сетевого трафика:
public int Delete(int id, byte[] timeStamp, bool persist = true)
{
var entity = new T {Id = id, TimeStamp = timeStamp};
Context.Entry(entity).State = EntityState.Deleted;
return persist ? SaveChanges() : 0;
}
Итак, класс BaseRepo завершен, и можно приступать к построению хранилищ, специфичных для сущностей.
Интерфейсы хранилищ, специфичных для сущностей
Каждая сущность будет иметь строго типизированное хранилище, производное от BaseRepo<T>, и интерфейс, который реализует IRepo<T>. Создайте в каталоге Repos проекта AutoLot.Dal новый каталог по имени Interfaces и добавьте в него пять файлов интерфейсов:
ICarRepo.cs
ICreditRiskRepo.cs
ICustomerRepo.cs
IMakelRepo.cs
IOrderRepo.cs
Содержимое интерфейсов будет представлено в последующих разделах.
Интерфейс хранилища данных об автомобилях
Откройте файл ICarRepo.cs и поместите в его начало такие операторы using:
using System.Collections.Generic;
using AutoLot.Models.Entities;
using AutoLot.Dal.Repos.Base;
Измените интерфейс на public и реализуйте IRepo<Car>, как показано ниже:
namespace AutoLot.Dal.Repos.Interfaces
{
public interface ICarRepo : IRepo<Car>
{
IEnumerable<Car> GetAllBy(int makeId);
string GetPetName(int id);
}
}
Интерфейс хранилища данных о кредитных рисках
Откройте файл ICreditRiskRepo.cs. Интерфейс ICreditRiskRep не добавляет никакой функциональности сверх той, что предоставляется в BaseRepo. Обновите код следующим образом:
using AutoLot.Models.Entities;
using AutoLot.Dal.Repos.Base;
namespace AutoLot.Dal.Repos.Interfaces
{