Files
parr_api/PARR.DAL/Services/Abstracts/BaseService.cs

347 lines
13 KiB
C#
Raw Normal View History

2023-05-17 16:30:13 +10:00
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
2023-05-17 16:30:13 +10:00
using Microsoft.Extensions.Logging;
using PARR.Common.Domain;
2023-05-17 16:30:13 +10:00
using PARR.DAL.Context;
2023-06-16 15:00:11 +10:00
using PARR.DAL.DomainModels;
2023-05-17 16:30:13 +10:00
using PARR.DAL.Models.Base;
using PARR.DAL.Models.Base.History.Base;
2023-05-17 16:30:13 +10:00
using PARR.DAL.Services.Interfaces.Base;
using System.Reflection;
2023-05-17 16:30:13 +10:00
namespace PARR.DAL.Services.Abstracts
{
internal abstract class BaseService<T> : IBaseService<T> where T : class, IBase
{
private readonly ILogger<BaseService<T>> logger;
protected abstract DbSet<T> EntitySet { get; }
protected abstract DataContext EntitiContext { get; }
public BaseService(ILogger<BaseService<T>> logger)
{
this.logger = logger;
}
public virtual async Task<bool> AddRangeAsync(List<T> objs)
{
objs.ForEach(item => item.DateCreated = DateTimeOffset.UtcNow);
try
{
await EntitySet.AddRangeAsync(objs);
return true;
}
catch (Exception ex)
{
logger.LogError(ex, "Ошибка при добавлении диапазона в БД.");
return false;
}
}
public async Task<bool> CommitAsync(IHistoryInitiator? initiator = null)
2023-05-17 16:30:13 +10:00
{
#region Изменения
var modifiedEntrities = EntitiContext.ChangeTracker.Entries()
.Where(t => t.State == EntityState.Modified/* || t.State == EntityState.Deleted*/);
foreach (var obj in modifiedEntrities)
{
// Если нунжо, обновляем дату изменения
DateModifiedResolver(obj);
//Если нужно, пишем историю
TableHistoryResolver(obj);
}
#endregion
SetInitiator(initiator);
2023-05-17 16:30:13 +10:00
try
{
await EntitiContext.SaveChangesAsync();
return true;
}
catch (Exception ex)
{
logger.LogError(ex, "Ошибка при Commit");
return false;
}
}
/// <summary>
/// Установить инициатора
/// </summary>
/// <param name="initiator"></param>
private void SetInitiator(IHistoryInitiator? initiator)
{
// Задаем инициатора только для новых и измененных записей
var entrities = EntitiContext.ChangeTracker.Entries()
.Where(t => t.State == EntityState.Modified || t.State == EntityState.Added);
// смотрим есть ли у объекта интерфейс 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;
}
}
}
/// <summary>
/// Обновления DateModified у таблиц с IBase
/// </summary>
/// <param name="obj"></param>
private void DateModifiedResolver(EntityEntry obj)
{
//if (obj.Entity is IBase)
// (obj.Entity as IBase)!.DateModified = DateTimeOffset.UtcNow;
if (obj.Entity is IBaseDateModified)
(obj.Entity as IBaseDateModified)!.DateModified = DateTimeOffset.UtcNow;
}
/// <summary>
/// При необходимости, записывать историю таблиц
/// </summary>
/// <param name="obj"></param>
private void TableHistoryResolver(EntityEntry obj)
{
var myHistoryInterface = obj.Entity.GetType().GetInterfaces()
.Where(t => t.IsGenericType)
.Where(t => t.GetGenericTypeDefinition() == typeof(IMyHistory<>))
.FirstOrDefault();
// у этого объекта нет интерфейса IMyHistory<>. Не ведем историю
if (myHistoryInterface == null)
return;
// !!! Эта таблица хочет хранить историю !!!
// Получаем тип таблицы где хранится история
var historyType = myHistoryInterface.GetGenericArguments().First();
var historyProps = historyType.GetProperties(/*BindingFlags.DeclaredOnly | */ /*BindingFlags.Public*/).ToList();
var historyInstance = Activator.CreateInstance(historyType);
if (historyInstance == null)
{
logger.LogError($"Не смог создать инстанс для ведения истории {historyType.Name}");
return;
}
// Заполняем поля истории, полями которые есть в исходной таблице
FillHistoryProps(obj, ref historyInstance, historyProps);
// Заполняем поля интерфейса IHistoryTable
FillHistoryProp(ref historyInstance, nameof(IHistoryTable.DateAddedToHistory), DateTimeOffset.UtcNow);
// Заполняем Id
FillHistoryProp(ref historyInstance, nameof(IBase.Id), Guid.NewGuid());
try
{
EntitiContext.Add(historyInstance);
}
catch (Exception ex)
{
logger.LogError(ex, $"Ошибка при добавлении объекта в историю {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)
{
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(IBase.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);
}
}
/// <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($"При изменении объекта для БД, не найдено свойство ${propName}");
return;
}
// сравним типы
if (histProp.PropertyType != typeof(TValue))
{
logger.LogError($"При изменении объекта для БД, не совпадают типы у свойства {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
2023-05-17 16:30:13 +10:00
public virtual async Task<bool> CreateAsync(T obj)
{
obj.DateCreated = DateTimeOffset.UtcNow;
try
{
await EntitySet.AddAsync(obj);
return true;
}
catch (Exception ex)
{
logger.LogError(ex, "Ошибка при добавлении в БД.");
return false;
}
}
public virtual bool Delete(T obj)
{
try
{
EntitySet.Remove(obj);
return true;
}
catch (Exception ex)
{
logger.LogError(ex, "Ошибка при удалении из БД.");
return false;
}
}
public virtual async Task<bool> DeleteAsync(Guid id)
{
try
{
var exist = await GetAsync(id);
if (exist == null)
{
logger.LogError($"Ошибка при удалении из БД. Не найдена запись в БД с id: {id}");
return false;
}
EntitySet.Remove(exist);
return true;
}
catch (Exception ex)
{
logger.LogError(ex, "Ошибка при удалении из БД.");
return false;
}
}
public virtual IQueryable<T> Get()
{
return EntitySet;
}
public virtual async Task<T?> GetAsync(Guid id)
{
return await EntitySet.FirstOrDefaultAsync(t => t.Id == id);
}
2023-06-16 15:00:11 +10:00
public virtual IQueryable<T> GetPage(IQueryable<T> query, PaginationFilter paginationFilter)
{
//if (paginationFilter.IsDisabledPagination)
// return query;
2023-06-16 15:00:11 +10:00
int skip = (paginationFilter.PageNumber - 1) * paginationFilter.PageSize;
2023-05-17 16:30:13 +10:00
2023-06-16 15:00:11 +10:00
return query.Skip(skip).Take(paginationFilter.PageSize);
}
2023-05-17 16:30:13 +10:00
}
}