Manually loading navigation properties in MVC 6 - asp.net-core-mvc

What is the best way to manually load navigation property in MVC 6?
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(Reservation reservation, bool ignoreConflicts = false)
{
int id = reservation.ItemID; // correct item ID
Item item = reservation.Item; // null
}
In this example, when user submits a form, I get a Reservation object with all navigation properties set to null.
The best way I can think of is manually looking up the Item DbSet to find the item with matching ID and assigning it to the navigation property.

Well, first thing first... you are in an MVC action. What you will receive will be from the client. Not the database directly.
So reservation.Item might not be null but it might not be the real data either.
In this scenario, you first have to retrieve your data from the server and then do one of two things:
Update the EF7 entity directly by hand from what you received from the client
Use TryUpdateModelAsync to update your current entity automatically.
It could be as simple as:
public ActionResult Update(Reservation reservation)
{
var item = context.Items.FirstOrDefault(x => x.ID == reservation.ItemID);
await TryUpdateModelAsync(item);
// todo: save context
// return....
}

This is an Entity Framework 7 issue, not an MVC6 one.
Lazy loading isn't implemented for EF7 yet, possibly it won't be at all.
You can track the ticket here https://github.com/aspnet/EntityFramework/issues/3797
This means instead we need to do it as you suggested; something like:
Item item = context.Items.FirstOrDefault(x => x.ID == reservation.ItemID);

Related

Proper way to Edit an entity in MVC 3 with the Entity Framework using Data Model First approach?

A majority of the examples I see now are either using the Code First Approach or using an older version of MVC and the Entity Framework.
Assume I have a movie to update and I get to the Edit View, in the Edit method with the Post verb, what is the proper way to update a Movie? The first Edit Method below gets me to the Edit View with the populated Movie values and the second one is the one I want to use to update, I have tried some things, but nothing updates the data.
public ActionResult Edit(int id)
{
var movie = (from m in _db.Movies1
where m.Id == id
select m).First();
return View(movie);
}
[HttpPost]
public ActionResult Edit(Movie movie)
{
try
{
// TODO: Add update logic here
//What do I need to call to update the entity?
_db.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
Assuming that _db is derived from ObjectContext you have two options:
Change the state of the entity to Modified:
_db.Movies1.Attach(movie);
_db.ObjectStateManager.ChangeObjectState(movie, EntityState.Modified);
_db.SaveChanges();
This marks all properties of movie as modified and will send an UPDATE statement to the database which includes all column values, no matter if the values really changed or not.
Reload the original entity from the database and apply the changes to it:
var originalMovie = (from m in _db.Movies1
where m.Id == movie.Id
select m).First();
// You actually don't need to assign to a variable.
// Loading the entity into the context is sufficient.
_db.Movies1.ApplyCurrentValues(movie);
_db.SaveChanges();
ApplyCurrentValues will mark only those properties as modified which really did change compared to the original and the UPDATE statement which will be sent to the database only includes the changed column values. So, the UPDATE statement is potentially smaller than in the first example but you have to pay the price to reload the original entity from the database.
Edit
How does the second code example work?
When you run a query using the context (_db) Entity Framework does not only retrieve the entity from the database and assign it to the left side of the query (originalMovie) but it actually stores a second reference internally. You can think of this internal context "cache" as a dictionary of key-value pairs - the key is the entity primary key and the value is the entity itself, the same object as originalMovie refers to.
ApplyCurrentValues(movie) looks up this entity in the context's internal dictionary: It takes the key property value Id of the passed in movie, searches for an entity with that key in the internal dictionary and then copies property by property from the passed in ("detached") movie to the internal ("attached") entity with the same key. EF's change tracking mechanism marks the properties as Modified which were actually different to create later the appropriate UPDATE statement.
Because of this internal reference to the original entity you do not need to hold your own reference: That's the reason why originalEntity is not used in the code. You can in fact remove the assignment to the local variable altogether.
The example would not work if you disable change tracking when you load the original entity - for example by setting _db.Movies1.MergeOption = MergeOption.NoTracking;. The example relies on enabled change tracking (which is the default setting when entities are loaded from the database).
I cannot say which of the two examples has better performance. That might depend on details like size of the entities, number of properties which have been changed, etc.
It's worth to note though that both approaches do not work if related entities are involved (for example movie refers to a category entity) and if the relationship or the related entity itself could have been changed. Setting the state to Modified and using ApplyCurrentValues both affect only scalar and complex properties of movie but not navigation properties.
Your second edit method should look something like this:
[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
var movie = (from m in _db.Movies1
where m.Id == id
select m).First();
if (TryUpdateModel(movie))
{
_db.SaveChanges();
return (RedirectToAction("Index"));
}
return View(movie);
}

MVC3 with EF 4.1 and EntityState.Modified

Updating an object with MVC3
I have a model that I can modify, please see the sample below:
[HttpPost]
public ActionResult Edit(Company c)
{
if (ModelState.IsValid)
{
db.Entry(c).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(c);
}
The model has other fields that are not showing in the view and cannot be modified by the user, but when I click the submit button the fields that were not showing in the view got set to null.
Can I somehow let EF know not to modify certain fields? Thanks.
Generally it is better not to bind to the entity object directly, rather create an edit model and bind to that.
After all.. whats to stop someone posting back values you don't want changed with this approach?
The main problem here is the fact that mvc model binding changes the properties in the model before its in a context therefore the entity framework doesn't know which values have changed (and hence which should be updated)
You've mitigated that slightly with db.Entry(c).State = EntityState.Modified; but that tells the entity framework that the whole record has been updated.
I would normally do the following:
Bind to a model specifically for this controller first
Create an instance of the entity class you want to update, set the Id accordingly and attach it to the context
Update the properties on the entity to be the same as the model you binded to (object is attached and therefore entity framework is tracking which columns are being changed now)
SaveChanges
Step 3 is a bit tedious therefore consider using a tool like automapper to make things easier
Edit:
[HttpPost]
public ActionResult Edit(Company c)
{
if (ModelState.IsValid)
{
Company dbCompayObjct = new Company { companyId = c.companyId };
db.Company.Attach(dbCompayObjct);
dbCompanyObjct.CompanyName = c.CompanyName;
dbCompanyObjct.City = c.City;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(c);
}
You are apparently overwriting your existing record with an incomplete record. When you use the method above, it will completely replace the existing one.
You either need to fill in all the fields you don't want to replace with the existing values, or you need to get the existing record and modify the fields you want to modify, then save it.
Reflection is not always evil, sometimes it's your friend:
public ActionResult Edit(Company c)
{
if (ModelState.IsValid)
{
Company UpdateC = db.Company.find(c.CompanyID);
foreach (var property in typeof(Company).GetProperties())
{
var propval = property.GetValue(c);
if (propval != null)
{
property.SetValue(UpdateC, propval);
}
}
db.SaveChanges();
return RedirectToAction("Index");
}
return View(c);
}

MVC 3 Details View

I am new to MVC frame work. And i am making one page where we can see details of department by clicking on details link button.
While User click link button it fetch the all the records of the particular department in List Collection and redirect to Details View.Data has been fetched in List but while going to Details view it Generates following error:
The model item passed into the dictionary is of type 'System.Collections.Generic.List1[DocPageSys.Models.Interfaces.DepartmentInfo]', but this dictionary requires a model item of type 'DocPageSys.Models.Interfaces.DepartmentInfo`'.
I understood the error but confusion to solve it.And stuck with this problem...
Since your Details view is strongly typed to DepartmentInfo:
#model DocPageSys.Models.Interfaces.DepartmentInfo
you need to pass a single instance of it from the controller action instead of a list:
public ActionResult Details(int id)
{
DepartmentInfo depInfo = db.Departments.FirstOrDefault(x => x.Id == id);
return View(depInfo);
}
So make sure that when you are calling the return View() method from your controller action you are passing a single DepartmentInfo instance that you have fetched from your data store.
To make it run fine initially you could simply hardcode some value in it:
public ActionResult Details(int id)
{
var depInfo = new DepartmentInfo
{
Id = 1,
Name = "Sales",
Manager = "John Smith"
}
return View(depInfo);
}
Oh, and you will notice that I didn't use any ViewData/ViewBag. You don't need it. Due to their weakly typed nature it makes things look really ugly. I would recommend you to always use view models.
Passing a list instead of a single item
This error tells you, that you're passing a list to your view but should be passing a single entity object instance.
If you did fetch a single item but is in a list you can easily just do:
return View(result[0]);
or a more robust code:
if (result != null && result.Count == 1)
{
return View(result[0]);
}
return RedirectToAction("Error", "Home");
This error will typically occur when there is a mismatch between the data that the controller action passes to the view and the type of data the view is expecting.
In this instance it looks as if you're passing a list of DepartmentInfo items when your view is expecting a single item.

How do I pass an object from the Index view to the edit view using MVC3

I have created a simple WCF service that is to be configured by an MVC3 UI.
When I call the index page from my controller, I want to display the values held in the configuration, which has been returned by the service. The user could then chose to edit these settings and then send them back to the service.
I want to do something like this in the index view ...
<div>
#Html.ActionLink("Edit", "Edit", model)
</div>
and then consume the model in the controller like this...
[HttpPost]
public ActionResult Edit( SettingsModel Config)
{
try
{
List<string> configErrors = null;
if (ModelState.IsValid)
{
// Set up a channel factory to use the webHTTPBinding
using (WebChannelFactory<IChangeService> serviceChannel = new WebChannelFactory<IChangeService>(new Uri(baseServiceUrl)))
{
IChangeService channel = serviceChannel.CreateChannel();
configErrors = channel.SetSysConfig(Config);
}
}
return RedirectToAction("Index");
}
catch
{
return View();
}
}
but this doesn't work.
Any suggestions???
When the form gets posted, all the input type fields data is collected and sent to the server. You can see this data using FireBug. The key point here is that, is the data that is being posted in a form, that MVC's default model binder can understand and map it to the model object, which is being passed as input parameter to the action method.
In your case, the model is of type "SettingsModel". You have to ensure that, the form data that is being posted is in format, that can be mapped to the "SettingsModel" object.
Same kind of question discussed in another thread : Can't figure out why model is null on postback?
Check Out this article : NerdDinner Step 6: ViewData and ViewModel
In the above article, carefully go through the "Using a ViewModel Pattern" section. My guess is that, this is what you are looking for.
You will need to post the values to populate the SettingsModel object on the Edit action. You can do this using hidden form fields if you don't want the user to see it. Otherwise you could have no parameters on the Edit action and make another call to the web service to populate the Settings model.

MVC - Dealing with the model and events on the page

I have what is probably a basic question regarding how to structure an MVC page.
Assume this is my model:
public class MyModel
{
int ProductId
List<ParameterTable> ParameterTables
...
[other properties]
...
}
ProductId initially won't have a value, but when its value is selected from a DropDownList it will trigger an event that retrieves the List items associated with that product.
My problem is when I do this AJAX call to get the parameter tables I'm not sure how to handle the response. I've only seen examples where people then manually inserted this data into the page via the jquery. This would mean handling displaying the data in your view (for the first time loading the page) and in the jquery (whenever it changes).
I am wondering if there's a way to somehow pass back a model of sorts that binds my return value of List into my page without needing to specify what to do with each value.
Would I have to have the changing of the ProductId DropDownList trigger an ActionResult that would reload the whole page to do this instead of a JsonResult?
You could return a partial view with your ajax call.
Controller action:
public ActionResult Filter(int productId) {
var product = _repository.Find(productId);
if (Request.IsAjaxRequest())
{
return PartialView("_Product", product);
}
else
{
return View(product);
}
}

Resources