Files
parr_api/PARR.DAL/Repositories/Base/BaseRepository.cs

425 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.Extensions.Logging;
using PARR.Core.Repositories.Base;
using PARR.DAL.Context;
using PARR.Domain.Common.Pagination;
using PARR.Domain.Entities.Attributes;
using PARR.Domain.Entities.Base;
using PARR.Domain.Entities.Base.History;
using PARR.Domain.Entities.Base.History.Base;
using System.Reflection;
namespace PARR.DAL.Repositories.Base
{
internal abstract class BaseRepository<T> : IBaseRepository<T> where T : class, IBaseEntity
{
//private readonly ILogger<BaseRepository<T>> logger;
//protected abstract DbSet<T> EntitySet { get; }
//protected abstract DataContext EntitiContext { get; }
//public BaseRepository(ILogger<BaseRepository<T>> logger)
//{
// this.logger = logger;
//}
protected readonly ILogger logger;
protected readonly DbSet<T> EntitySet;
protected readonly DataContext EntityContext;
protected BaseRepository(ILogger logger, DataContext dataContext)
{
this.logger = logger;
this.EntityContext = dataContext;
this.EntitySet = dataContext.Set<T>();
}
public virtual async Task<bool> AddRangeAsync(List<T> objs)
{
logger.LogDebug("Начинаю добавление диапазона объектов типа {EntityType}, количество: {Count}",
typeof(T).Name, objs.Count);
objs.ForEach(item => item.DateCreated = DateTimeOffset.UtcNow);
try
{
await EntitySet.AddRangeAsync(objs);
logger.LogDebug("Успешно добавлено {Count} объектов типа {EntityType}",
objs.Count, typeof(T).Name);
return true;
}
catch (Exception ex)
{
logger.LogError(ex, "Ошибка при добавлении диапазона объектов типа {EntityType}", typeof(T).Name);
return false;
}
}
public async Task<bool> CommitAsync(IHistoryInitiator? initiator = null)
{
logger.LogDebug("Начинаю сохранение изменений в БД для объектов типа {EntityType}", typeof(T).Name);
#region Изменения
var modifiedEntrities = EntityContext.ChangeTracker.Entries()
.Where(t => t.State == EntityState.Modified/* || t.State == EntityState.Deleted*/);
logger.LogDebug("Найдено {Count} измененных сущностей для обработки истории", modifiedEntrities.Count());
foreach (var obj in modifiedEntrities)
{
// Если нужно, обновляем дату изменения
DateModifiedResolver(obj);
//Если нужно, пишем историю
TableHistoryResolver(obj);
}
#endregion
SetInitiator(initiator);
try
{
var changedCount = await EntityContext.SaveChangesAsync();
logger.LogDebug("Успешно сохранено {ChangedCount} изменений в БД для объектов типа {EntityType}",
changedCount, typeof(T).Name);
return true;
}
catch (Exception ex)
{
logger.LogError(ex, "Ошибка при сохранении изменений в БД для объектов типа {EntityType}", typeof(T).Name);
return false;
}
}
/// <summary>
/// Установить инициатора
/// </summary>
/// <param name="initiator"></param>
private void SetInitiator(IHistoryInitiator? initiator)
{
if (initiator == null)
return;
logger.LogDebug("Устанавливаю инициатора для изменений");
// Задаем инициатора только для новых и измененных записей
var entrities = EntityContext.ChangeTracker.Entries()
.Where(t => t.State == EntityState.Modified || t.State == EntityState.Added);
var entityCount = entrities.Count();
logger.LogDebug("Найдено {Count} сущностей для установки инициатора", entityCount);
// смотрим есть ли у объекта интерфейс IHistoryInitiator, если есть, задаём значения
foreach (var obj in entrities)
{
// для всех объектов которые наследуются от IHistoryInitiator и не являются наследниками IHistoryTable
if (obj.Entity is IHistoryInitiator && obj.Entity is IHistoryTable == false)
{
(obj.Entity as IHistoryInitiator)!.InitiatorIp = initiator?.InitiatorIp ?? null;
(obj.Entity as IHistoryInitiator)!.InitiatorParrComponentId = initiator?.InitiatorParrComponentId ?? null;
(obj.Entity as IHistoryInitiator)!.InitiatorComment = initiator?.InitiatorComment ?? null;
logger.LogDebug("Установлен инициатор для сущности типа {EntityType}", obj.Entity.GetType().Name);
}
}
}
/// <summary>
/// Обновления DateModified у таблиц с IBase
/// </summary>
/// <param name="obj"></param>
private void DateModifiedResolver(EntityEntry obj)
{
if (obj.Entity is IBaseEntityDateModified entity)
{
//logger.LogDebug("Обновляю DateModified для сущности типа {EntityType}", obj.Entity.GetType().Name);
//(obj.Entity as IBaseEntityDateModified)!.DateModified = DateTimeOffset.UtcNow;
var entityType = entity.GetType();
//Ищем DateModified
var properyInfo = entityType.GetProperty(nameof(IBaseEntityDateModified.DateModified));
// Проверяем наличие атрибута ManualControlAttribute на этом свойстве
bool isManual = properyInfo?.GetCustomAttributes(typeof(ManualControlAttribute), false).Any() ?? false;
if (!isManual)
{
logger.LogDebug("Обновляю DateModified для сущности типа {EntityType}", obj.Entity.GetType().Name);
entity.DateModified = DateTimeOffset.UtcNow;
}
else
{
logger.LogDebug("Пропуск обновления DateModified (ManualControl) для {EntityType}", entityType.Name);
}
}
}
/// <summary>
/// При необходимости, записывать историю таблиц
/// </summary>
/// <param name="obj"></param>
private void TableHistoryResolver(EntityEntry obj)
{
logger.LogDebug("Проверяю необходимость создания истории для сущности типа {EntityType}", obj.Entity.GetType().Name);
var myHistoryInterface = obj.Entity.GetType().GetInterfaces()
.Where(t => t.IsGenericType)
.Where(t => t.GetGenericTypeDefinition() == typeof(IMyHistory<>))
.FirstOrDefault();
// у этого объекта нет интерфейса IMyHistory<>. Не ведем историю
if (myHistoryInterface == null)
{
logger.LogDebug("Сущность типа {EntityType} не требует ведения истории", obj.Entity.GetType().Name);
return;
}
// !!! Эта таблица хочет хранить историю !!!
// Получаем тип таблицы где хранится история
var historyType = myHistoryInterface.GetGenericArguments().First();
var historyProps = historyType.GetProperties(/*BindingFlags.DeclaredOnly | */ /*BindingFlags.Public*/).ToList();
logger.LogDebug("Создаю историю для сущности типа {EntityType}, тип истории: {HistoryType}",
obj.Entity.GetType().Name, historyType.Name);
var historyInstance = Activator.CreateInstance(historyType);
if (historyInstance == null)
{
logger.LogError("Не смог создать инстанс для ведения истории {HistoryType}", historyType.Name);
return;
}
// Заполняем поля истории, полями которые есть в исходной таблице
FillHistoryProps(obj, ref historyInstance, historyProps);
// Заполняем поля интерфейса IHistoryTable
FillHistoryProp(ref historyInstance, nameof(IHistoryTable.DateAddedToHistory), DateTimeOffset.UtcNow);
// Заполняем Id
FillHistoryProp(ref historyInstance, nameof(IBaseEntity.Id), Guid.NewGuid());
try
{
EntityContext.Add(historyInstance);
logger.LogDebug("История добавлена для сущности типа {EntityType}", obj.Entity.GetType().Name);
}
catch (Exception ex)
{
logger.LogError(ex, "Ошибка при добавлении объекта в историю {HistoryType}", historyType.Name);
}
}
/// <summary>
/// Заполнить объект истории
/// </summary>
/// <param name="originalObj"></param>
/// <param name="historyInstance"></param>
/// <param name="propsList"></param>
private void FillHistoryProps(EntityEntry originalObj, ref object historyInstance, List<PropertyInfo> propsList)
{
logger.LogDebug("Заполняю историю для сущности типа {EntityType}", originalObj.Entity.GetType().Name);
foreach (var prop in propsList)
{
if (originalObj.OriginalValues.Properties.FirstOrDefault(t => t.Name == prop.Name) == null)
continue;
var sourceObjProp = originalObj.Property(prop.Name);
if (sourceObjProp == null)
return;
//Смотрим, если это Id, записываем его в ParentId
var histPropName = prop.Name == nameof(IBaseEntity.Id) ? nameof(IHistoryTable.ParentId) : prop.Name;
var histProp = historyInstance.GetType().GetProperty(histPropName);
if (histProp == null) continue;
//сравним типы
if (sourceObjProp.Metadata.PropertyInfo?.PropertyType != histProp.PropertyType)
continue;
var origValues = originalObj.Property(prop.Name).OriginalValue;
histProp.SetValue(historyInstance, origValues);
}
logger.LogDebug("Завершено заполнение истории для сущности типа {EntityType}", originalObj.Entity.GetType().Name);
}
/// <summary>
/// Заполнить поле объекта
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="instanceObj"></param>
/// <param name="propName"></param>
/// <param name="value"></param>
private void FillHistoryProp<TValue>(ref object instanceObj, string propName, TValue value)
{
var histProp = instanceObj.GetType().GetProperty(propName);
if (histProp == null)
{
logger.LogError("При изменении объекта для БД, не найдено свойство {PropertyName}", propName);
return;
}
// сравним типы
if (histProp.PropertyType != typeof(TValue))
{
logger.LogError("При изменении объекта для БД, не совпадают типы у свойства {PropertyName}, {PropertyType}!={ValueType}",
propName, histProp.PropertyType.Name, typeof(TValue).Name);
return;
}
histProp.SetValue(instanceObj, value);
}
#region test, comments
//private void SaveHistory(EntityEntry obj)
//{
// // пока просто попробуем для шаблонов
// if (obj.Entity is Template == false)
// return;
// var template = (Template)obj.Entity;
// // https://stackoverflow.com/questions/15012621/how-to-get-original-entity-from-changetracker
// var history = new TemplateHistory();
// // {
// history.Id = Guid.NewGuid();
// history.ParentId = template.Id;
// history.DateAddedToHistory = DateTimeOffset.UtcNow;
// history.InitiatorIp = null;
// history.InitiatorParrComponentId = null;
// history.InitiatorComment = null;
// //---
// //DateModified="",
// //Name ="",
// //IsActiveTemplate = null,
// //IsActiveSchedule = null,
// //LastRun = null,
// //NextRun = null
// // DateModified = template.DateModified,
// // Name = template.Name,
// // IsActiveTemplate = template.IsActiveTemplate,
// // IsActiveSchedule = template.IsActiveSchedule,
// // LastRun = template.LastRun,
// // NextRun = template.NextRun
// //};
// // obj.OriginalValues.GetValue
// //ITemplateBase хочу получить все поля из интерфейса, потом заполнить по ним в хистори
// var interfaceType = typeof(ITemplateGeneralProps);
// foreach (var prop in interfaceType.GetProperties())
// {
// var propName = prop.Name;
// //var propType = prop.PropertyType;
// var originalPropValue = obj.OriginalValues.GetValue<object>(propName);
// var histProp = history.GetType().GetProperty(propName);
// histProp.SetValue(histProp, originalPropValue);
// }
// EntitiContext.Add(history);
//}
#endregion
public virtual async Task<bool> CreateAsync(T obj)
{
logger.LogDebug("Начинаю создание объекта типа {EntityType}", typeof(T).Name);
if (obj.DateCreated == DateTimeOffset.MinValue)
obj.DateCreated = DateTimeOffset.UtcNow;
try
{
await EntitySet.AddAsync(obj);
logger.LogDebug("Объект типа {EntityType} добавлен в контекст", typeof(T).Name);
return true;
}
catch (Exception ex)
{
logger.LogError(ex, "Ошибка при добавлении объекта типа {EntityType} в БД", typeof(T).Name);
return false;
}
}
public virtual bool Delete(T obj)
{
logger.LogDebug("Начинаю удаление объекта типа {EntityType}", obj.GetType().Name);
try
{
EntitySet.Remove(obj);
logger.LogDebug("Объект типа {EntityType} удален из контекста", obj.GetType().Name);
return true;
}
catch (Exception ex)
{
logger.LogError(ex, "Ошибка при удалении объекта типа {EntityType} из БД", obj.GetType().Name);
return false;
}
}
public virtual async Task<bool> DeleteAsync(Guid id)
{
logger.LogDebug("Начинаю удаление объекта типа {EntityType} по ID: {Id}", typeof(T).Name, id);
try
{
var exist = await GetAsync(id);
if (exist == null)
{
logger.LogError("Ошибка при удалении из БД. Не найдена запись в БД типа {EntityType} с id: {Id}",
typeof(T).Name, id);
return false;
}
EntitySet.Remove(exist);
logger.LogDebug("Объект типа {EntityType} с ID {Id} удален из контекста", typeof(T).Name, id);
return true;
}
catch (Exception ex)
{
logger.LogError(ex, "Ошибка при удалении объекта типа {EntityType} из БД по ID: {Id}", typeof(T).Name, id);
return false;
}
}
public virtual IQueryable<T> Get()
{
logger.LogDebug("Получаю набор объектов типа {EntityType}", typeof(T).Name);
return EntitySet;
}
public virtual async Task<T?> GetAsync(Guid id)
{
logger.LogDebug("Получаю объект типа {EntityType} по ID: {Id}", typeof(T).Name, id);
return await EntitySet.FirstOrDefaultAsync(t => t.Id == id);
}
public virtual IQueryable<T> GetPage(IQueryable<T> query, PaginationFilter paginationFilter)
{
logger.LogDebug("Получаю страницу объектов типа {EntityType}, страница: {PageNumber}, размер: {PageSize}",
typeof(T).Name, paginationFilter.PageNumber, paginationFilter.PageSize);
int skip = (paginationFilter.PageNumber - 1) * paginationFilter.PageSize;
return query.Skip(skip).Take(paginationFilter.PageSize);
}
}
}