A bit of background to my issue - I've inherited a large C# MVC application, and I'm currently making a change to the main process in the app.
So originally, the user would upload an item, and that would be that. Now however, the user can select a checkbox in order to have the item delivered. If this checkbox is selected, the Delivery table is populated with the relevant details.
Here is the POST action in my Items controller for when the user originally uploads the item details:
[HttpPost]
public ActionResult AddItemDetails(Int64? itemId, Item item, FormCollection formValues)
{
if (formValues["cancelButton"] != null)
{
return RedirectToAction("Index");
}
if (formValues["backButton"] != null)
{
return RedirectToAction("AddItemStart");
}
string ImageGuid = formValues["ImageGUID"];
ViewData["Image_GUID"] = ImageGuid;
if (item.ImageID == null && String.IsNullOrEmpty(ImageGuid))
{
ModelState.AddModelError("Image", "Image is required.");
ViewData["Image"] = "Image is required.";
}
if (ModelState.IsValid)
{
item.SubmissionState = -1; // Unsubmitted;
/**********************ADDED 25/1/2011**************************/
item.ProductCode = formValues["ProductCode"];
item.Name = formValues["Name"];
string deliverySelection = formValues["deliverySelection"];
if (deliverySelection == "on")
{
item.DeliverySelection = "Yes";
Delivery c = new Delivery()
{
ProductCode = formValues["ProductCode"],
Name = formValues["Name"],
PhoneNo = formValues["PhoneNo"],
Address = formValues["Address"],
SubmissionDate = System.DateTime.Now
};
item.Delivery = c;
}
else
{
item.DeliverySelection = "No";
}
/*****************************END*******************************/
if (itemId.HasValue)
{
UpdateItemDetails(item, ImageGuid, this);
}
else
{
titleId = ItemServices.AddItem(item, ImageGuid);
}
return RedirectToAction("AddSubItemDetails", new { itemId = item.ItemID });
}
return View(item);
}
This works fine, and achieves the desired result. However, I am a little stuck on modifying the update action for in the Items controller. Here is what I have so far:
[HttpPost]
public ActionResult UpdateItemDetails(Int64 itemId, Item item, FormCollection formValues)
{
if (formValues["cancelButton"] != null)
{
return RedirectToAction("View", new { itemId = itemId });
}
string image = formValues["ImageGUID"];
ViewData["Image_GUID"] = ImageGuid;
if (item.ImageID == null && String.IsNullOrEmpty(ImageGuid))
{
ModelState.AddModelError("Image", "Image is required.");
}
if (ModelState.IsValid)
{
//**********************Added 31.01.2011****************************//
using (ModelContainer ctn = new ModelContainer())
{
string DeliverySelection = formValues["deliverySelection"];
if (deliverySelection == "on")
{
item.DeliverySelection = "Yes";
Delivery c = new Delivery()
{
ProductCode = formValues["ProductCode"],
Name = formValues["Name"],
PhoneNo = formValues["PhoneNo"],
Address = formValues["Address"],
SubmissionDate = System.DateTime.Now
};
ctn.Delierys.AddObject(c);
item.Delivery = c;
}
else
{
item.DeliverySelection = "No";
}
ctn.AddToItems(item);
ctn.SaveChanges();
UpdateItemDetails(item, ImageGuid, this);
return RedirectToAction("View", new { itemId = itemId });
}
return View("UpdateItemDetails", MasterPage, item);
}
Notice how this is slightly different and uses the UpdateItemDetails() to update the database. I was unsure what to do here as I do need to collect formvalues to insert into the Delivery database. Here is UpdateItemDetails:
private void UpdateItemDetails(Item item, string ImageFileGuid, ItemsController controller)
{
using (ModelContainer ctn = new ModelContainer())
{
Item existingData = ItemServices.GetCurrentUserItem(item.ItemID, ctn);
controller.UpdateModel(existingData);
existingData.UpdatedBy = UserServices.GetCurrentUSer().UserID;
existingData.UpdatedDate = DateTime.Now;
// If there is a value in this field, then the user has opted to upload
// a new cover.
//
if (!String.IsNullOrEmpty(ImageFileGuid))
{
// Create a new CoverImage object.
//
byte[] imageBytes = FileServices.GetBytesForFileGuid(Guid.Parse(ImageFileGuid));
Image newImage = new Image()
{
OriginalCLOB = imageBytes,
ThumbnailCLOB = ImageServices.CreateThumbnailFromOriginal(imageBytes),
HeaderCLOB = ImageServices.CreateHeaderFromOriginal(imageBytes),
FileName = "CoverImage"
};
existingData.Image = newImage;
}
ctn.SaveChanges();
}
}
The code working as above, throws the following error when I try to update the details:
System.Data.SqlClient.SqlException:
The conversion of a datetime2 data
type to a datetime data type resulted
in an out-of-range value. The
statement has been terminated.
This error is thrown at ctn.SaveChanges() in the update action.
So I suppose my first question is how to overcome this error, however without making changes that will affect the AddItemDetails action. My second question then would be, if that error was cleared up, would this be a correct way to go about updating?
I'd be very grateful for any pointers. If any more info is needed, just ask.
Thanks :)
I was looking into your error and found this answer to another question that gives a little more information about the DATETIME and DATETIME2 data types.
DATETIME supports 1753/1/1 to
"eternity" (9999/12/31), while
DATETIME2 support 0001/1/1 through
eternity.
If you check the data that is being submitted do you see anything anomalous? Do you have a date property in your item class that is being set to some default value that is invalid for the DATETIME field?
It looks like one of the dates in the object you are trying to save is DateTime.MinValue. It could be the submission date for example. Check your form data to see if the value is being updated ofmr the client correctly and go from there.
I have this issue currently because if someone mistakenly forgets a slash (e.g. 1/189 when they meant 1/1/89), TryUpdateModel() updates the model without error, translating it to the .NET DateTime of "1/1/0189".
But then the Save crashes with the "conversion of a datetime2 data type to a datetime data type resulted in an out-of-range value".
So wow do I catch this before the Save?
Related
Hello I have a controller method that I want to return the view model of that looks like this
This is what it would look like if it was hard-coded
public ActionResult SpecialOrderSummary(int? id)
{
// Retrieve data from persistence storage and save it to the view model.
// But here I am just faking it.
var vm = new ItemViewModel
{
ItemId = 123,
ItemName = "Fake Item",
Parts = new List<ItemPartViewModel>
{
new ItemPartViewModel
{
PartId = 1,
PartName = "Part 1"
},
new ItemPartViewModel
{
PartId = 2,
PartName = "Part 2"
}
}
};
return View(vm);
}
But I obviously don't want it hard coded. So this is what I was trying to do instead to achieve my goal
public ActionResult SpecialOrderSummary(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
JobOrder jobOrder = db.JobOrders.Find(id);
if (jobOrder == null)
{
return HttpNotFound();
}
ViewBag.JobOrderID = jobOrder.ID;
ItemInstance ii = db.ItemInstances.Where(x => x.serialNumber == jobOrder.serialNumber).FirstOrDefault();
Item item = db.Items.Find(ii.ItemID);
var vm = new ItemViewModel
{
ItemId = item.ItemID,
ItemName = item.Name,
Parts = new List<ItemPartViewModel>
{
foreach(ItemHasParts ihp in item.IHP)
{
Part part = db.Parts.Find(ihp.PartID);
new ItemPartViewModel
{
PartId = part.ID,
PartName = part.Name
};
}
}
};
return View(vm);
}
But that doesn't work. As it doesn't seem to recognize the closing }
of the opening "Parts" and the opening "vm" bracket as it skips both. Why is this?
Hmmm I thought I answered this question before: https://stackoverflow.com/a/62782124/2410655. Basically you can't have a for loop like that in the middle of the view model.
I would like to add 2 more things to it.
1. Id?
If the special order summary expects an ID, don't declare it as optional. If you do so, you have to add more logic to check whether there is an ID or not.
If the order summary expects an ID, just declare it as int id. And if the client doesn't provide it, let the MVC framework handle the error. Now depending on your setup, your MVC might throw a 404, or 500, or a user-friendly page. It's up to you, the developer, to set it up.
2. Be careful on NullReference Exception
In your code example, I see you used FirstOrDefault() on the item instance. That will bite you if it comes back as NULL and you call db.Items.Find(ii.ItemID)...
So based on your example, I would change the code to:
public ActionResult SpecialOrderSummary(int id)
{
JObOrder jobOrder = db.JobOrders.Find(id);
if (jobOrder == null)
{
return HttpNotFound();
}
ItemInstance itemInstance = db.ItemInstances
.Where(x => x.serialNumber == jobOrder.serialNumber)
.FirstOrDefault();
Item item = null;
if (itemInstance != null)
{
item = db.Items.Find(itemInstance.ItemID);
}
var vm = new JobOrderSummaryViewModel
{
JobOrderId = jobOrder.ID,
Parts = new List<ItemPartViewModel>();
};
if (item != null)
{
vm.ItemId = item.ItemId;
vm.ItemName = item.ItemName;
foreach(ItemHasParts ihp in item.IHP)
{
// Does Part and Item have many-to-many relationships?
// If so, you might be able to get the part by
// ihp.Part instead of looking it up using the ID.
// Again, it depends on your setup.
Part part = db.Parts.Find(ihp.PartID);
if (part != null)
{
vm.Parts.Add(new ItemPartViewModel
{
PartId = part.ID,
PartName = part.Name
});
}
}
}
return View(vm);
}
Note:
You have additional calls back to the database inside the loop (db.Parts.Find(ihp.PartID);). That will cause performance issue if you have huge data. Is there any way you can fetch all your data you needed once at the beginning?
Below is my controller That I am calling from a href button and passing an id. The href button is a duplicate button which is meant to create a copy of the selected module and add it to the database and then return it.
public ActionResult dupe(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
MODULE Modules = db.MODULE.Find(id);
if (Modules == null)
{
return HttpNotFound();
}
else
{
MODULE newMOd = new MODULE();
newMOd.APPLY__FINISH = Modules.APPLY__FINISH;
newMOd.CREATED_BY = Modules.CREATED_BY;
newMOd.CREATED_DATE = Modules.CREATED_DATE;
newMOd.MODULE_DESC = "Duplicate-"+Modules.MODULE_DESC;
newMOd.MODULE_TYPE = Modules.MODULE_TYPE;
newMOd.MODULE_TYPE1 = Modules.MODULE_TYPE1;
newMOd.PRODUCT_LINE = Modules.PRODUCT_LINE;
newMOd.MODULE_NAME = "Duplicate-" + Modules.MODULE_NAME;
foreach (MODULE_PARTS mp in Modules.MODULE_PARTS)
{
newMOd.MODULE_PARTS.Add(mp);
}
foreach (MODULE_OPTION mo in Modules.MODULE_OPTION)
{
MODULE_OPTION m = new MODULE_OPTION();
m.OPTION_NAME = mo.OPTION_NAME;
m.OPTION_TYPE = mo.OPTION_TYPE;
m.PRODUCT_LINE = mo.PRODUCT_LINE;
m.ADDED_BY = mo.ADDED_BY;
m.ADDED_ON = mo.ADDED_ON;
m.DEFAULT_FACTOR = mo.DEFAULT_FACTOR;
foreach (OPTION_PARTS op in mo.OPTION_PARTS)
{
m.OPTION_PARTS.Add(op);
}
newMOd.MODULE_OPTION.Add(mo);
}
newMOd.MODULE_PARTS = Modules.MODULE_PARTS;
newMOd.MODULE_OPTION = Modules.MODULE_OPTION;
db.MODULE.Add(newMOd);
db.SaveChanges();
}
return View(Modules);
}
This is my controller method, and when I try to add this module to database I get collection modified error. I'm not sure how or where?
enter image description here
The exception message in this case is quite clear... you cannot modify a member of a collection you are currently iterating over. It's that simple.
I have a project where I report time on diffrent projects, and I am working on so I can delete a report incase I do it wrong, which is working decent.
When I go to the summery page of all my reports it lists all the dates, I click the date and it sends it to the TimeReport view like this:
http://localhost:9061/Reports/TimeReport/b604a74a-2034-4916-9534-57788db1e8e2
And than it checks if the ReportId has a value like this:
#if (Model.ReportId.HasValue)
{
<button type="button" class="btn btn-danger" data-toggle="modal" data-target="#basic">Ta bort!</button>
}
And if it exists a Remove button will appear, I click the remove button and it will remove the report. but the URL is still the same, so if I refresh the site my application will crash because it that ID no longer exists in the database.
if (form != null && form.AllKeys.Contains("delete"))
{
new DatabaseLayer().DeleteTimeReport(Guid.Parse(form["ReportId"]));
LoadDefaultSettings(projectData);
ViewData.Model = projectData;
ViewData["deleted"] = true;
return View();
}
This is the model that check if if the GUID exists.
public void SaveToDatabase(Guid consultantId)
{
using (DatabaseLayer db = new DatabaseLayer())
{
//Time report exists, delete it.
if (ReportId.HasValue)
{
db.DeleteTimeReport(ReportId.Value);
}
//Time report does not exist, create a new one.
else
{
ReportId = Guid.NewGuid();
}
Report report = new Report
{
FK_ConsultantID = consultantId,
FK_UserID = Constants.UserTreetop,
Date = Date,
TimeReportID = ReportId.Value
};
TimeReportData reportData = new TimeReportData
{
Absent = 0,
Description = "",
StartHour = Times.StartHour,
StartMinute = Times.StartMinute,
EndHour = Times.EndHour,
EndMinute = Times.EndMinute,
BreakHour = Times.BreakHour,
BreakMinute = Times.BreakMinute,
FK_TimeReportID = ReportId.Value,
TimeReportDataID = Guid.NewGuid()
};
TimeReportProject[] reportProjects = new TimeReportProject[Projects.Count];
for (int i = 0; i < Projects.Count; i++)
{
reportProjects[i] = new TimeReportProject
{
Description = Projects[i].Description,
FK_ProjectID = Projects[i].ProjectId,
FK_TimeReportID = ReportId.Value,
Hours = Projects[i].Hours.GetValueOrDefault(), //Projects[i].Hours.Value,
HourRate = db.GetProjectHourRate(Projects[i].ProjectId, Date, Projects[i].Hours.GetValueOrDefault()),
TimeReportProjectID = Guid.NewGuid()
};
}
db.InsertTimeReport(report, reportData, reportProjects);
}
}
And as it exists it does this
public void DeleteTimeReport(Guid timeReportId)
{
db.ExecuteStoreCommand(
#" DELETE FROM [Salesweb].[dbo].[TimeReportProject] WHERE FK_TimeReportID = #id;
DELETE FROM [Salesweb].[dbo].[TimeReportData] WHERE FK_TimeReportID = #id;
DELETE FROM [Salesweb].[dbo].[TimeReport] WHERE TimeReportID = #id;"
, new SqlParameter("#id", timeReportId));
db.SaveChanges();
}
This is the view when I pass in the guid I Want to delete as the guid has a value the remove button will appear.
But as I delete the project it will return to the same view. Like we can see the tabs is not showing up, so if I want the to show again I have to go to another view, and than back to the same view. And if I refresh it will crash due the guid dosen't exist in the DB.
And here is the whole controller, it's a bit messy right now.
public ActionResult TimeReport(FormCollection form, Guid? id)
{
ViewDataDictionary vd = new ViewDataDictionary
{
["projects"] = new DatabaseLayer().GetConsultantProjects(Constants.CurrentUser(User.Identity.Name)),
["id"] = 1,
["showDescription"] = true
};
ViewData["vd"] = vd;
NewTimeReportModel projectData = new NewTimeReportModel();
if (form != null && form.AllKeys.Contains("delete"))
{
new DatabaseLayer().DeleteTimeReport(Guid.Parse(form["ReportId"]));
LoadDefaultSettings(projectData);
ViewData.Model = projectData;
ViewData["deleted"] = true;
return RedirectToAction("Index");
}
if (id.HasValue && (form == null || form.AllKeys.Length == 0))
{
using (DatabaseLayer db = new DatabaseLayer())
{
var timeReport = db.GetTimeReport(id.Value);
projectData = new NewTimeReportModel(timeReport);
if (projectData.Projects.Count == 1)
projectData.Projects[0].Hours = null;
}
}
else if (form == null || form.AllKeys.Length == 0)
{
LoadDefaultSettings(projectData);
}
else
{
//Get's all the dates from the view and formates them to look like yy-mm-dd so we can parse it to a datetime.
string[] dates = FormateDate(form["date"]);
//Loops over all the dates and saves the dates to the database.
projectData = ReportDates(form, projectData, dates);
//Loads default settings if all dates been reported.
LoadDefaultSettings(projectData);
}
//Get's and lists all the missing days
ListAllMssingDays();
ViewData.Model = projectData;
return View();
}
Recommended thing to do in such cases is run redirect to some default URL, like the summary page. Guessing that you have Summary action, that should be something like:
if (form != null && form.AllKeys.Contains("delete"))
{
new DatabaseLayer().DeleteTimeReport(Guid.Parse(form["ReportId"]));
return RedirectToAction("Summary", "Reports");
}
Note that this will do a client-side redirect, so to say - this will do a response with code 302 and new URL /Reports/Summary. This is usually a desired behavior though.
The exception you're getting is because your code assumes the item you're deleting will exist.
Change
return db.TimeReports.Where(x => x.TimeReportID == timeReportId).Single();
To
return db.TimeReports.Where(x => x.TimeReportID == timeReportId).SingleOrDefault();
Which will return null if your Where clause returns 0 items.
Then wherever in your code you're calling GetTimeReport() you need to check for and handle null.
I have the following function that searches a database for entries where a column called "description" have the same value. Right now it just returns the first value it finds or a default value is there isn't one.
public static NewCode GetAltCode(int altCodeVer, string descrip)
{
var sql = #"select Code, Description, VersionID from Code.CodeLookup where versionid=#vers and description=#description";
return ObjectFactory.GetInstance<IDatabaseFactory>().Query<NewCode>(sql, new { vers = altCodeVer, description = descrip, }).FirstOrDefault();
}
I have this if statement to check and make sure the result isn't null, and if it is, to say that the "code isn't found"
[Authorize(parentAction: "Edit")]
public ActionResult Lookup(string Code, int? VersionId = null)
{
var Info = VisitViews.GetDescriptionByVersionId(Code, VersionId.HasValue ? VersionId.Value : 9);
var description = string.Empty;
// CHECK FOR NULL
if (Info != null)
{
description = Info.Description;
if (VersionId == 9)
{
var altInfo = VisitViews.GetAltCode(10, description);
}
if (VersionId == 10)
{
var altInfo = VisitViews.GetAltCode(9, description);
}
}
else
description = "CODE NOT FOUND";
return Json(new { Description = description });
}
My question is, instead of doing FirstOrDefault, is there a way to store the results in an array (or even to store them in a list and call ToArray on the list)? I'm trying to get all of the codes received during the sql search instead of just one so that another function I am working on can traverse the array and place the items where they need to be in a UI.
For future reference of this post, here is the answer:
Change the return type to NewCode[] and replace .FirstOrDefault() with .ToArray()
public static NewCode[] GetAltCode(int altCodeVer, string descrip)
{
var sql = #"select Code, Description, VersionID from Code.CodeLookup where versionid=#vers and description=#description";
return ObjectFactory.GetInstance<IDatabaseFactory>().Query<NewCode>(sql, new { vers = altCodeVer, description = descrip, }).ToArray();
}
I'm having some issues in updating and inserting records using ASP.NET MVC and Entity Framework.
I have a form (which is a report) that is dynamically created and can have any amount of questions. I'm trying to allow the user to edit the report and submit the changes so that it is updated in the database.
I am retrieving the report to be edited from the database via a repository then setting it to an instance of ModeratorReport. I'm then changing the value of the properties and using db.SaveChanges to save the changes to the database.
The problem is that it is not saving the changes.
Please could someone advise me on what I am doing wrong?
Here is the Edit Action:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(FormCollection formCollection, int moderatorReportId, string status)
{
ModeratorReport reportToEdit = repository.GetModeratorReportById(moderatorReportId);
List<QuestionAnswer> originalReportAnswers = repository.GetAllModeratorReportAnswers(moderatorReportId, status).ToList();
foreach (QuestionAnswer answer in originalReportAnswers) {
reportToEdit.QuestionAnswers.Remove(answer);
}
int sectionID;
int questionID;
foreach (string key in formCollection.AllKeys)
{
var value = formCollection[key.ToString()];
Match m = Regex.Match(key, "section(\\d+)_question(\\d+)");
if (m.Success) {
QuestionAnswer newAnswer = new QuestionAnswer();
sectionID = Convert.ToInt16(m.Groups[1].Value.ToString());
questionID = Convert.ToInt16(m.Groups[2].Value.ToString());
newAnswer.ModeratorReportID = moderatorReportId;
newAnswer.SectionID = sectionID;
newAnswer.QuestionID = questionID;
newAnswer.Answer = value;
newAnswer.Status = "SAVED";
reportToEdit.QuestionAnswers.Add(newAnswer);
}
}
reportToEdit.Status = "SAVED";
AuditItem auditItem = new AuditItem();
auditItem.ModeratorReportID = moderatorReportId;
auditItem.Status = "SAVED";
auditItem.AuditDate = DateTime.Now;
auditItem.Description = "The Moderator report..."
auditItem.UserID = User.Identity.Name;
reportToEdit.Audit.Add(auditItem);
db.SaveChanges();
return RedirectToAction("Details", new { id = moderatorReportId });
}
The problem looks like you're just not setting reportToEdit's EntityState to modified. Like so:
reportToEdit.Audit.Add(auditItem);
reportToEdit.EntityState = EntityState.Modified;
db.SaveChanges();
For more information about the EntityState enumeration, see this MSDN article.