using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.Extensions.Logging; using PARR.DAL.Context; using PARR.DAL.DomainModels; using PARR.DAL.Models; using PARR.DAL.Models.Base; using PARR.DAL.Models.Base.History; 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() { var modifiedEntrities = EntitiContext.ChangeTracker.Entries() .Where(t => t.State == EntityState.Modified/* || t.State == EntityState.Deleted*/); foreach (var obj in modifiedEntrities) { // Если нунжо, обновляем дату изменения DateModifiedResolver(obj); //Если нужно, пишем историю TableHistoryResolver(obj); } try { await EntitiContext.SaveChangesAsync(); return true; } catch (Exception ex) { logger.LogError(ex, "Ошибка при Commit"); return false; } } /// /// Обновления DateModified у таблиц с IBase /// /// private void DateModifiedResolver(EntityEntry obj) { if (obj.Entity is IBase) (obj.Entity as IBase)!.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 // Заполняем Id // EntitiContext.Add(historyInstance); } /// /// Заполнить объект истории /// /// /// /// 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 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); //} 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); } } }