An application I'm working on at work had an interesting error that we couldn't track down and I've not been able to find anything similar on my searches.
My company has built a helpdesk system to track user issues, and after an error in submission the ticket "disappeared" from the list of all tickets. It was still visible in the database and was not filtered out anywhere. It was possible to navigate to the ticket by changing the URL to the ticket ID and all the details were the same as before the error in saving.
We ultimately solved the issue by restarting the web-app, but we'd like to track down the cause of the row disappearing from the list.
I've inherited this app and it has a whole host of issues we're already aware of, so I'm leaning towards the whole thing needing rebuilding from the ground up to make the save methods fully async instead of the half-and-half that we've currently got.
Not really sure what code to post as examples as the vast majority of the time it works, but obviously there's something not right in it.
EDITED TO ADD: Here's the edit post method from the controller
[HttpPost]
public async Task<ActionResult> Edit(EditTicketViewModel ticket)
{
try
{
var tech = (ticket.Technician_ID != null) ? this.UserService.Get(ticket.Technician_ID) : this.UserService.Get(this.User.Identity.GetUserId());
var cust = this.UserService.Get(ticket.Customer_ID);
var ogticket = this.TicketService.Get(ticket.Id);
var dateEdited = DateTime.UtcNow;
var productVersionId = OrganisationService.GetOrgProduct(ticket.OrgId, true).First(x => x.ProductID == ticket.ProductID).Product_Version.Id; ;
await this.TicketService.Edit(ticket.Id, ticket.Title, ticket.Description, dateEdited, ticket.Date_Closed, ticket.Customer_ID, ticket.State_Code, ticket.Priority_Code, ticket.Technician_ID, ticket.ProductID, productVersionId, ticket.Ticket_Type_ID);
if (ticket.State_Code == (int)StateCode.Closed)
{
await this.IntegrationService.SendEmailAsync(new EmailProperties()
{
Email = cust.Email,
Subject = "Ticket Closed",
Body = "Your ticket '{ticket.Title}' has now been closed.\n\n"//+
//$"Log into the portal to view it now -> <a href='https://helpdesk.rubixx.co.uk/Ticket/Details/{ticket.Id}'>Ticket {ticket.Id}</a>"
});
await this.IntegrationService.PrepareTeamsMessageAsync(new MessageProperties()
{
Summary = $"{this.UserService.Get(this.User).FullName} has closed a ticket",
Ticket_Id = ticket.Id,
Note_Id = null,
Type = TeamsMessageType.Closed
});
}
else await this.IntegrationService.PrepareTeamsMessageAsync(new MessageProperties()
{
Summary = $"{this.UserService.Get(this.User).FullName} has updated a ticket",
Ticket_Id = ticket.Id,
Note_Id = null,
Type = TeamsMessageType.Updated
});
this.TicketService.CreateTimelineEvent(ticket.Id, this.User.Identity.GetUserId(), DateTime.UtcNow.ToLocalTime(), "", TimelineEventType.TicketEdited);
this.AddToastMessage("Success", "Edited Successfully", ToastType.Success);
return Redirect(ticket.ReturnUrl);
}
catch (Exception ex)
{
this.IntegrationService.LogMessage($"Exception occurred: {ex.Message}");
this.AddToastMessage("Error: ", "An internal error has occurred", ToastType.Error);
return this.Redirect("/");
}
}
And the service function that actually saves the changes:
public async Task Edit(int ID, string Title, string Description, DateTime? dateedited, DateTime? dateclosed, string CustomerId, int StateCode, int Prioritycode, string TechnicianId, int Productid, int? productVersionId, int ticketType = (int)TicketTypeEnum.Support)
{
var originalTicket = Context.Tickets.Find(ID);
originalTicket.Title = Title;
originalTicket.Technician_ID = TechnicianId;
originalTicket.State_Code = ticketType == (int)TicketTypeEnum.FeatureRequest ? (int)Enums.StateCode.FeatureSuggestion : StateCode;
originalTicket.Priority_Code = ticketType == (int)TicketTypeEnum.FeatureRequest ? (int)PriorityCode.FeatureSuggestion : Prioritycode;
originalTicket.Description = Description;
originalTicket.Date_Edited = dateedited;
originalTicket.Date_Closed = dateclosed;
originalTicket.Customer_ID = CustomerId;
originalTicket.ProductID = Productid;
originalTicket.Product_Version_ID = productVersionId;
originalTicket.Ticket_Type_ID = ticketType;
await Context.SaveChangesAsync();
}
Here's the CreateTimelineEvent code:
public void CreateTimelineEvent(int ticketno, string raisedby, DateTime dateadded, string details, TimelineEventType type, string assignedto = null)
{
try
{
var timelineEvent = new TimelineEvent()
{
Ticket_Number = ticketno,
Raised_By = raisedby,
DateAdded = dateadded,
Details = details,
Type = (int)type,
Assigned_To = assignedto
};
Context.TimelineEvents.Add(timelineEvent);
Context.SaveChanges();
}
catch (Exception ex)
{
throw ex;
}
}
The GetTickets function:
public IEnumerable<Ticket> GetTickets(FilterType type, string current_user_id, TicketTypeEnum ticketType = TicketTypeEnum.Support)
{
IEnumerable<Ticket> tickets = null;
switch (ticketType)
{
case TicketTypeEnum.Support:
tickets = Context.Tickets.Include(e => e.Tech).Include(e => e.Customer).SupportTickets();
break;
case TicketTypeEnum.FeatureRequest:
tickets = Context.Tickets.Include(e => e.Tech).Include(e => e.Customer).FeatureRequestTickets();
break;
default:
tickets = Context.Tickets.Include(e => e.Tech).Include(e => e.Customer);
break;
}
switch (type)
{
case FilterType.OpenTickets:
return tickets.OpenTickets();
case FilterType.ClosedTickets:
return tickets.ClosedTickets();
case FilterType.MyOpenTickets:
return tickets.MyOpenTickets(current_user_id);
case FilterType.OpenedToday:
return tickets.OpenedToday();
case FilterType.DueToday:
return tickets.DueToday();
default:
return tickets;
}
}
Apologies for taking so long to get back to this question, but not long after I posted this I managed to track the issue down to a home-grown Dependency Injection system creating a static instance of the DBContext.
We've since decided to re-write the system in .Net Core to leverage the built in Dependency Injection.