Relationship field is not updated in Edit action (ASP.NET, EF5) - asp.net-mvc

I would really appreciate some insight on this: The following code
[HttpPost]
public ActionResult Edit(BeamCollection beamcollection)
{
if (ModelState.IsValid)
{
beamcollection.BeamMaterial = db.Types.Find(Convert.ToInt32(Request.Form.Get("BeamMaterial_ID")));
db.Entry(beamcollection).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Details", "Bridge");
}
return View(beamcollection);
}
When I attempt to modify a BeamCollection record, all changes are reflected and saved to the DB except for the beamcollection.BeamMaterial which takes the selected value from a DropDownList. When I debug, I can see that the selected value is being assigned to beamcollection.BeamMaterial!
By the way, this field is defined as follows
public virtual AllTypes BeamMaterial { get; set; }
So it reflects a one to many relationship with AllTypes, but it is a unidirectional relationship.
What is kind of strange (to me), is the the same technique is used for the Create action and it perfectly works:
public ActionResult Create(BeamCollection beamcollection)
{
if (ModelState.IsValid)
{
beamcollection.BridgeInfo = db.Bridges.Find(bridgeID);
beamcollection.BeamMaterial = db.Types.Find(Convert.ToInt32(Request.Form.Get("BeamMaterial_ID")));
db.BeamCollections.Add(beamcollection);
db.SaveChanges();
return RedirectToAction("Details", "Bridge");
}
return View(beamcollection);
}
Why is this happening and how to make it work, Please help.

try to use:
db.attach(beamcollection.GetType().Name,beamcollection);
db.ObjectStateManager.ChangeObjectState(beamcollection, EntityState.Modified);
db.SaveChanges();

Thanks to Fabrice's tip I managed to find the correct way, Here is the code:
var currentBeamCollection = db.BeamCollections.Find(beamcollection.ID);
db.Entry(currentBeamCollection).CurrentValues.SetValues(beamcollection);
currentBeamCollection.BeamMaterial = beamcollection.BeamMaterial;
db.SaveChanges();
The logic is as follows: get the original record, update all fields (except for the navigation properties, read below), update the navigation property, finally save.
When I tried to do the following
db.Entry(currentBeamCollection).CurrentValues.SetValues(beamcollection.BeamMaterial);
The system failed with an exception that complains about setting the ID property. I also read that CurrentValues.SetValues() doesn't update navigation properties, and I noticed that the BeamMaterial property was not being updated, so I needed to update it manually.
Thanks Fabrice.

Related

update a value in the control

Following is the default code in mvc, what I want is go give 'hint' a value in this function to let me known the data has changed
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "ID,categoryID,subTitle,subject,sen,image,hint")] List list)
{
if (ModelState.IsValid)
{
db.Entry(list).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("ListSpryList", new { id = list.categoryID });
}
return View(list);
}
I think I maybe should insert something like following, before the code "db.saveChanges()"
db.Engtry(list.hit).value="changed";
it is obvious wrong but something like this.
Oh, sorry everyone, it is a quite stupid question, only need to insert a sinple code
list.sen = "changed";
before
db.SaveChanges();
then I got what I want
Sorry everyone for waste your time.

MVC Controller using related data

Hope someone can help me with this.
I have a controller in my ASP.NET MVC project that is used for editing a so called survey form. The survey form has a 1-to-1 relation with objects of type Form. When saving the survey form I also want to set the name of the form. However the form property is null. The FormID property has the correct value (using database first, EF5). Here is the problem.
[HttpPost]
public ActionResult Edit(SurveyForm surveyform)
{
if (ModelState.IsValid)
{
db.Entry(surveyform).State = EntityState.Modified;
// This cannot be done because surveyform.Form is null
surveyform.Form.Name = "Wish this would work!";
db.SaveChanges();
}
}
My question is: how can I 'attach' surveyform to the model so that is loads related data?
Thanks in advance!
At a guess, I'd say the entity being POSTed back isn't being tracked, so Entity Framework doesn't realise it's from the database. The line db.Entry(surveyForm) gets the entry matching the POSTed form, but you're not retaining it. Try this:
[HttpPost]
public ActionResult Edit(SurveyForm surveyform)
{
if (ModelState.IsValid)
{
var formEntry = db.Entry(surveyform);
formEntry.State = EntityState.Modified;
formEntry.Form.Name = "This might work...";
db.SaveChanges();
}
}
Hopefully, the .Entry() will get you the database's version. Unfortunately, you'll probably have to copy the property values accross, although you might find you can use .Attach() and then copy over the navigation properties from the database's version.
Having said that, it's generally a good idea to not use your database models in the view if you can help it; separation of concerns and all that. If there's more than a couple of properties on that model that are needed for the database but not the view (or vice versa) then you might want to use a local view model for it and just copy the properties to a freshly-retrieved database entity.
Try:
db.Entry(surveyform).Reference(x => x.Form).Load();
surveyform.Form = db.Entry(surveyform).Reference(x => x.Form).CurrentValue;
In your model, make sure you have a navigation propery like this:
public virtual Form Form { get; set; }
And then in your DbContext:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<SurveyForm>().HasOptional(p => p.Form);
}
HasOption can be replaced with HasRequired.
I have never had the exact issue you are having, so I hope this helps.
Eventually I combined multiple suggestions above and ended up with the following.
I added the following to my view:
#Html.TextBox("formName", Model.Form.Name)
and then changed the code in my controller to:
[HttpPost]
public ActionResult Edit(string formName, SurveyForm surveyForm)
{
if (ModelState.IsValid)
{
var surveyFormEntry = db.Entry<SurveyForm>(surveyForm);
db.SurveyForms.Attach(surveyForm);
surveyFormEntry.State = EntityState.Modified;
surveyFormEntry.Reference(x => x.Form).Load();
surveyForm.Form = db.Entry(surveyForm).Reference(x => x.Form).CurrentValue;
surveyForm.Form.Name = formName;
db.SaveChanges();
return RedirectToAction("Index", new { surveyId = surveyForm.SurveyID });
}
It was a lot of trial and error. A way of programming I so disapprove on and dislike but I'm happy it finally works.
Thanks all!

How to check the original value of an attribute using MVC and EF

Need to compare the original value with the current value in order to do some updates on objects prior to save changes. How to get the original value of an object? The following does not work:
public ActionResult Edit(int id = 0)
{
Project project = db.Projects.Find(id);
...
db.Projects.Attach(project); // ATTACH here
...
}
[HttpPost]
public ActionResult Edit(Project project)
{
if (ModelState.IsValid)
{
db.Entry(project).State = EntityState.Modified; // The STATE was DETACHED here...
...
}
Is this a configuration issue, or did I miss something? Thanks in advance for any help!
The problem with your solution is the lifetime of the controller. By default a new instance of the controller is generated for each client request. This means that you need to load the original value inside the POST method of edit, because you cannot share the instance of your context.
I have edited the code sample after #Gerard's comment
[HttpPost]
public ActionResult Edit(Project project)
{
if (ModelState.IsValid)
{
var original_value = db.Projects.Find(project.ProjectId);
or
var original_value_detached = db.Projects.AsNoTracking().Where(P => P.ProjectId == project.ProjectId).FirstOrDefault();
}
}
Or you write your own implementation of the controller factory to share you context between two requests, but you should consider requests from different clients. You can find an example here
It looks like you are implementing concurrency. May be, you can try to catch your changes using DbUpdateConcurrencyException. Something like this:
try
{
...
db.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
var entry = ex.Entries.Single();
var dbValue = (Project)entry.GetDatabaseValues().ToObject();
if(dbValue.State == EntityState.Modified)
{
//***your code
}
}

Store update, insert, or delete statement affected

Im learning MVC 4. I have created a database first project using EF5. In my edit view I want to add a product number to a customer. When I hit save I get the message below. I think it is because product number is null in the product table, hence it cannot update. Can I get around this? I have added my edit control
public ActionResult Edit(int id = 0)
{
UserProfile userprofile = db.UserProfiles.Find(id);
if (userprofile == null)
{
return HttpNotFound();
}
//ViewBag.userId = new SelectList(db.Devices, "DeviceID", "DeviceIMEI", userprofile.UserId);THIS CREATES A NEW ENTRY IN USERPROFILE TABLE
ViewBag.Device_DeviceID = new SelectList(db.Devices, "DeviceID", "DeviceIMEI", userprofile.Device);
ViewBag.ShippingDetails_ShippingDetailsID = new SelectList(db.ShippingDetails, "ShippingDetailsID", "Address1", userprofile.ShippingDetails_ShippingDetailsID);
return View(userprofile);
}
//
// POST: /User/Edit/5
[HttpPost]
public ActionResult Edit(UserProfile userprofile)
{
if (ModelState.IsValid)
{
db.Entry(userprofile).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
//ViewBag.userId = new SelectList(db.Devices, "DeviceID", "DeviceIMEI", userprofile.UserId);
ViewBag.Device_DeviceID = new SelectList(db.Devices, "DeviceID", "DeviceIMEI", userprofile.Device);
ViewBag.ShippingDetails_ShippingDetailsID = new SelectList(db.ShippingDetails, "ShippingDetailsID", "Address1", userprofile.ShippingDetails_ShippingDetailsID);
return View(userprofile);
}
"Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries"
It looks like you dont pass Id of UserProfile from
view to controller.
You should add
#Html.HiddenFor(model => model.Id)
to your form in view
You're posting a view model, which is disconnected from your entity framework, and trying to tell the EF that it has changed -- which it doesn't know about. Try something like this instead,
var obj = yourContext.UserProfiles.Single(q=>q.Id==userProfile.Id);
obj = userprofile; // ... Map userprofile to the tracked object, obj
yourContext.SaveChanges();
Try this:
if (ModelState.IsValid)
{
db.UserProfiles.Attach(userProfile);
db.SaveChanges();
return RedirectToAction("Index");
}

How to pass thw Viewdata to all the views in my controller?

i have a dropdown list which select a value
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Screenname(FormCollection collection)
{
Viewdata["screenname"] = collection[0];
return RedirectToAction("Index", new { ScreenName = ViewData["screenname"] });
}
then i want to access this ViewData in other actions like this
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(FormCollection collection, string screenname)
{
try
{
/// thats my dataobject which creates
DataObj.SaveData(Guid.Empty, collection, screenname);
return RedirectToAction("Index", new { ScreenName = ViewData["screenname"] });
}
catch
{
return View("Error");
}
}
where index looks like this ...
public ActionResult Index(string ScreenName)
{
///thats my list
GetTable = new GetDataTable(ScreenName);
return View(GetTable);
}
First when i select the value and index gets executed properly.... but when i try to access the viewdata again it doesn't contain the value so anybody if please can help ...
or alternate method to save and retrieve data .
The ViewData object is specific for the particular action that is executing. To pass data between actions, use TempData. more on the difference between the two on MSDN.
You can also directly write to the session state through the Controller.Session property.
This has actually been covered quite often here. The solution for now is to use TempData to save the data you need before you use RedirectToAction().
If you do a search for "RedirectToAction" you'll find a number of posts covering this topic, such as this one.
The next official release of the framework will fix this.
I used a view to take the data from the user and then saved it to a static variable and then used this variable to pass the data to all the other views .
Thanks anyways

Resources