I'm quite new to C# and ASP.NET (and programming in general) and try to do some simple exercises.
What I am trying to do:
I would like to build a simple MVC App where records will have versions.
That is: Given a record, that I am about to change via the "Edit"-View, this record will not be overwritten. Instead a new record will be created (like a new Version). Both, the old and new record, have the same ItemId (which is not the primary key!), that links them together "semantically". In order to know, which record is the newer Version, the newer record has a VersionId that is +1 the VersionId of the older one.
Currently: I've started working on the Create-Action. A new record shall get a value of 1 for it's VersionId and for ItemId the largest ItemId already in the DB plus 1 - unless there is no record in the DB in which case ItemId shall be 1.
The Model:
namespace HowToUpdate.Models
{
public class ItemWithVersion
{
public int Id { get; set; }
public int ItemNr { get; set; }
public int VersionNr { get; set; }
public string Name { get; set; }
}
}
The Controller Action:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,Name")] ItemWithVersion itemWithVersion)
{
if (ModelState.IsValid)
{
// set the ItemNr
int currentMaxItemNr = db.ItemWithVersions.Max(i => i.ItemNr);
itemWithVersion.ItemNr = currentMaxItemNr + 1;
// set the VersionNr
itemWithVersion.VersionNr = 1;
db.ItemWithVersions.Add(itemWithVersion);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(itemWithVersion);
}
Problem: When I run localhost/ItemWithVersion/Create, enter a Value for the Name and Submit, i get the following error:
"The cast to value type 'System.Int32' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type.
Source error: int currentMaxItemNr = db.ItemWithVersions.Max(i => i.ItemNr);"
I tried:
// set the ItemNr
int currentMaxItemNr = db.ItemWithVersions.Max(i => i.ItemNr);
if (currentMaxItemNr == null)
{
itemWithVersion.ItemNr = 1;
}
else
{
itemWithVersion.ItemNr = currentMaxItemNr + 1;
}
Now the error seems to be int currentMaxItemNr = db.ItemWithVersions.Max(i => i.ItemNr);
Also int? currentMaxItemNr = db.ItemWithVersions.Max(i => i.ItemNr); and var currentMaxItemNr = db.ItemWithVersions.Max(i => i.ItemNr); won't do any good.
It's probably basic but I need your help! :) Thx.
Your if-statement is wrong:
if (currentMaxItemNr != null)
currently checks if currentMaxItemNr has a value and if it does, make it 1
So your statement should be if (currentMaxItemNr == null)
Edit:
I can't replicate your error unfortunately, but I did check and found out that there's an exception thrown when calling Max() on an empty List. So it would be better to first call if (db.ItemWithVersions.Count() > 0)
That way you are sure that Max() will return a result. If it fails that statement, you can set currentMaxItemNr to 0
You need to make sure that your table is not empty before calling the Max() method. You can use the Any() method to do that.
int currentMaxItemNr = 0;
if (db.ItemWithVersions.Any())
{
currentMaxItemNr = db.ItemWithVersions.Max(i => i.ItemNr);
}
itemWithVersion.ItemNr = currentMaxItemNr + 1;
// set the VersionNr
itemWithVersion.VersionNr = 1;
db.ItemWithVersions.Add(itemWithVersion);
db.SaveChanges();
Probably, the reason is that Id is declared as a int (not nullable, so null can not be assigned to id) . Try following.
public int? Id { get; set; }
Care to try?
int currentMaxItemNr = db.ItemWithVersions.Max(i => i.ItemNr ?? 1);
This will return currentMaxItemNr = 1 if your i.ItemNr is null.
here is how I would do this. When you click on Edit we run:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,Name")] ItemWithVersion itemWithVersion)
{
if (ModelState.IsValid)
{
// get the item with highest version
ItemWithVersion item = db.ItemWithVersions.Where(i =>i.ItemNr == itemWithVersion.ItemNr).OrderByDescending(i => i.VersionNr).FirstOrDefault();
//if item doesnt exist we need to create
if(item == null) {
//get the last item with highest ItemNr
ItemWithVersion lastitem = db.ItemWithVersions.OrderByDescending(i => i.ItemNr).FirstOrDefault();
if(lastitem == null) {
//if we didnt find a item, it means is the first item in the DB
itemWithVersion.ItemNr = 1;
} else {
//increment the itemNr for the new Item
itemWithVersion.ItemNr = lastitem.ItemNr + 1;
}
//set version to 1 since is the first version for this new ItemNr
itemWithVersion.VersionNr = 1;
} else {
//if we found a item for the current ItemNr we increase the version for the new item
itemWithVersion.VersionNr = item.VersionNr + 1;
}
db.ItemWithVersions.Add(itemWithVersion);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(itemWithVersion);
}
Related
I have a shopping cart list that I want to loop through, when the item is already in the cart the amount has to be increased, otherwise a new object has to be created.
I know you can't change a list during a loop,
so I tried to work with Tolist() but when I try that he only makes the first object and overwrites when I make the second one.
I don't get this fixed, can somebody help me fix it?
[HttpPost, ActionName("Details")]
[ValidateAntiForgeryToken]
public IActionResult DetailsPost(int id, DetailsProductViewModel detailsProductViewModel)
{
List<ShoppingCart> shoppingCartsList = new List<ShoppingCart>();
if (HttpContext.Session.Get<IEnumerable<ShoppingCart>>(WC.SessionCart)!=null
&& HttpContext.Session.Get<IEnumerable<ShoppingCart>>(WC.SessionCart).Count() > 0)
{
shoppingCartsList = HttpContext.Session.Get<List<ShoppingCart>>(WC.SessionCart);
foreach (var item in shoppingCartsList)
{
if (item.ProductId == id)
{
item.Aantal += 1;
}
else
{
shoppingCartsList.Add(new ShoppingCart { ProductId = id, Aantal = detailsProductViewModel.Product.Aantal });
}
}
}
else
{
shoppingCartsList.Add(new ShoppingCart { ProductId = id, Aantal = detailsProductViewModel.Product.Aantal });
}
HttpContext.Session.Set(WC.SessionCart, shoppingCartsList);
return RedirectToAction(nameof(Index));
}
you don't need a loop. just try to find the item in the list. if found, increase amount, if not create new one.
...//also check your shoppingCartsList is null or not
var product = shoppingCartsList.FirstOrDefault(x=>x.ProductId == id);
if(product == null)
{
shoppingCartsList.Add(new ShoppingCart { ProductId = id, Aantal = detailsProductViewModel.Product.Aantal });
}
else
{
product.Aantal++;
}
HttpContext.Session.Set(WC.SessionCart, shoppingCartsList);
Description: I have a model WorkOrder which contains WorkOrderForm model (start_date, end_date, organization), a list of models (the actual orders) and page parameters (page, page_size, page_count). The main idea is to display work orders, and because there is a large amount of them I have to filter the data; work orders get pulled from a database server (not local). On the initial View i prompt for start_date and end_date, I use DataType.Date and for organization i use string, this information get's stored in the model which I then pass in to the HttpPost. It extracts the data and displays it. Now, because there is A LOT of orders, I made costume pages to sort data, and I use 2 variables, page and page_size, which are displayed and can be set on the view after a valid WorkOrderForm was submitted.
Problem: The problem I am facing right now is that I can't seem to pass the 2 parameters page and page_size, from the view back to the controller at the same time. Both of them work but seem to reset each other, Example: I am on page 4, set page_size from 20 to 50, and it resets page to 1, this one is alright, but the main on is when I chose a page, it will reset the page_size to default (20). All the submitting has to happen inside Html.BeginForm() otherwise i lose the information stored in my model.
EDIT: now using PagedList.
New Problem: when I select a page it calls the [httpget], resetting the model and page size. I tried to implement it all in the Index, but failed miserably.
WorkOrder:
public class WorkOrder
{
[Key]
public int Id { get; set; }
public IPagedList<mymodel> view_list { get; set; }
public WorkOrderForm work_form { get; set; }
}
Controller:
[HttpGet]
public ActionResult Index(WorkOrder model)
{
var list = new List<mymodel>();
model.view_list = list.ToPagedList(1,1);
return View(model);
}
[HttpPost]
public ActionResult Index(WorkOrder model, int? page, int? page_size, string start_date, string end_date, string org)
{
var list = new List<mymodel>();
if (ModelState.IsValid)
{
using (OracleDbctx ctx = new OracleDbctx())
{
//database stuff
int pageNumber = (page ?? 1);
int pageSize = (page_size ?? 20);
model.view_list = list.ToPagedList(pageNumber, pageSize);
return View(model);
}
}
ModelState.AddModelError("", "Incorrect Information submitted");
return View();
}
Page Info submission in my view:
#:Page #(Model.view_list.PageCount < Model.view_list.PageNumber ? 0 : Model.view_list.PageNumber) of #Model.view_list.PageCount
#Html.PagedListPager(Model.view_list, page => Url.Action("Index", "WorkOrder",
new { page, ViewBag.page_size, start_date = Model.work_form.start_date, end_date = Model.work_form.end_date, org = Model.work_form.org }));
Question: How do I go about passing both of the parameters from the view and at the same time keep my model information, even if i am only choosing to update 1 parameter? If you have suggestions on possible work around's I would be happy to hear some ideas, even pointing me in a direction would be appreciated. If there might be something I am missing please do say.
New Question: How to ether get PagedList to call the [httppost] Index, or whats the best way to implement something like this in the default Index controller?
Following implementation of the Index in the Controller worked
public ActionResult Index(WorkOrder model, int? page, int? page_size, string start_date, string end_date, string org)
{
if (model.work_form == null)
{
if (start_date != null && end_date != null && org != null)
{
var form = new WorkOrderForm
{
start_date = start_date,
end_date = end_date,
org = org
};
model.work_form = form;
}
}
if (model.work_form != null)
{
using (OracleDbctx ctx = new OracleDbctx())
{
//do database stuff
//var list = database(query).ToList()
int pageNumber = (page ?? 1);
int pageSize = (page_size ?? 20);
int count = list.Count;
if (pageNumber - 1 > count / page_size)
{
pageNumber = 1;
}
model.view_list = list.ToPagedList(pageNumber, pageSize);
return View(model);
}
}
return View();
}
I have a case where entities.SaveChanges() is throwing a DbEntityValidationException and stating
The AValue field is required", however the AValue property has been populated.
Is there something else that can be checked to determine why this is throwing this error.
The basic code throwing the error:
try
{
var entities = new MyEntities();
var item = (from i in entities.Item
where i.Id == 1
select i).First();
item.AValue = "NewValue";
entities.SaveChanges();
}
catch (DbEntityValidationException exception)
{
var report = exception.Message();
}
I've done a bit of digging and in the catch have put:
var x = item.AValue; // and it shows "NewValue"
and
var message = new StringBuilder();
foreach (var entityValidationError in exception.EntityValidationErrors)
{
foreach (var validationError in entityValidationError.ValidationErrors)
{
object value = null;
try { value = entityValidationError.Entry.CurrentValues.GetValue<object>(validationError.PropertyName); }
catch (System.Exception exception) { value = null; }
message.AppendLine(validationError.PropertyName + ": " + validationError.ErrorMessage + " (VALUE: " + (value ?? "-") + ")");
}
}
return message.ToString();
// and i get "AValue: The AValue field is required. (VALUE: -) so that value item is null
I'm pretty stumped at this point, just can't figure out why it looks fine during the try but still throws a validation error message. Is there anything else I can add to see if there is something else going on there? Or any other recommended troubleshooting steps?
Please verify that the Item.AValue is set up properly in the Entity Framework dataset definition so that it is pointing to the AValue column in the relevant database table. If for some reason this is missing, then setting it in code would not cause it to be updated in the Db, which could lead to this error.
Your code contains some small syntax mistakes:
var item = (from i in entities.Item
where i.Id = 1
select i).First();
should be (double == sign)
var item = (from i in entities.Item
where i.Id == 1
select i).First();
Also:
item.AValue = "NewValue"
should be followed by a ";" sign.
I've recreated a small example that works:
Object class:
public class Item
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
Entity Framework context class:
public class Context : DbContext
{
public DbSet<Item> items { get; set; }
public Context()
: base("Demo")
{
}
}
On the first run, adds an item in the database:
using (var ctx = new Context())
{
Item stud = new Item() { Name = "Name" };
ctx.items.Add(stud);
ctx.SaveChanges();
}
Now, replace the code above with the following code:
try
{
var entities = new Context();
var item = (from i in entities.items
where i.Id == 1
select i).First();
item.Name = "NewValue";
entities.SaveChanges();
}
catch (DbEntityValidationException exception)
{
}
It seems that the issue was that there was a sub-object on my item that also had a property of AValue and this was the one that was throwing the exception.
Is there some way (via the DbEntityValidationException) to report on what object it is as well?
In my MVC Controller, I have this code (after adding an object, I redirect user to edit that object):
[PrivilegeRequirement("DB_ADMIN")]
public ActionResult Add()
{
MongoDataContext dc = new MongoDataContext();
var model = new ModelObj();
dc.Models.Insert(model);
return this.RedirectToAction("Edit", new { pId = model.Id, });
}
[PrivilegeRequirement("DB_ADMIN")]
public ActionResult Edit(ObjectId? pId)
{
MongoDataContext dc = new MongoDataContext();
var model = dc.Models.FindOneById(pId);
if (model == null)
{
Session["error"] = "No model with this ID found.";
return this.RedirectToAction("");
}
return this.View(model);
}
However, pId is always null, making the FindOneById always return null. I have debugged and made sure that the Id had value when passing from Add Action. Moreover, I tried adding a testing parameter:
[PrivilegeRequirement("DB_ADMIN")]
public ActionResult Add()
{
MongoDataContext dc = new MongoDataContext();
var model = new ModelObj();
dc.Models.Insert(model);
return this.RedirectToAction("Edit", new { pId = model.Id, test = 10 });
}
[PrivilegeRequirement("DB_ADMIN")]
public ActionResult Edit(ObjectId? pId, int? test)
{
MongoDataContext dc = new MongoDataContext();
var model = dc.Models.FindOneById(pId);
if (model == null)
{
Session["error"] = "No model with this ID found.";
return this.RedirectToAction("");
}
return this.View(model);
}
When I debug, I received the test parameter in Edit Action with value of 10 correctly, but pId is null. Please tell me what I did wrong, and how to solve this problem?
I would suspect that the ObjectId is not serializing/deserializing correctly. Given that it doesn't make for a great WebAPI anyway, I generally use a string and convert within the method to an ObjectId via the Parse method (or use TryParse):
public ActionResult Edit(string id, int? test)
{
// might need some error handling here .... :)
var oId = ObjectId.Parse(id);
}
You can use ToString on the ObjectId to convert it to a string for calling:
var pId = model.Id.ToString();
This question already has answers here:
How to convert C# nullable int to int
(20 answers)
Closed 8 years ago.
Hi my create method receives an int how can I allow this to be null-able? So I can use this method without the int sometimes.
public ActionResult Create(int id)
{
var model = new Job { IncidentID = id };
ViewBag.ActionCode = new SelectList(db.ActionTypes, "ActionCode", "ActionCode");
return View(model);
}
Obviously I've tried
(int ? id)
but then here it is not happy as it cant convert int? to int here :
var model = new Job { IncidentID = id };
try this
public ActionResult Create(int? id)
{
var model = new Job { IncidentID = id.GetValueOrDefault(0) };
//or var model = new Job { IncidentID = (int.parse(id) };
ViewBag.ActionCode = new SelectList(db.ActionTypes, "ActionCode", "ActionCode");
return View(model);
}
GetValueOrDefault(0) helps to assigns zero if id has no value or null
or
try this
var model = new Job { IncidentID = id.HasValue ? id.Value : 0 };
Just check if id has value and assign IncidentID only if it has:
public ActionResult Create(int? id)
{
var job = new Job();
if (id.HasValue)
job.IncidentID = id.Value;
ViewBag.ActionCode = new SelectList(db.ActionTypes, "ActionCode", "ActionCode");
return View(job);
}
You may use nullable int as your method parameter. Nullable<T> has a HasValue method which check whether a value has been assigned to the nullable variable. If it returns true, use the Value property to get the value of the variable.
public ActionResult Create(int? id)
{
var model=new Job();
if(id.HasValue)
{
model.IncidentID=id.Value;
}
//to do :return something
}