using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.Extensions.Logging; using PARR.Common.Domain; using PARR.DAL.Context; using PARR.DAL.DomainModels; using PARR.DAL.Models.Base; using PARR.DAL.Models.Base.History.Base; using PARR.DAL.Services.Interfaces.Base; using System.Reflection; namespace PARR.DAL.Services.Abstracts { internal abstract class BaseService : IBaseService where T : class, IBase { private readonly ILogger> logger; protected abstract DbSet EntitySet { get; } protected abstract DataContext EntitiContext { get; } public BaseService(ILogger> logger) { this.logger = logger; } public virtual async Task AddRangeAsync(List 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 CommitAsync(IHistoryInitiator? initiator = null) { #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); try { await EntitiContext.SaveChangesAsync(); return true; } catch (Exception ex) { logger.LogError(ex, "Ошибка при Commit"); return false; } } /// /// Установить инициатора /// /// 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; } } } /// /// Обновления DateModified у таблиц с IBase /// /// 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; } /// /// При необходимости, записывать историю таблиц /// /// 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}"); } } /// /// Заполнить объект истории /// /// /// /// private void FillHistoryProps(EntityEntry originalObj, ref object historyInstance, List 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); } } /// /// Заполнить поле объекта /// /// /// /// /// private void FillHistoryProp(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(propName); // var histProp = history.GetType().GetProperty(propName); // histProp.SetValue(histProp, originalPropValue); // } // EntitiContext.Add(history); //} #endregion public virtual async Task 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 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 Get() { return EntitySet; } public virtual async Task GetAsync(Guid id) { return await EntitySet.FirstOrDefaultAsync(t => t.Id == id); } public virtual IQueryable GetPage(IQueryable query, PaginationFilter paginationFilter) { //if (paginationFilter.IsDisabledPagination) // return query; int skip = (paginationFilter.PageNumber - 1) * paginationFilter.PageSize; return query.Skip(skip).Take(paginationFilter.PageSize); } } }