using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Reflection; using Interfaces; using SQLite.Net; using SQLite.Net.Platform.Win32; using SQLiteRepository.Converters; using SQLiteRepository.Extensions; using SQLiteRepository.Properties; namespace SQLiteRepository { public class SQLiteRepository : IRepository { private readonly SQLiteConnection _connection; private readonly ILogger _logger; private readonly string _path; private const string DATABASE_NAME = "flexitimedb.db"; private const string UPGRADE_SCRIPT_PREFIX = "SQLiteRepository.UpgradeScripts."; public SQLiteRepository(ILogger logger) { _path = new Uri(Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().CodeBase), DATABASE_NAME)) .LocalPath; if (logger == null) throw new ArgumentNullException(nameof(logger)); _logger = logger; _connection = new SQLiteConnection(new SQLitePlatformWin32(), _path); _connection.CreateTable(); _connection.CreateTable(); _connection.CreateTable(); _connection.CreateTable(); _connection.CreateTable(); _connection.CreateTable(); _connection.CreateTable(); _logger.Trace("Initialised SQLite Repository"); _logger.Trace("Checking For Upgrades"); CheckForDbUpgrade(); } public UserList GetUsers(int pageNumber = -1, int pageSize = -1, int groupId = -1) { var ret = new UserList(); List users = GetUserList(groupId, pageSize, pageNumber); var userCount = GetUserCount(); if (pageNumber == -1 && pageSize == -1) { ret.PageNumber = 1; ret.PageSize = 20; } else { ret.PageNumber = pageNumber; ret.PageSize = pageSize; } if (!users.Any()) { return ret; } foreach (var user in users) { var userObj = UserConverter.ConvertToUserDto(user); userObj.AssociatedIdentifiers = GetAssociatedIdentifiers(user.Id); userObj.State = GetUserState(GetLogDirection(user.Id)); userObj.LastEventDateTime = GetLastLogDateTime(GetLastTimeLog(user.Id)); userObj.Groups = GetGroups(user.Id); ret.Users.Add(userObj); } ret.TotalUserCount = userCount; return ret; } public UserList Search(string searchParam) { _logger.Trace("Searching SQLite database for the term: {0}", searchParam); var ret = new UserList(); searchParam = string.Format("%{0}%", searchParam); var users = _connection.Query( SQLiteProcedures.SEARCH_USER_LIST, searchParam, searchParam); _logger.Trace("Got {0} results for term: {1}", users.Count, searchParam); if (!users.Any()) { ret.PageNumber = 1; ret.PageSize = 20; return ret; } foreach (var user in users) { var userObj = UserConverter.ConvertToUserDto(user); var cards = _connection.Query( SQLiteProcedures.GET_CARDS_BY_USER_ID, user.Id); foreach (var card in cards) { userObj.AssociatedIdentifiers.Add(IdentifierConverter.ConvertToIdentifierDto(card)); } userObj.State = GetUserState(GetLogDirection(user.Id)); ret.Users.Add(userObj); } ret.PageSize = 20; ret.PageNumber = (int)Math.Ceiling((double)ret.Users.Count/(double)ret.PageSize); return ret; } public User GetUser(int id) { var ret = new User(); try { var users = _connection.Query( SQLiteProcedures.GET_USER_BY_ID, id); if (!users.Any()) return ret; var user = users.First(); ret = UserConverter.ConvertToUserDto(user); ret.Groups = GetGroups(); var usersGroups = GetGroups(user.Id); foreach (var group in usersGroups) { ret.Groups.First(x => x.Id == group.Id).IsAssociatedToUser = true; } var cards = _connection.Query( SQLiteProcedures.GET_CARDS_BY_USER_ID, user.Id); foreach (var card in cards) { ret.AssociatedIdentifiers.Add(IdentifierConverter.ConvertToIdentifierDto(card)); } } catch (Exception ex) { _logger.Error(ex, "Error in GetUser with Id: {0}", id); ret.UserId = id; ret.FirstName = ret.LastName = string.Empty; ret.HoursPerWeek = -1.0f; ret.IsContractor = false; ret.AssociatedIdentifiers.Clear(); } return ret; } public TimeLogList GetTimeLogs(int userId) { return GetTimeLogs(userId, DateTime.UtcNow); } public TimeLogList GetTimeLogs(int userId, DateTime selectedDate) { var ret = new TimeLogList { SelectedDate = selectedDate.Date }; var calendarWeek = GetIso8601CalendarWeek(selectedDate); ret.CalendarWeek = calendarWeek; try { ret.TimeLogs = GetTimeLogList(userId, calendarWeek, selectedDate.Year); } catch (Exception ex) { _logger.Error(ex, "Error in GetTimeLogs with Id: {0} and selected date: {1}, Exception: {2}", userId, selectedDate, ex); } try { ret.HoursPerWeekMinutes = GetUserContractedHours(userId) * 60.0f; } catch (Exception ex) { _logger.Error(ex, "Error in GetUserContracterHours with Id: {0} and selected date: {1}, Exception: {2}", userId, selectedDate, ex); } ret.UserInformation = GetUser(userId); return ret; } private float GetUserContractedHours(int userId) { var hoursQuery = _connection.Query(SQLiteProcedures.GET_USER_CONTRACTED_HOURS, userId); if (hoursQuery.Any()) { return hoursQuery.First().HoursPerWeek; } return -1.0f; } public IdentifierList GetUnassignedIdentifierList() { var ret = new IdentifierList(); var cardQuery = _connection.Query( SQLiteProcedures.GET_UNASSIGNED_CARD_LIST, Constants.UNASSIGNED_CARD_USER_ID); foreach (var card in cardQuery) { ret.data.Add(IdentifierConverter.ConvertToIdentifierDto(card)); // new Identifier //{ // Id = card.Id, // IsAssociatedToUser = card.UserId_FK != Constants.UNASSIGNED_CARD_USER_ID, // UniqueId = card.CardUId, // LastUsed = card.LastUsed.DateTime //}); } return ret; } public void ClearUnassignedIdentifiers() { var unassignedIdentifiers = _connection.Query(SQLiteProcedures.GET_UNASSIGNED_CARD_LIST); //remove the logs from the timelog db _connection.Query( SQLiteProcedures.FormatInQuery(SQLiteProcedures.DELETE_TIMELOG_ENTRIES, unassignedIdentifiers.Select(x => x.Id.ToString()).ToList())); //now remove the unassigned identifiers/cards _connection.Execute( SQLiteProcedures.CLEAR_UNASSIGNED_CARDS, Constants.UNASSIGNED_CARD_USER_ID); } public OperationResponse UpdateUser(User user, out int userIdResult) { var ret = OperationResponse.NONE; //Get a list of current associated identifiers, convert into a list of Identifier Objects.. var currentCards = _connection.Query(SQLiteProcedures.GET_CARDS_BY_USER_ID, user.UserId) .Select(IdentifierConverter.ConvertToIdentifierDto).ToList(); var cardsToRemove = currentCards.Exclude(user.AssociatedIdentifiers, x => x.Id).Select(x => x.Id).ToList(); #region Update/Create User int userId; if (user.UserId != -1) { UpdateUserDetails(user.FirstName, user.LastName, user.HoursPerWeek, user.IsContractor, user.UserId); userId = user.UserId; } else { var userInsert = UserConverter.ConvertFromUserDto(user); _connection.Insert(userInsert); userId = userInsert.Id; if (ret < OperationResponse.CREATED) ret = OperationResponse.CREATED; } #endregion #region GetUnique Identifiers var existingCards = _connection.Query(SQLiteProcedures.FormatInQuery( SQLiteProcedures.GET_CARDS_BY_UNIQUE_ID_LIST, user.AssociatedIdentifiers.Select(x => x.UniqueId.ToString()).ToList())); foreach (var card in user.AssociatedIdentifiers) { if (existingCards.All(x => x.CardUId != card.UniqueId)) { //this card is not currently in the system.. var cardInsert = IdentifierConverter.ConvertFromIdentifierDto(card, user.UserId); _connection.Insert(cardInsert); existingCards.Add(cardInsert); if (ret < OperationResponse.CREATED) ret = OperationResponse.CREATED; //only change it if my status supercedes. } } #endregion #region Update Card/Unique Id entries/associations //make sure all identifiers are associated to the right user UpdateIdentifierUserId(existingCards.Select(x=>x.Id).ToList(), userId); //make sure to remove all identifiers that have been deselected in edit are removed from user UpdateIdentifierUserId(cardsToRemove, Constants.UNASSIGNED_CARD_USER_ID); #endregion #region Update Group Associations SetUserGroups(userId, user.Groups.Where(x => x.IsAssociatedToUser).ToList()); #endregion userIdResult = userId; return ret; } public LogEventResponse LogEventTime(Identifier identifier, out int logId, DateTime logTime = default(DateTime)) { #region Set the LogTime before we start querying anything. if (logTime == default(DateTime)) { logTime = DateTime.UtcNow; _logger.Debug("Using own log time: {0}", logTime.ToString("o")); } else { _logger.Debug("Using supplied log time: {0}", logTime.ToString("o")); } #endregion var ret = new LogEventResponse(); var cardIdQuery = _connection.Query( SQLiteProcedures.GET_CARDS_BY_UNIQUE_ID, identifier.UniqueId); #region Get/Insert the PK Id Identifier to associate the time log to. var ident = new CardUniqueId(); if (!cardIdQuery.Any()) { //new card, create it! ident.UserId_FK = -1; ident.CardUId = identifier.UniqueId; _connection.Insert(ident); UpdateIdentifierLastUsed(DateTimeOffset.UtcNow, ident.Id); logId = -1; //dont try to log any timelogs against this card, as it is unassigned to a user. ret.ProcessResponse=OperationResponse.SUCCESS; ret.Direction = LogDirection.UNKNOWN; return ret; } ident = cardIdQuery.First(); _logger.Warn("More than 1 Identifier returned with ID {0}, card ids returned: {2}", ident.CardUId, string.Join(",", cardIdQuery.Select(x => x.CardUId).ToList())); #endregion // Get The User Direction (are they going in or out)? var logDirection = GetLogDirection(ident.UserId_FK); #region Check the user hasnt registered an event in the last few minutes.. if (ident.UserId_FK != -1) { //only check log gap if the card is associated to a user var hysteresisThresholdMinutes = Convert.ToInt32(ConfigurationHandler.ConfigurationHandler.GetConfiguration("SwipeTimeGap") ?? "3"); var threshold = DateTime.UtcNow.AddMinutes(0 - hysteresisThresholdMinutes); var logs = _connection.Query( SQLiteProcedures.GET_LOGS_IN_LAST_X_MINUTES, threshold.Ticks, ident.UserId_FK); _logger.Trace("Checking last swipe event gap"); if (logs.Any()) { _logger.Error("Not logging event for user id: {0}, logged event within TimeGap Threshold of {1}", ident.UserId_FK, threshold); logId = -1; ret.ProcessResponse = OperationResponse.FAILED; ret.Direction = LogDirection.UNKNOWN; return ret; } } #endregion #region Get the current time (for swiping). and calendar week/year to help recall the data. var calendarWeek = GetIso8601CalendarWeek(logTime); var year = logTime.Year; #endregion var timeLog = new TimeLogDb { SwipeEventDateTime = DateTime.UtcNow, UserId_FK = ident.UserId_FK, IdentifierId = ident.Id, Direction = logDirection, Year = year, CalendarWeek = calendarWeek, Source = LogSourceDb.IDENTIFIER }; _connection.Insert(timeLog); UpdateIdentifierLastUsed(timeLog.SwipeEventDateTime, timeLog.IdentifierId); logId = timeLog.Id; ret.Direction = (LogDirection)(int)logDirection; ret.ProcessResponse = OperationResponse.SUCCESS; return ret; } public OperationResponse CreateGroup(Group group, out int groupId) { var query = _connection.Query(SQLiteProcedures.GET_GROUP_BY_NAME, group.Name); if (query.Any()) { groupId = query[0].GroupId; return OperationResponse.NONE; } var groupDb = new GroupDb { GroupName = group.Name }; var resp = _connection.Insert(groupDb); groupId = groupDb.GroupId; return OperationResponse.CREATED; } public List GetGroups(int userId = -1) { var ret = new List(); List query; if (userId == -1) { query = _connection.Query(SQLiteProcedures.GET_GROUPS); } else { query = _connection.Query(SQLiteProcedures.GET_GROUPS_FOR_USER, userId); } foreach (var group in query) { ret.Add(new Group { Id = group.GroupId, Name = group.GroupName, UserCount = int.Parse(group.AssignedUserCount ?? "0") }); } return ret; } public Group GetGroup(int groupId) { var query = _connection.Query(SQLiteProcedures.GET_GROUP_BY_ID, groupId); if (query.Any()) { var group = query.First(); return new Group { Id = group.GroupId, Name = group.GroupName, UserCount = 0 }; } return new Group(); } public OperationResponse UpdateGroup(Group group) { _connection.Query(SQLiteProcedures.UPDATE_GROUP, @group.Name, @group.Id); return OperationResponse.UPDATED; } public OperationResponse DeleteGroup(int groupId) { _connection.Delete(groupId); return OperationResponse.DELETED; } public OperationResponse DeleteLog(TimeLog log) { var query = _connection.Query( SQLiteProcedures.GET_TIMELOG_ENTRY, log.Id); if (!query.Any()) return OperationResponse.FAILED; UpdateExistingLogDirections(log); _connection.ExecuteScalar(SQLiteProcedures.DELETE_TIMELOG_ENTRY, log.Id); return OperationResponse.DELETED; } public OperationResponse CreateLog(TimeLog log) { log.CalendarWeek = GetIso8601CalendarWeek(log.EventTime.UtcDateTime); log.Year= log.EventTime.Year; var dbLog = TimeLogConverter.ConvertFromTimeLogDto(log); dbLog.IdentifierId = ConvertSourceToIdentifierId(log.Source); #region update in/out directions for manual logs. UpdateExistingLogDirections(log); #endregion //and now insert the new log. _connection.Insert(dbLog); return OperationResponse.CREATED; } public OperationResponse UpdateLog(TimeLog log) { var query = _connection.Query( SQLiteProcedures.GET_TIMELOG_ENTRY, log.Id); if(!query.Any()) return OperationResponse.FAILED; if (log.CalendarWeek > 52 || log.CalendarWeek < 1) { log.CalendarWeek = GetIso8601CalendarWeek(log.EventTime.UtcDateTime); } if (log.Year < 2017) { log.Year = log.EventTime.Year; } _connection.ExecuteScalar( SQLiteProcedures.UPDATE_TIMELOG_ENTRY, log.UserId, (LogDirectionDb) (int) log.Direction, log.EventTime, log.CalendarWeek, log.Year, (LogSourceDb) (int) log.Source, log.Id); return OperationResponse.UPDATED; } public Policy GetPolicy() { var query = $"select * from {nameof(PolicyDb)}"; var policies = _connection.Query(query); return PolicyConverter.ConvertToPolicyDto(policies.OrderByDescending(x => x.Version).FirstOrDefault()); } public void SavePolicy(Policy policy) { if (string.IsNullOrEmpty(policy.Version)) { policy.Version = (GetNextPolicyVersion()+1).ToString(); } var policyDb = PolicyConverter.ConvertFromPolicyDto(policy); _connection.Insert(policyDb); } private int GetNextPolicyVersion() { var query = $"select Max({nameof(PolicyDb.Id)}) from {nameof(PolicyDb)}"; var id = _connection.ExecuteScalar(query); return id; } private int GetUserCount() { return _connection.ExecuteScalar(SQLiteProcedures.GET_TOTAL_USER_COUNT); } private List GetUserList(int groupId = -1, int pageSize=-1, int pageNumber=-1) { List users; if (pageNumber != -1 && pageSize != -1) { users = _connection.Query(SQLiteProcedures.GET_ALL_USERS_PAGINATE, pageSize, (pageNumber - 1) * pageSize); } else if (groupId != -1) { users = _connection.Query( SQLiteProcedures.GET_ALL_USERS_BY_GROUP, groupId); } else { users = _connection.Query(SQLiteProcedures.GET_ALL_USERS); } return users; } private void CheckForDbUpgrade() { var data = _connection.Query(SQLiteProcedures.GET_DB_VERSION); if (!data.Any()) { //Pre-Upgrade database, need upgrading _logger.Trace("Pre version 0.2 RC database found, performing update.."); ExecuteUpgradeFromVersion("0.1"); //execute 0.2 upgrade scripts onwards. } else { var installedVersion = new Version(data.First().VersionNumber); var currentVersion = new Version(AssemblyInfo.ASSEMBLY_VERSION); if (currentVersion.CompareTo(installedVersion) > 0) //greater than 0 - current version is newer { _logger.Trace("Installed Database Version: {0} is older than current version {1}"); ExecuteUpgradeFromVersion(installedVersion.ToString()); } } } private void ExecuteUpgradeFromVersion(string installedVersion) { var instVers = new Version(installedVersion); //so we have established that each script for upgrade will be .sql, so now start at lowest version and work up! var assembly = Assembly.GetExecutingAssembly(); var upgradeScripts = assembly.GetManifestResourceNames() .Where(x => x.StartsWith(UPGRADE_SCRIPT_PREFIX)) .OrderBy(x => new Version(Path.GetFileNameWithoutExtension(x.Replace(UPGRADE_SCRIPT_PREFIX, string.Empty)))) .Where(x => { var scriptVersion = new Version(Path.GetFileNameWithoutExtension(x.Replace(UPGRADE_SCRIPT_PREFIX, string.Empty))) .CompareTo(instVers); return scriptVersion > 0; }) .ToList(); //now have an ordered list of upgrade script files, so execute in order! foreach (var upgradeScript in upgradeScripts) { using (var stream = assembly.GetManifestResourceStream(upgradeScript)) using(var str = new StreamReader(stream)) { var script = str.ReadToEnd(); _logger.Trace("Executing upgrade script with name: {0}", upgradeScript); _connection.Execute(script); } } SetDbVersion(AssemblyInfo.ASSEMBLY_VERSION); } private void SetDbVersion(string vers) { _connection.DeleteAll(); _connection.Insert(new DbVersion {VersionNumber = vers}); _logger.Trace("Set Database version to: {0}", vers); } private DateTime GetLastLogDateTime(TimeLogDb timeLog) { if (timeLog != null) { return timeLog.SwipeEventDateTime.DateTime; } return DateTime.MinValue; } private bool GetUserState(LogDirectionDb logDirection) { switch (logDirection) { case LogDirectionDb.OUT: return true; default: return false; } } private int ConvertSourceToIdentifierId(LogSource logSource) { switch (logSource) { case LogSource.UI: return -100; case LogSource.TRAYAPP: return -200; default: return -10; } } private bool SetUserGroups(int userId, List groups) { var groupIds = GetGroupIds(groups.Select(x => x.Name).ToList()); return SetUserGroups(userId, groupIds); } private bool SetUserGroups(int userId, int[] groupIds) { //remove the existing user>group associations _connection.Query("delete from UserGroupJoinDb where UserId_FK = ?", userId); //add the new group associations. _connection.InsertAll(groupIds.Select(x => new UserGroupJoinDb { GroupId_FK = x, UserId_FK = userId })); return true; } private int[] GetGroupIds(List groupNames) { var ret = new List(); foreach (var g in groupNames) { var query = _connection.Query("select GroupId from GroupDb where GroupName=?", g); if (!query.Any()) continue; var id = query.First(); ret.Add(id.GroupId); } return ret.ToArray(); } private List GetTimeLogList(int userId, int calendarWeek, int year) { var timeLogList = _connection.Query( SQLiteProcedures.GET_TIMELOGS, userId, calendarWeek, year); var timeLogs = timeLogList.Select(x => new TimeLog { Id = x.Id, CalendarWeek = x.CalendarWeek, Direction = (LogDirection)x.Direction, IdentifierId = x.IdentifierId, EventTime = x.SwipeEventDateTime, UserId = x.UserId_FK, Year = x.Year }).OrderBy(x=>x.EventTime.UtcDateTime).ToList(); var dict = new Dictionary(); var logList = new List(); //make sure each day of the week is accounted for in the dictionary. foreach (DayOfWeek day in Enum.GetValues(typeof(DayOfWeek))) { dict.Add(day, new DailyLogs()); } //add the logs to the respective day of the week. foreach (var log in timeLogs.OrderBy(x=>x.EventTime)) { dict[log.EventTime.DayOfWeek].Logs.Add(log); } var logGroups = timeLogs.GroupBy(x => x.EventTime.DayOfWeek); foreach (var group in logGroups) { var groupLogs = group.ToList(); var dailyLog = new DailyLogs { Logs = groupLogs, Day = group.Key, DayOfWeek = group.Key.ToString() }; dailyLog.DailyTotal = CalculateDailyTotal(dailyLog); logList.Add(dailyLog); } foreach (DayOfWeek day in Enum.GetValues(typeof(DayOfWeek))) { if (logList.Any(x => x.Day == day)) continue; var dailyLog = new DailyLogs { Day = day, DayOfWeek = day.ToString() }; logList.Add(dailyLog); } foreach (var dailyCollection in dict) { dailyCollection.Value.DailyTotal = CalculateDailyTotal(dailyCollection.Value); } return logList.OrderBy(x => ((int)x.Day + 6) % 7).ToList(); } private double CalculateDailyTotal(DailyLogs dailyLogs) { var totalInTime = TimeSpan.FromSeconds(0); var logs = dailyLogs.Logs.OrderBy(x => x.EventTime.UtcDateTime).ToArray(); var totalCalcMax = IsOdd(logs.Length) ? logs.Length - 1 : logs.Length; for (int i = 0; i < totalCalcMax; i += 2) { totalInTime += (logs[i + 1].EventTime - logs[i].EventTime); } return Math.Round(totalInTime.TotalMinutes, 2); } /// /// determines if the number is an odd or even value /// /// number to determine is odd or even /// true - number is odd private bool IsOdd(int value) { return value % 2 != 0; } /// /// Get the new direction for the user based on previous entry logs in the system. /// /// /// If the user has not logged in today, the direction will be In. /// If the user has logged in already today, the direction will be the opposite of the last /// recorded log direction. ("out" if "in", "in" if "out") /// /// Id of the user to get the log direction of. /// indicating what direction the new log is. private LogDirectionDb GetLogDirection(int userId) { var logDirection = LogDirectionDb.UNKNOWN; if (userId != -1) { var lastEntry = GetLastTimeLog(userId); if (lastEntry != null) { // See if the datetime retrieved is yesterday. If yesterday, logDirection = true (in) if (IsLogDateTimeYesterdayOrOlder(lastEntry.SwipeEventDateTime.DateTime)) { logDirection = LogDirectionDb.IN; } else { // we have a time log from today already, so just do the opposite of what we last did! logDirection = LogDirectionConverter.InvertLogDirectionDb(lastEntry.Direction); } } else { //assume its the first then! logDirection = LogDirectionDb.IN; } } return logDirection; } private TimeLogDb GetLastTimeLog(int userId) { var lastEntry = _connection.Query( SQLiteProcedures.GET_LAST_TIMELOG_DIRECTION, userId); if (lastEntry.Any()) { return lastEntry.First(); } return null; } private void UpdateIdentifierLastUsed(DateTimeOffset dt, int cardId) { _connection.ExecuteScalar(SQLiteProcedures.UPDATE_CARD_LAST_USED, dt, cardId); } private List GetAssociatedIdentifiers(int userId) { var cards = _connection.Query( SQLiteProcedures.GET_CARDS_BY_USER_ID, userId); var ret = new List(); foreach (var card in cards) { ret.Add(new Identifier() { UniqueId = card.CardUId, IsAssociatedToUser = true, Id = card.Id }); } return ret; } /// /// Get the calendar week of the year according to the ISO8601 standard (starts monday). /// /// the date to get the calendar week of. /// the calendar week of the year in integer form (1-52) private int GetIso8601CalendarWeek(DateTime date) { var day = CultureInfo.InvariantCulture.Calendar.GetDayOfWeek(date); if (day >= DayOfWeek.Monday && day <= DayOfWeek.Wednesday) { date = date.AddDays(3); } return CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday); } /// /// Check whether the specified DateTime is from yesterday or older. /// /// the DateTime object to check is yesterday or older /// true - is yesterday or older. private bool IsLogDateTimeYesterdayOrOlder(DateTime dt) { return dt.Date.CompareTo(DateTime.Today.Date) < 0; } private void UpdateExistingLogDirections(TimeLog log) { //need to make this generic so that both create and delete will update the log directions.. but ARGH. //if you want to use this as a delete, you would delete and call this, if you want to create, call this then create. //should look at how to improve this method so that it will compensate for if the log hasnt been deleted yet, but ott? var weekLogs = GetTimeLogList(log.UserId, log.CalendarWeek, log.Year); //Get the same logs of the day that the log has been entered for var todaysLogs = weekLogs.FirstOrDefault(x => x.Day == log.EventTime.DayOfWeek); if (todaysLogs != null) { //Get the days logs that are after the manually created date var logs = todaysLogs.Logs.Where(x => x.EventTime.CompareTo(log.EventTime) >= 0).OrderBy(x => x.EventTime).ToList(); //Update each log with the inverse progressively var currentlogDirection = log.Direction; for (var i = 0; i < logs.Count; i++) { logs[i].Direction = LogDirectionConverter.InvertLogDirection(currentlogDirection); UpdateLog(logs[i]); currentlogDirection = logs[i].Direction; } } } private void UpdateIdentifierUserId(List cardsToUpdate, int userId) { foreach (var card in cardsToUpdate) { _connection.Query( SQLiteProcedures.UPDATE_CARD_USER_ID, userId, card); } } private void UpdateUserDetails(string firstName, string lastName, float hoursPerWeek, bool isContractor, int userId) { _connection.Query( SQLiteProcedures.UPDATE_USER_DETAILS, firstName, lastName, hoursPerWeek, isContractor, userId ); } } }