using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using MP.TaskMan.Models; using NLog; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using static MP.TaskMan.Objects.Enums; namespace MP.TaskMan.Controllers { public class MpTaskController : IDisposable { #region Public Constructors public MpTaskController(IConfiguration configuration) { _configuration = configuration; Log.Info("Avviato MpTaskController"); } #endregion Public Constructors #region Public Methods public DateTime CalcNextExe(TaskListModel taskRec) { DateTime dtNext = DateTime.Today; DateTime adesso = DateTime.Now; int maxIter = 1000; try { // prendo come partenza la DATA di fine a cui aggiungo l'ora/minuti schedulata precedentemente TimeSpan oraSched = taskRec.DtNextExec.Subtract(taskRec.DtNextExec.Date); DateTime baseDT = taskRec.DtLastExec.Date.Add(oraSched); // calcolo next exec da tipo... con ciclo fino a quando supero dataora attuale... while (dtNext < adesso && maxIter > 0) { switch (taskRec.Freq) { case TaskFreqType.ND: dtNext = baseDT.AddDays(taskRec.Cad); break; case TaskFreqType.Sec: dtNext = baseDT.AddSeconds(taskRec.Cad); break; case TaskFreqType.Min: dtNext = baseDT.AddMinutes(taskRec.Cad); break; case TaskFreqType.Hour: dtNext = baseDT.AddHours(taskRec.Cad); break; case TaskFreqType.Day: dtNext = baseDT.AddDays(taskRec.Cad); break; case TaskFreqType.Week: dtNext = baseDT.AddDays(7 * taskRec.Cad); break; case TaskFreqType.Month: dtNext = baseDT.AddMonths(taskRec.Cad); break; case TaskFreqType.Year: dtNext = baseDT.AddYears(taskRec.Cad); break; default: dtNext = baseDT.AddDays(taskRec.Cad); break; } baseDT = dtNext; maxIter--; } } catch (Exception exc) { Log.Error($"Eccezione in CalcNextExe{Environment.NewLine}{exc}"); } return dtNext; } public void Dispose() { _configuration = null; } /// /// Chiamata esecuzione di un singolo task programmato, SE in stato abilitato /// /// /// Se true rischedula successiva chiamata /// public TaskResultModel ExecuteSqlTask(int TaskId, bool SchedNext) { TaskResultModel callRes = new TaskResultModel(); using (var dbCtx = new TaskContext(_configuration)) { // imposto timeout a 5 min //var currTimeout = dbCtx.Database.GetCommandTimeout(); dbCtx.Database.SetCommandTimeout(TimeSpan.FromMinutes(5)); string sqlCall = ""; try { DateTime dtStart = DateTime.Now; // recupero i dati da richiamare... var currRec = dbCtx .DbSetTaskList .Where(x => x.TaskId == TaskId && x.Enabled) .FirstOrDefault(); if (currRec != null) { // recupero comando string sqlCommand = currRec.Command; string rawParams = currRec.Args; // salvo la call x eventuale log errore sqlCall = $"EXEC {sqlCommand} {rawParams}"; var rawData = dbCtx .DbSetTaskResult .FromSqlRaw($"EXEC {sqlCommand} {rawParams}") .ToList(); callRes = rawData.FirstOrDefault() ?? new TaskResultModel(); DateTime dtEnd = DateTime.Now; // preparo record esecuzione... TaskExecModel resRec = new TaskExecModel() { TaskId = TaskId, DtStart = dtStart, DtEnd = dtEnd, IsError = callRes.ExecResult < 0, Result = callRes.TextResult }; dbCtx .DbSetTaskExe .Add(resRec); // aggiorno record chiamata... currRec.DtLastExec = dtStart; currRec.LastResult = resRec.Result; currRec.LastIsError = resRec.IsError; currRec.LastDuration = dtEnd.Subtract(dtStart).TotalSeconds; // solo se richiesto rischedulazione ricalcola chiamata if (SchedNext) { // calcolo prossima esecuzione... currRec.DtNextExec = CalcNextExe(currRec); } // segno modificato dbCtx.Entry(currRec).State = EntityState.Modified; // salvo modifiche! dbCtx.SaveChanges(); } } catch (Exception exc) { Log.Error($"Eccezione in ExecuteSqlCommand{Environment.NewLine}Call eseguita:{Environment.NewLine}{sqlCall}{Environment.NewLine}------- Stack Eccezione -------{Environment.NewLine}{exc}"); } } return callRes; } /// /// Annulla modifiche su una specifica entity (cancel update) /// /// /// public bool RollBackEntity(object item) { bool answ = false; using (var dbCtx = new TaskContext(_configuration)) { try { if (dbCtx.Entry(item).State == EntityState.Deleted || dbCtx.Entry(item).State == EntityState.Modified) { dbCtx.Entry(item).Reload(); } } catch (Exception exc) { Log.Error($"Eccezione in rollBackEntity{Environment.NewLine}{exc}"); } } return answ; } /// /// Ricerca task dato tipo + num max (desc) /// /// TaskId da cui deriva /// public List TaskExecGetFilt(int TaskId, int maxRec) { List dbResult = new List(); using (var dbCtx = new TaskContext(_configuration)) { dbResult = dbCtx .DbSetTaskExe .Include(x => x.TaskListNav) .Where(x => (x.TaskId == TaskId)) .OrderByDescending(x => x.DtStart) .Take(maxRec) .ToList(); } return dbResult; } /// /// Esegue registrazione di un Task generico (NON SQL) /// /// /// Se true rischedula successiva chiamata /// Record esecuzione task esterno /// public TaskResultModel TaskExecSaveExecuted(int TaskId, bool SchedNext, TaskExecModel ResRec) { TaskResultModel callRes = new TaskResultModel(); using (var dbCtx = new TaskContext(_configuration)) { try { // recupero i dati da richiamare... var currRec = dbCtx .DbSetTaskList .Where(x => x.TaskId == TaskId) .FirstOrDefault(); if (currRec != null) { // registro task ricevuto dbCtx .DbSetTaskExe .Add(ResRec); // aggiorno record chiamata... currRec.DtLastExec = ResRec.DtStart; currRec.LastResult = ResRec.Result; currRec.LastIsError = ResRec.IsError; currRec.LastDuration = ResRec.DtEnd.Subtract(ResRec.DtStart).TotalSeconds; // solo se richiesto rischedulazione ricalcola chiamata if (SchedNext) { // calcolo prossima esecuzione... currRec.DtNextExec = CalcNextExe(currRec); } // segno modificato dbCtx.Entry(currRec).State = EntityState.Modified; // salvo modifiche! dbCtx.SaveChanges(); } } catch (Exception exc) { Log.Error($"Eccezione in TaskExecSaveExecuted{Environment.NewLine}{exc}"); } } return callRes; } /// /// Upsert record TaskExec /// /// Record da aggiornare/inserire /// public bool TaskExecUpsert(TaskExecModel rec2upd) { bool done = false; using (var dbCtx = new TaskContext(_configuration)) { try { var currData = dbCtx .DbSetTaskExe .Where(x => x.TaskExecId == rec2upd.TaskExecId) .FirstOrDefault(); if (currData != null) { currData.TaskId = rec2upd.TaskId; currData.DtStart = rec2upd.DtStart; currData.DtEnd = rec2upd.DtEnd; currData.IsError = rec2upd.IsError; currData.Result = rec2upd.Result; dbCtx.Entry(currData).State = EntityState.Modified; } else { dbCtx .DbSetTaskExe .Add(rec2upd); } dbCtx.SaveChanges(); done = true; } catch (Exception exc) { Log.Error($"Eccezione in TaskExecUpsert{Environment.NewLine}{exc}"); } } return done; } /// /// Ricerca task dato tipo e /// /// /// public List TaskListGetAll(Task2ExeType TType) { List dbResult = new List(); using (var dbCtx = new TaskContext(_configuration)) { dbResult = dbCtx .DbSetTaskList .Where(x => (TType == Task2ExeType.ALL || x.TType == TType)) .OrderBy(x => x.Ordinal) .ToList(); } return dbResult; } /// /// Update ordinamento task /// /// Record da spostare x priorità /// public bool TaskListMove(TaskListModel rec2upd, bool moveUp) { bool done = false; using (var dbCtx = new TaskContext(_configuration)) { try { var currData = dbCtx .DbSetTaskList .Where(x => x.TaskId == rec2upd.TaskId) .FirstOrDefault(); if (currData != null) { int actOrdinal = currData.Ordinal; TaskListModel? otherRec = null; // cerco, secondo richiesta, precedente o successivo if (moveUp) { otherRec = dbCtx .DbSetTaskList .Where(x => x.Ordinal < currData.Ordinal && x.Group==currData.Group) .OrderByDescending(x => x.Ordinal) .FirstOrDefault(); } else { otherRec = dbCtx .DbSetTaskList .Where(x => x.Ordinal > currData.Ordinal && x.Group == currData.Group) .OrderBy(x => x.Ordinal) .FirstOrDefault(); } // inverto ordinale SE ho record if (otherRec != null) { currData.Ordinal = otherRec.Ordinal; otherRec.Ordinal = actOrdinal; dbCtx.Entry(currData).State = EntityState.Modified; dbCtx.Entry(otherRec).State = EntityState.Modified; } } //salvo dbCtx.SaveChanges(); done = true; } catch (Exception exc) { Log.Error($"Eccezione in TaskListUpsert{Environment.NewLine}{exc}"); } } return done; } /// /// Riordino record di un dato gruppo (da richiamare post cambio gruppo...) /// /// /// public bool TaskListReorder(int codGroup) { bool done = false; using (var dbCtx = new TaskContext(_configuration)) { // in primis recupero i record del gruppo ordinati var currList = dbCtx .DbSetTaskList .Where(x => x.Group == codGroup) .OrderBy(x => x.Ordinal) .ThenBy(x => x.TaskId) .ToList(); int newIdx = 0; int numRec = currList.Count; // li verifico in base al loro id attuale... foreach (var item in currList) { newIdx++; // se va cambiato lo riassegno if (item.Ordinal != newIdx) { item.Ordinal = newIdx; dbCtx.Entry(item).State = EntityState.Modified; } } // salvo! try { dbCtx.SaveChanges(); done = true; } catch (Exception exc) { Log.Error($"Eccezione in TaskListReorder{Environment.NewLine}{exc}"); } } return done; } /// /// Upsert record TaskList /// /// Record da aggiornare/inserire /// public bool TaskListUpsert(TaskListModel rec2upd) { bool done = false; using (var dbCtx = new TaskContext(_configuration)) { try { var currData = dbCtx .DbSetTaskList .Where(x => x.TaskId == rec2upd.TaskId && rec2upd.TaskId > 0) .FirstOrDefault(); if (currData != null) { currData.Ordinal = rec2upd.Ordinal; currData.Group = rec2upd.Group; currData.Enabled = rec2upd.Enabled; currData.Name = rec2upd.Name; currData.Descript = rec2upd.Descript; currData.Command = rec2upd.Command; currData.Args = rec2upd.Args; currData.Freq = rec2upd.Freq; currData.Cad = rec2upd.Cad; currData.DtLastExec = rec2upd.DtLastExec; currData.DtNextExec = rec2upd.DtNextExec; currData.LastDuration = rec2upd.LastDuration; currData.LastResult = rec2upd.LastResult; dbCtx.Entry(currData).State = EntityState.Modified; } else { dbCtx .DbSetTaskList .Add(rec2upd); } dbCtx.SaveChanges(); done = true; } catch (Exception exc) { Log.Error($"Eccezione in TaskListUpsert{Environment.NewLine}{exc}"); } } return done; } #endregion Public Methods #region Private Fields private static Logger Log = LogManager.GetCurrentClassLogger(); #endregion Private Fields #region Private Properties private static IConfiguration _configuration { get; set; } = null!; #endregion Private Properties } }