.NET 5.0, ORACLE, ASP.NET Identity с N-уровневой архитектурой — часть 2

В этой части нашего руководства мы погрузимся в создание базовой структуры проекта для N-уровневого проекта, включая создание базового репозитория и создание уровня данных, создание сервисного уровня и использование всех вновь созданных материалов в нашем веб-проекте.
Прежде всего, в предыдущей части мы создали наш контекст с сущностями, объяснили, как создать миграцию (сначала код) с базой данных Oracle с использованием EFCore и как создать шаблоны существующих сущностей базы данных в нашем проекте (сначала база данных). Теперь мы применим все это через пару шаблонов проектирования на нескольких уровнях.

Уровень логики данных и шаблон репозитория

Первый шаблон, который мы реализуем, — это шаблон репозитория на нашем уровне доступа к данным. Наш код создаст общий IBaseRepository с CRUD операции, содержащие ВСТАВИТЬ, ОБНОВИТЬ, УДАЛИТЬ и ВЫБРАТЬ операции, а позже мы собираемся добавить реализацию IBaseRepository.

Entity Framework уже реализует шаблон репозитория, поскольку мы можем получать доступ, добавлять, изменять и удалять сущности через DbSet. Мы создаем общий интерфейс IBaseRepository и реализуем этот репозиторий, чтобы избежать шаблонного кода для операций CRUD каждого репозитория для каждой сущности.

Теперь давайте создадим общий интерфейс IBaseRepository с базовой операцией CRUD.
Добавьте класс интерфейса IBaseRepository в папку Repositores/Abstract проекта DLL:

public interface IBaseRepository<T> where T : class
{
  #region Select methods
  Task<T> SelectById(int id);
  Task<List<T>> SelectAll();
  #endregion
  #region Insert methods
  Task<T> Insert(T entity);
  #endregion
  #region Update methods
   void Update(T entity);
  #endregion
  #region Delete methods
  void Delete(int Id);
  #endregion
  #region Other methods
  void SaveChanges();
  #endregion
}

IBaseRepository интерфейс принимает один общий тип T. T, как определено, должен быть классом, поэтому он должен быть сущностью, такой же, как и одна из нашей папки Entities в проекте DLL. Как объяснялось ранее, наш общий интерфейс IBaseRepository определяет базовые операции CRUD, которые мы реализуем позже в нашей реализации BaseRepository. Давайте создадим реализацию нашего универсального IBaseRepository.
Добавьте реализацию BaseRepository в папку Repositores/Implementation проекта DLL:

public class BaseRepository<T> : IBaseRepository<T> where T : class
{
  #region Fields
  protected readonly DbContext _context;
  protected readonly DbSet<T> dbSet;
  #endregion
  #region Constructor
  protected BaseRepository(DbContext context)
  {
    _context = context;
    dbSet = context.Set<T>();
  }
  #endregion
  #region Select methods
  public virtual async Task<T> SelectById(int id)
  {
    return await dbSet.FindAsync(id);
  }
  public virtual async Task<List<T>> SelectAll()
  {
    return await dbSet.ToListAsync();
  }
  #endregion
  #region Insert methods
  public virtual async Task<T> Insert(T entity)
  {
   var addedEntity = (await dbSet.AddAsync(entity)).Entity;
   await _context.SaveChangesAsync();
   return addedEntity;
  }
  #endregion
  #region Update methods
  public virtual async void Update(T entity)
  {
   dbSet.Update(entity);
   await _context.SaveChangesAsync();
  }
  #endregion
  #region Delete methods
  public async void Delete(int Id)
  {
   T entity = dbSet.Find(Id);
   var removedEntity = dbSet.Remove(entity).Entity;
   await _context.SaveChangesAsync();
  }
  #endregion
  #region Other methods
  public void SaveChanges()
  {
    _context.SaveChanges();
  }
  #endregion
}

Структура вашего проекта должна выглядеть так.
1_-K38JXeQ4YxxzIMVtrz9kQ.png

Теперь следующим шагом для нас будет реализация репозитория сущностей для наших сущностей базы данных. В предыдущей главе мы создали две сущности: Log и ExampleTable. Давайте создадим интерфейсы для наших репозиториев сущностей и реализации этих репозиториев. Сначала добавьте интерфейс ILogRepository в папку Repositores/Abstract проекта DLL:

public interface ILogRepository : IBaseRepository<Log>, IDisposable
{
 //Additional methods or override ones from BaseRepository
 #region Select methods
 #endregion
 #region Insert methods
 #endregion
 #region Update methods
 #endregion
 #region Delete methods
 #endregion
 #region Other methods
 #endregion
}

Затем добавьте реализацию LogRepository в папку Repositores/Implementation проекта DLL:

public class LogRepository : BaseRepository<Log>, ILogRepository
{
 //Implement additional methods or override ones from BaseRepository and implement them
 #region Select methods
 public LogRepository(EXAMPLE_SCHEMA_Context context) : base(context)
 { }
 #endregion
#region Select methods
#endregion
#region Insert methods
#endregion
#region Update methods
#endregion
#region Delete methods
#endregion
#region Other methods
public void Dispose()
 {
 _context.Dispose();
 }
#endregion
}

Теперь давайте сделаем то же самое для нашей второй сущности ExampleTable. Сначала добавьте интерфейс IExampleTableRepository в папку Repositores/Abstract проекта DLL:

public interface IExampleTableRepository : IBaseRepository<ExampleTable>, IDisposable
{
 //Additional methods or override ones from BaseRepository
 #region Select methods
 #endregion
 #region Insert methods
 #endregion
 #region Update methods
 #endregion
 #region Delete methods
 #endregion
 #region Other methods
 #endregion
}

Затем добавьте реализацию ExampleTableRepository в папку Repositores/Implementation проекта DLL:

public class ExampleTableRepository : BaseRepository<ExampleTable>, IExampleTableRepository
{
 //Implement additional methods or override ones from BaseRepository and implement them
 #region Select methods
 public ExampleTableRepository(EXAMPLE_SCHEMA_Context context) :  base(context)
 { }
 #endregion
 #region Select methods
 #endregion
 #region Insert methods
 #endregion
 #region Update methods
 #endregion
 #region Delete methods
 #endregion
 #region Other methods
 public void Dispose()
 {
   _context.Dispose();
 }
 #endregion
}

Как мы видим, мы расширяем BaseRepository в наших репозиториях сущностей. В ILogRepository или любом другом интерфейсе мы можем добавить дополнительные методы, применимые только к этому объекту. Вот почему мы расширяем LogRepository как BaseRepository и реализуем ILogRepository. BaseRepository содержит основные операции CRUD, а ILogRepository содержит методы, процедуры и другой код LINQ для конкретных объектов.

Структура вашего проекта должна выглядеть так.
1_jak9gYGZdM2-GePR8CjjeQ.png

Слой бизнес-логики и службы

Теперь, когда мы реализовали репозитории в нашем проекте DLL, пришло время перейти к проекту слоя бизнес-логики, чтобы реализовать наши службы, которые будут реализовывать шаблон Unit of Work. Шаблон единицы работы отслеживает бизнес-транзакцию и переводит эту транзакцию в транзакцию базы данных. Например, если у нас есть десять шагов для нашей бизнес-транзакции, и эти шаги каким-то образом связаны с бизнес-логикой, мы можем коллективно выполнить эту транзакцию как единое целое.
Реализация шаблона Unit of Work должна выглядеть так.
1_CtbFSP_5iZ234f1USahz4A.png

Итак, давайте создадим ILogService, IExampleTableServices интерфейсы и ЛогСервис, ПримерТаблСервис как реализация этих интерфейсов.

public interface ILogService
{
 #region Select methods
 Task<List<Log>> SelectLog();
 Task<Log> SelectLogById(int Id);
 #endregion
 #region Insert methods
 void InsertLog(Log entity);
 #endregion
 #region Update methods
 void UpdateLog(Log entity);
 #endregion
 #region Delete methods
 void DeleteLog(int Id);
 #endregion
}
public class LogService : ILogService
{
 //Service can have multiple repositories implementing UoW(Unit oof Work) design pattern.
 #region Fields
 private readonly ILogRepository _logRepository;
 #endregion
 #region Constructor
 public LogService(ILogRepository logRepository)
 {
  _logRepository = logRepository;
 }
 #endregion
 #region Select methods
 public Task<List<Log>> SelectLog()
 {
   return _logRepository.SelectAll();
 }
 public Task<Log> SelectLogById(int Id)
 {
  return _logRepository.SelectById(Id);
 }
 #endregion
 #region Insert methods
 public void InsertLog(Log entity)
 {
   _logRepository.Insert(entity);
 }
 #endregion
 #region Update methods
 public void UpdateLog(Log entity)
 {
   _logRepository.Update(entity);
 }
 #endregion
 #region Delete methods
 public void DeleteLog(int Id)
 {
   _logRepository.Delete(Id);
 }
 #endregion
 #region Other methods
 #endregion
}
public interface IExampleTableService
{
 #region Select methods
 Task<List<ExampleTable>> SelectExampleTable();
 Task<ExampleTable> SelectExampleTableById(int Id);
 #endregion
 #region Insert methods
 void InsertExampleTable(ExampleTable entity);
 #endregion
 #region Update methods
 void UpdateExampleTable(ExampleTable entity);
 #endregion
 #region Delete methods
 void DeleteExampleTable(int Id);
 #endregion
}
public class ExampleTableService : IExampleTableService
{
 //Service can have multiple repositories implementing UoW(Unit oof Work) design pattern.
 #region Fields
 private readonly IExampleTableRepository _exampleTableRepository;
 #endregion
 #region Constructor
 public ExampleTableService(IExampleTableRepository ExampleTableRepository)
 {
   _exampleTableRepository = ExampleTableRepository;
 }
 #endregion
 #region Select methods
 public Task<List<ExampleTable>> SelectExampleTable()
 {
   return _exampleTableRepository.SelectAll();
 }
 public Task<ExampleTable> SelectExampleTableById(int Id)
 {
   return _exampleTableRepository.SelectById(Id);
 }
 #endregion
 #region Insert methods
 public void InsertExampleTable(ExampleTable entity)
 {
   _exampleTableRepository.Insert(entity);
 }
 #endregion
 #region Update methods
 public void UpdateExampleTable(ExampleTable entity)
 {
   _exampleTableRepository.Update(entity);
 }
 #endregion
 #region Delete methods
 public void DeleteExampleTable(int Id)
 {
   _exampleTableRepository.Delete(Id);
 }
 #endregion
 #region Other methods
 #endregion
}

Структура нашего проекта BLL должна выглядеть так:
1_TMOErbMvMiJEdo4-ym0vFg.png

Вот и все. Единица работы состоит из нескольких репозиториев, которые реализуют некоторую бизнес-логику, объединяя их все в один сервис.

Внедрение зависимости

Итак, каков наш следующий шаг? Далее мы собираемся использовать наши репозитории и сервисы в наших контроллерах. Мы собираемся внедрить репозитории в наши сервисы, а затем эти сервисы в наши контроллеры, что является методом достижения инверсии управления (IoC) между классами и их зависимостями. Существует несколько библиотек, которые позволяют нам внедрять необходимые зависимости класса, но мы будем использовать стандартную платформу ASP.NET CORE DI для внедрения наших зависимостей.

Прежде чем мы углубимся в код, давайте объясним 3 режима жизни внедряемой службы:
Область применения — пожизненные службы создаются один раз для каждого запроса в рамках области.
Переходный — пожизненные сервисы создаются каждый раз, когда они запрашиваются.
Синглтон — который создает один экземпляр во всем приложении. Он создает экземпляр в первый раз и повторно использует один и тот же объект во всех вызовах.

Давайте откроем класс Startup.cs и в методе ConfigureServices внедрим наш DBContext и необходимые службы и репозитории.

public void ConfigureServices(IServiceCollection services)
{
 services.AddControllersWithViews();
 //Dependency injection   services.AddEntityFrameworkOracle().AddDbContext<EXAMPLE_SCHEMA_Cont ext>();  services.AddScoped(typeof(IBaseRepository<>),typeof(BaseRepository<> ));  services.AddScoped(typeof(ILogRepository),typeof(LogRepository));  services.AddScoped(typeof(IExampleTableRepository),typeof(ExampleTab leRepository));
}

Сопоставление AutoMapper между сущностями DLL и ViewModels

Далее, проблема, которую мы собираемся решить, — это проблема сопоставления наших сущностей DLL, которые генерируются либо с помощью кода, либо базы данных. Каждый раз, когда нам нужно изменить нашу сущность, мы теряем DataAnotations, и нам нужно снова применить это изменение, или нам нужно добавить новые свойства для каждой сущности, в которую мы копируем данные, и изменить нашу логику для копирования одного объекта в другой в нашем коде (Entity в EntityViewModel и наоборот).

AutoMapper в C# — это средство сопоставления двух объектов. Он сопоставляет свойства двух разных объектов, преобразуя входной объект одного типа в выходной объект другого типа.

Давайте сначала установим AutoMapper либо через графический интерфейс в нашем веб-проекте, щелкнув правой кнопкой мыши зависимости веб-проекта, а затем управляя пакетами NuGet.
1_Qti0z5kwFfw9mfrEcEj-AQ.png

или выполнив следующие команды консоли PM в веб-проекте:

  • Пакет установки AutoMapper — версия 11.0.0
  • Install-Package AutoMapper.Extensions.Microsoft.DependencyInjection — версия 11.0.0

Затем в нашем классе Startup.cs в методе ConfigureServices добавьте следующий код:

public void ConfigureServices(IServiceCollection services)
{
 …
  //AutoMapper setup
  services.AddAutoMapper(typeof(Startup));
 …
 
 …
  //Dependency injection
  services.AddSingleton(provider => new MapperConfiguration(cfg =>
  {
    cfg.AddProfile(new EXAMPLE_SCHEMA_Profile());
  }).CreateMapper());
 …
}

Далее давайте создадим ViewModel для наших классов сущностей Log и ExampleTable:

public class LogViewModel
{
 public int pk { get; set; }
 public string CreatedBy { get; set; }
 public DateTime CreationDate { get; set; }
 public string ModifiedBy { get; set; }
 public DateTime ModifiedDate { get; set; }
 public DateTime? Date { get; set; }
 public string Value { get; set; }
}
public class ExampleTableViewModel
{
 public int Pk { get; set; }
 public string CreatedBy { get; set; }
 public DateTime CreationDate { get; set; }
 public string ModifiedBy { get; set; }
 public DateTime ModifiedDate { get; set; }
 public string Name { get; set; }
}

Далее давайте создадим AutoMapperConfigurations папка в проекте NTierOracleIdentityExample.Web. Затем создайте новый профиль с именем EXAMPLE_SCHEMA_Профиль класс во вновь созданной папке:

public class EXAMPLE_SCHEMA_Profile : Profile
{
 public EXAMPLE_SCHEMA_Profile()
 {
   CreateMap<Log, LogViewModel>().ReverseMap();
   CreateMap<ExampleTable, ExampleTableViewModel>().ReverseMap();
 }
}

Как видно из предыдущего кода, нам нужно сопоставить каждое свойство класса сущности с соответствующим классом ViewModel с помощью AutoMapper.

Примечание. Если имена свойств различаются в исходном и целевом типах, то по умолчанию C# Automapper не сопоставляет эти свойства.

Структура нашего веб-проекта должна выглядеть так:
1_piQUnNi4DzEhORT_4UeyVQ.png

Завершение контроллера

Наконец, давайте добавим код в наш HomeController. Мы внедрим интерфейсы ILogService и IMapper в конструктор HomeController. Далее мы добавим несколько фиктивных методов для операций CRUD нашего сервиса, таких как GetLog, GetLogById, EditLog и DeleteLog.

public class HomeController : Controller
{
 #region Fields
 private readonly IMapper _mapper;
 private readonly ILogService _logService;
 #endregion
 #region Constructor
 public HomeController(ILogService logService, IMapper mapper)
 {
   _logService = logService;
   _mapper = mapper;
 }
 #endregion
 #region GET methods
 public IActionResult Index()
 {
   return View();
 }
 public JsonResult GetLog()
 {
   List<LogViewModel> logs = _mapper.Map<List<Log>,            List<LogViewModel>>(_logService.SelectLog().Result);
 
 var logList = from l in logs
 select new
 {
   Id = l.pk,
   LogDate = l.Date,
   LogValue = l.Value
 };
  return Json(new { status = “success”, records = logList.OrderBy(l => l.Id).ToList(), total = logList.Count() });
 }
 public JsonResult GetLogById(int pk)
 {
 LogViewModel log = _mapper.Map<Log, LogViewModel>(_logService.SelectLogById(pk).Result);
  return Json(new { status = “success”, records = log });
 }
 #endregion
#region POST methods
 [HttpPost]
 public JsonResult EditLog(LogViewModel viewModel)
 {
  if (ModelState.IsValid)
  {
   //Map viewModel to model
   Log model = _mapper.Map<Log>(viewModel);
   model.ModifiedBy = “DummyUser”;
   model.ModifiedDate = DateTime.Now;
   _logService.UpdateLog(model);
  return Json(new
  {
   success = true,
   message = “Log saved!”
  });
  }
  else
  {
   return Json(new { success = false, message = “Error!” });
  }
 }
[HttpPost]
 public JsonResult DeleteLog(int pk)
 {
 _logService.DeleteLog(pk);
 return Json(new { success = false, message = “Log deleted!” });
 }
#endregion
#region Helper methods
#endregion
}

Итак, это все для этой части. В следующей части мы рассмотрим удостоверение ASP.NET с базой данных ORACLE, а также способы создания необходимых сущностей в базе данных для удостоверения и способы его реализации в нашем приложении. И, наконец, запустите наше приложение.

Похожие записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *