EF Update with Generic Repository Error - asp.net-mvc

I am using a generic repository to be the go between between presentation and EF. I am getting the following error when I try to update.
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
I know that this is generated because EF can't have 2 entities out with the same ID. So I need to either detach or transfer the values to the new object. I am choosing to transfer. However I can't figure out how to get the identity in a way that I don't have to pass it in every time.
public virtual void Update(T entity)
{
Context.Entry(Context.Set<T>().Find(entity.~theentityid~)).CurrentValues.SetValues(entity);
Context.Entry(entity).State = EntityState.Modified;
}
EDIT: The line Context.Entry(Context.Set().Find(entity.~theentityid~)).CurrentValues.SetValues(entity); was added after I started getting the error in an effort to solve it.
My identity columns are not all named Id so I can't use that. Is there a generic way to grab the identity value no matter which entity type is passed in so that I can find the entity that is already attached.
EDIT: Based on the comments I am including more code. My GalleryCollectionRepository inherits the generic repository that is listed above.
private static IntranetEntities db = new IntranetEntities();
private GalleryCollectionRepository GalleryCollectionRepo = new GalleryCollectionRepository(db);
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "collectionID,name,description,createdOn,createdByCommonName,createdByUsername,expirationDate,workOrderID")] GalleryCollection collection)
{
if (ModelState.IsValid)
{
GalleryCollectionRepo.Update(collection);
GalleryCollectionRepo.Save();
return RedirectToAction("Index");
}
return View(collection);
}
The edit form is the standard autogenerated form.
This is a sample of the top section of the form the rest of the form is all standard stuff so didn't want to make the post really long but including it.
#model Marketing.Models.GalleryCollection
#{
ViewBag.Title = "Edit";
Layout = "~/Areas/Intranet/Views/Shared/_Layout.cshtml";
}
<h2>Edit</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Collection</h4>
<hr />
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.collectionID)
<div class="form-group">
#Html.LabelFor(model => model.collectionName, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.collectionName)
#Html.ValidationMessageFor(model => model.collectionName)
</div>
</div>

Related

How do I choose which column from the "foreign" table is displayed in the view?

I am building a simple ASP.NET MVC app with Entity Framework Database First that allows a user to edit tables in a database. One table has a foreign key to another table. I want the user to be able to change the foreign key value.
My question: How do I choose which column from the "foreign" table is displayed to the user in the view? I scaffolded the view out, but it is displaying the wrong column.
The foreign key is in the DealerAuto table, which has columns: DealerAutoID, DealerID, DealerMakeName, DealerModelName. For some reason, the dropdown in the view for DealerAutoID is pulling in DealerMakeName. I want it to pull in DealerModelName.
View.cshtml:
#model ModelYearChange.Models.DealerAutoTrim
[...]
<div class="form-group">
#Html.LabelFor(model => model.DealerAutoID, "DealerAutoID", new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("DealerAutoID", String.Empty)
#Html.ValidationMessageFor(model => model.DealerAutoID)
</div>
</div>
DealerAutoTrimController.cs:
public ActionResult Create()
{
ViewBag.DealerAutoID = new SelectList(db.DealerAutoes, "DealerAutoID", "DealerMakeName");
ViewBag.DealerModelName = new SelectList(db.DealerAutoes, "DealerModelName", "DealerModelName");
return View();
}
// POST: /DealerAutoTrim/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "DealerAutoTrimID,DealerAutoID,DealerTrimName,DealerTrimMSRP,DealerTrimMPG_City,DealerTrimMPG_Highway,DealerTrimBulletPoints,Year")] DealerAutoTrim dealerautotrim)
{
if (ModelState.IsValid)
{
db.DealerAutoTrims.Add(dealerautotrim);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.DealerAutoID = new SelectList(db.DealerAutoes, "DealerAutoID", "DealerMakeName", dealerautotrim.DealerAutoID);
return View(dealerautotrim);
}
Your list binding is kind of messed up :)
Firstly, you only need one SelectList parsed to the View. So in your controller, simply have
ViewBag.DealerAutoes = SelectList(db.DealerAutoes, "DealerModelId", "DealerModelName");
Note: the second SelectList parameter specifies what field becomes the "value" of the dropdown, with the third parameter defining what field becomes the "text" value.
Then in your view, you can simply have:
#Html.DropDownListFor(m => m.DealerAutoId, (SelectList)ViewBag.DealerAutoes)

ASP.NET MVC 4 Error Saving ViewModels

Can somebody help me on how to save and update data into multiple entities using a ViewModel?
I have a ViewModel that looks like this:
public class StudentViewModel
{
public Student student;
public StudentAddress studentAddress { get; set; }
public StudentPhoto studentPhoto { get; set; }
// Three entities are related to one to one relationship
public StudentViewModel()
{ }
}
My Controller is:
[HttpPost]
public ActionResult Create(StudentViewModel studentViewModel)
{
if (ModelState.IsValid)
{
return View(studentViewModel);
}
Student s = new Student()
{
Name =studentViewModel.Student.Name,
Speciality = studentViewModel.Student.Speciality,
DateOfReg = studentViewModel.Student.DateOfJoinig,
Qualification = studentViewModel.Student.Qualification,
Email = studentViewModel.Student.Email
};
StudentAddress sa = new StudentAddress()
{
StudentId= studentViewModel.Student.StudentId,
Address = studentViewModel.StudentAddress.Address,
Area = studentViewModell.StudentAddress.Area,
City = studentViewModel.StudentAddress.City,
State = studentViewModel.StudentAddress.State,
Mobile = studentViewModel.StudentAddress.Mobile
};
StudentPhoto sp = new StudentPhoto()
{
StudentId= studentViewModel.Student.StudentId,
Photo = studentViewModel.StudentPhoto.Photo
};
db.Students.Add(s);
db.StudentAddress.Add(sa);
db.StudentPhoto.Add(sp);
db.SaveChanges();
return RedirectToAction("Home");
}
View is:
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Doctor</legend>
#Html.EditorFor(model => model.Student.Name )
#Html.EditorFor(model => model.Student.Speciality)
#Html.EditorFor(model => model.Student.DateOfJoinig)
#Html.EditorFor(model => model.Student.Standard)
#Html.HiddenFor(model => model.Student.StudentId)
#Html.EditorFor(model => model.StudentAddress.Address)
#Html.EditorFor(model => model.StudentAddress.Area)
#Html.EditorFor(model => model.StudentAddress.City)
#Html.EditorFor(model => model.StudentAddress.State)
#Html.HiddenFor(model => model.Student.StudentId)
#Html.EditorFor(model => model.StudentPhoto.Photo)
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
I was able to retrieve and display the data (from multiple entities) into the view. However, now I'm stuck on how can I save and update the above entities with the new data. Most of the examples are 1-1 relationship the mapping is automatic, but in this case the data belongs to multiple entities.
My problem is when i try to save data it redirected to the create page. "ModelState.IsValid" is false always so no data saved. Please help me how do i proceed.
Thanks.
This line at the top of your Action is wrong:
if (ModelState.IsValid)
{
return View(studentViewModel);
}
It should be the opposite, only if the Model is NOT valid, then you should stop the process and re-render the View with the form.
Try:
if (!ModelState.IsValid)
{
return View(studentViewModel);
}
In Controller you check if(modelstate.isvalid) - if is valid you returned view without saving data from view.
The problem with your implementation is that your view model contains a several models(Entities). This is not a good implementation.
Try to create a viewmodel which just contains the fields (flattened version) that you want to be edited by the user when creating a student. Use Data Annotations in your view model like Required or StringLength to validate user inputs.

Custom update method in MVC 4 with WCF

I am a newbie in MVC and currently using MVC 4 + EF Code First and WCF in my web project. Basically, in my project, WCF services will get the data from database for me, and it will take care of updating data as well. As a result, when I finish updating a record, I have to call the service client to make the change for me other than the "traditional" MVC way. Here is my sample code:
Model:
[DataContract]
public class Person
{
[Key]
[DataMember]
public int ID{ get; set; }
[DataMember]
public string Name{ get; set; }
[DataMember]
public string Gender{ get; set; }
[DataMember]
public DateTime Birthday{ get; set; }
}
Controller:
[HttpPost]
public ActionResult Detail(int ID, string name, string gender, DateTime birthday)
{
// get the WCF proxy
var personClient = personProxy.GetpersonSvcClient();
//update the info for a person based on ID, return true or false
var result = personClient.Updateperson(ID, name, gender, birthday);
if (result)
{
return RedirectToAction("Index");
}
else
{
//if failed, stay in the detail page of the person
return View();
}
}
View:
#model Domain.person
#{
ViewBag.Title = "Detail";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Detail</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Person</legend>
#Html.HiddenFor(model => model.ID)
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Gender)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Gender)
#Html.ValidationMessageFor(model => model.Gender)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Birthday)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Birthday)
#Html.ValidationMessageFor(model => model.Birthday)
</div>
<p>
<input type="submit" value="Update"/>
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
The controller is the part I am confused of. The Detail function takes multiple parameters, how can I call it from the View? Also, what should I put into this return field in the controller:
//if failed, stay in the detail page of the person
return View();
We usually put the model in, but the model seems to be not changed, since I am updating the database directly from my WCF service.
Any suggestion would be really appreciated!
UPDATE:
I know I can probably get it works by change the update method to take only one parameter which is the model itself, but this is not an option in my project.
you call the Details action in the controller when you hit "Update"
//sidenote : use single parameter in your function that accepts the values it makes life easier
The form will call the post method in the controller that has the same name as the get method that rendered the view when it is submitted.
You can alter this default behavior by specifying parameters in the BeginForm method
#using (Html.BeginForm("SomeAction", "SomeController"))
Also, you are using a strongly typed view (good!), so you can change the signature of your post method to accept the model object
[HttpPost]
public ActionResult Detail(Person person)

Creating a Child object in MVC4 - Parent's information not being passed to Create() controller

I have the following [HttpGet] Create() method:
public ActionResult Create(int? parentId)
{
var model = new CreatePersonViewModel();
// pull parent from db
var parent = _db.Persons.FirstOrDefault(s => s.Id == parentId);
model.Parent = parentSet;
return View("Create", model);
}
If I'm creating a new Person from another person's Details page, I pass in the ID of that parent Person and then construct a viewModel with the Parent included.
The POST looks like this:
[HttpPost]
public ActionResult Create(CreatePersonViewModel viewModel)
{
if (ModelState.IsValid)
{
var parent = viewModel.Parent; // This is always null for some reason
var person = new Person() { Name = viewModel.Name };
// if it has a parent, build new relationship
if (parent != null)
{
person.Parent = parent;
parent.Children.Add(person);
};
_db.Save();
return RedirectToAction("detail", "person", new { personId = person.Id });
}
return View(viewModel);
}
For some reason the viewModel getting pushed back to the POST method never contains the Parent that was defined in the GET controller method. How can I tell MVC to push the parent from GET to POST, without muddling the View with a hidden field for Parent?
In case it helps, my view is here:
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>CreatePersonViewModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
The controller action will only receive data that is explicitly included in the HTML form.
If you want to receive the Parent object, you'll need to put it in hidden <input> tags.
Note that any data that comes from the client is under complete control of your attacker and cannot be trusted.
The web is stateless so how would the server know what data you wanted to pass behind the scenes?
You could use TempData to pass data and it will remain on the server until it is read by your next request. However I don't believe you need that here. Since you aren't using anything but the parent id and the name, store that on the client - ie your viewmodel contains only those fields- no entity in your viewmodel.
When you post to the server load your parent, assign a new child and save it. No sense sending the entire object to the client.
Also I would verify on te server that the current user has access to those records - if applicable here unless your application allows all users access to all persons.

ASP.NET MVC Persisting mdoel's ID value when Editing

public Edit(int? id){ /* Codes */ }
[HttpPost]
public Edit(Item model){ /* Codes */ }
I retrieve a copy of Item in the first Edit method, which would contain a value for ItemID. But when it gets to the HttpPost method, the id value's lost.
If switched to
public Edit(int? ItemID){ /* Codes */ }
[HttpPost]
public Edit(Item model){ /* Codes */ }
this way ItemID can be persisted in the Item model.
But is this a good way to handle it? Will ASP.NET MVC always be able to know that it needs to plug "ItemID" into Item?
and are there other ways to persist the ID value? Thanks.
I cannot understand how do you lose id at the HttpPost handling. Maybe you should check your binder and possibly write one for yourself? In my experience default binders are a little cumbersome. You could start from here although I don't pretend it's the best solution. In case you need to write many binders by hand take a look at some tools that could help you make conversion in declarative way like AutoMapper .
Have you tried adding the id as a parameter to the Post action?
public Edit(int? id){ /* Codes */ }
[HttpPost]
public Edit(int id, Item model){ /* Codes */ }
This way, when the form is posted back, the id will be populated from the URL.
Is the property on your Item model called ItemID? If so, then the default model binder won't populate it if you're passing around a field called ID. If you change your method signatures so that the parameter names match up with your Item property names it should work as expected.
Phil Haack had a post that may or may not be related to what you're doing.
Also, if you're not sending the ID out to the client as part of a form (hidden field or whatnot) and it isn't part of the POST URL then it would only make sense that you wouldn't have the ID field populated properly on POST.
The MVC 2 way of solving the ID issue is Optional URL Parameters.
If you're still on MVC 1 then use a binding attribute on the method argument:
[HttpPost]
public ActionResult Edit([Bind(Exclude = "ItemID")] Item model)
{
// ...
}
Little late answer but when using razor its common to use a hidden field in order to bind the Id to the model.
#Html.HiddenFor(model => model.Id)
A complete form post could look like this:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Address</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.Id)
<div class="form-group">
#Html.LabelFor(model => model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.StreetLine1, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.StreetLine1, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.StreetLine1, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}

Resources