I am using Automapper to copy the data from my data objects to my view model objects.
cfg.CreateMap<Customer, CustomerVM>().ReverseMap();
cfg.CreateMap<Order, OrderVM>().ReverseMap();
There is a navigation property between the Order and the Customer. This works fine for displaying the complex objects on my view.
#Html.EditorFor(a => a.Customer.AccountName)
#Html.EditorFor(a => a.Notes)
And the objects seem to map back properly in my post action.
[HttpPost]
public ActionResult SaveOrder(OrderVM order)
{
if (ModelState.IsValid)
{
var order2 = AutomapperConfig.MapperConfiguration.CreateMapper().Map<OrderVM, Order>(order);
order2.LastEditDate = DateTime.Now;
order2.LastEditBy = HttpContext.User.Identity.Name;
order2.CreateDate = order.CreateDate;
using (var db = new ProofingContext())
{
db.Entry(order2).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("index");
}
}
return View();
}
When I step thru the code in the post action the view model shows the updated data in all the related objects. But when I save the changes I only get the changes to the order saved to my DB. What do I need to do to save the changes to the related objects too?
From this link(Attaching an existing but modified entity to the context section):
Note that if the entity being attached has references to other
entities that are not yet tracked, then these new entities will
attached to the context in the Unchanged state—they will not
automatically be made Modified. If you have multiple entities that
need to be marked Modified you should set the state for each of these
entities individually.
As an alternative, there is a library (GraphDiff) that allows you update the entire graph of detached entities. For more info take a look this post.In your case it would be:
context.UpdateGraph(order2, map => map.OwnedEntity(x => x.Customer)
.OwnedCollection(p => p.Notes));
Related
I've been looking around and can't quite find the answer. I'm using a ViewModel in my Edit View so that I can have values for some dropdownlist. Now when I go to update my DB I'm not understanding how I can update my database record. I'm guessing I could create a new entity object, do a Find, and then update each property based on the ViewModel passed in from the Form but that sure seems like a lot of manual work.
Here I'm using the VeiwModel in the Edit View.
#model CPPCustomerCall.ViewModels.CustomerCallVM
Here is my controller's ActionResult. I changed the object type of the ActionResult to take in CustomerCallVM instead of the CustomerCall which was auto-generated. I assume since the Edit View's model is the ViewModel that's the type of object the ActionResult will receive. However, my ViewModel has more properties that aren't needed for the Entity Model to update the record. How do I go about updating my DB record in this ActionResult?
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit([Bind(Include = "Id,CustomerName,Subject,PhoneNumber,CallMessage,CallDate,Status,CallNotes")] CustomerCallVM customerCall)
{
if (ModelState.IsValid)
{
db.Entry(customerCall).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(customerCall);
}
First, Bind and view models are mutually exclusive. If you don't want something to be eligible to be modified, then it shouldn't be on your view model, in the first place. Barring that, view models diverge from entities in the sense that they can't be saved directly. As a result, there's always some intervention present on your part to map the posted values back onto the entity, which means you can then selectively not map over certain properties that shouldn't be, regardless of whether they were posted or not. Long and short, get rid of the Bind stuff. It's just something else to maintain and a huge source of potential bugs.
That said, the code you have is workable; you're just missing the crucial part where you map the data from your view model back onto your entity. First, you need to fetch the entity from the database so you have a base to work from:
var customerCall = db.CustomerCalls.Find(id);
if (customerCall == null)
{
return new HttpNotFoundResult();
}
FWIW, your edit route should include the id in the route, according to REST conventions. Following REST isn't strictly required, but it's certainly recommended. While a web application adhering to REST doesn't mean it's a good application, not adhering to rest is generally a sure sign of a badly designed and coded application.
Then, you map over your properties. You can either do this manually:
customerCall.CustomerName = model.CustomerName;
// etc.
Or you can use a library like AutoMapper.
mapper.Map(model, customerCall);
AutoMapper requires a bit of initial setup to make this magic work, of course, so review the docs, if you're going that route. Manual mapping is easier, but far more tedious and repetitive.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit([Bind(Include = "Id,CustomerName,Subject,PhoneNumber,CallMessage,CallDate,Status,CallNotes")] CustomerCallVM customerCall)
{
if (ModelState.IsValid)
{
// Find The record you need
var dbObj = CustomerCalls.FirstOrDefault(x=> x.id = customerCall.id);
//Check if null
if(dbObj == null) dbObj = new CustomerCall();
/// Map your properties
// Add object to the stack
if(dbObj.id == 0){
CustomerCalls.Add(dbObj);
}else{
CustomerCalls.Update(dbObj);
}
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(customerCall);
}
I'm debugging this method for two cases: one where there is a parent, the other where there is no parent.
If there is no parent, the new Person has an id of 0 but never actually gets saved to the db.
If there is a parent, the new Person has an id of 0 in this method, but a new record is inserted into the db with the correct value (one more than the highest in the table).
What is going on here? I know I'm doing something wrong, I'm just not sure what.
I'm using EF Codefirst.
The code for the controller method:
[HttpPost]
public ActionResult Create(CreatePersonViewModel viewModel)
{
if (ModelState.IsValid)
{
var parent = _db.Persons.FirstOrDefault(s => s.PersonId == viewModel.ParentId);
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.PersonId });
}
return View(viewModel);
}
If there is no parent, the new Person has an id of 0 but never actually gets saved to the db.
That's because you never tell EF that it should persist the entity. You only create a new Person() and that's it.
You should do:
dbContext.AddToPersons(person);
before calling dbContext.SaveChanges().
In the case when there is a parent, person is saved because of its relationship with parent.
Update
Just occurred to me: If you're doing code first you might not have the AddToPersons(...) method available on the data context. If this is so, you can use dbContext.Persons.AddObject(person) instead.
The fact with you are referring to is auto increment ID for you object. It is controlled by your ORM. You may want to check this question
You may want to check this link from msdn
Remarks Refresh has the dual purpose of allowing an object to be
refreshed with data from the data source and being the mechanism by
which conflicts can be resolved. For more information, see Saving
Changes and Managing Concurrency (Entity Framework). The order in
which objects are refreshed is nondeterministic. After the Refresh
method is called, the object’s original values will always be updated
with the data source value, but the current values might or might not
be updated with the data source value. This depends on the
RefreshMode. The StoreWins mode means that the object should be
updated to match the data source values. The ClientWins value means
that only the changes in the object context will be persisted, even if
there have been other changes in the data source. To ensure that an
object has been updated by data source-side logic, you can call the
Refresh method with the StoreWins value after you call the SaveChanges
method.
I have this code in a Windows Service targeted to .Net 4.5 that uses a database-first Entity Framework layer:
var existingState = DataProcessor.GetProcessState(workerId);
existingState.ProcessStatusTypeId = (int)status;
existingState.PercentProgress = percentProgress;
existingState.ProgressLog = log;
DataProcessor.UpdateProcessState(existingState);
And this code in a data processing class in the same solution:
public ProcessState GetProcessState(int id)
{
using (var context = new TaskManagerEntities())
{
var processes = (from p in context.ProcessStates.Include("ProcessType").Include("ProcessStatusType")
where p.IsActive && p.ProcessStateId == id
select p);
return processes.FirstOrDefault();
}
}
public ProcessState UpdateProcessState(ProcessState processState)
{
using (var context = new TaskManagerEntities())
{
context.ProcessStates.Add(processState);
context.Entry(processState).State = System.Data.EntityState.Modified;
context.SaveChanges();
}
return processState;
}
ProcessState is a parent to two other classes, ProcessStatusType and ProcessType. When I run that code in the windows service, it retrieves a record, updates the entity and saves it. Despite the fact that the ProcessType child is never used in the above code, when the save on the ProcessState entity is performed, EF does an insert on the ProcessType table and creates a new record in it. It then changes the FK in the ProcessStatus entity to point it at the new child and saves it to the database.
It does not do this in the ProcessStatusType table, which is set up with an essentially identical FK parent-child relationship.
I now have a database full of identical ProcessType entries that I don't need, and I don't know why this is occurring. I feel like I'm making some obvious mistake that I can't see because this is my first EF project. Is the issue that I'm allowing the context to expire in between calls but maintaining the same entity?
Using Add will set the state of all elements to Added, which is causing the child elements to be inserted. The parent element is not inserted as you specify EntityState.Modified for this element.
Try using the following in the UpdateProcessState rather than using Add.
context.ProcessStates.Attach(processState);
context.Entry(processState).State = EntityState.Modified;
context.SaveChanges();
Attach will set the state of all elements to Unchanged and by specifying Modified for the parent element you are indicating that only this element should be updated.
On another note. You should use the strongly-typed Include(x => x.ProcessType) rather than Include("ProcessType").
I am working on an MVC application which used data from different entities as well showing current date. I want to pass this data from controller to view. Should i create one entity to hold other entities ?
You can make a view Model and put everything you need in View in this viewmodel.
public class MyViewModel
{
Entity1 Ent{get;set;}
Entity2 Ent2{get;set;}
DateTime CurrentDate{get;set;}
}
public ActionResult index()
{
MyViewModel model = new MyviewModel();
model.Ent = new Entity1();
model.Ent2 = new Entity2();
model.CurrentDate = DateTime.Now;
return View(model)
}
your view must now accept VieModel instead of db generated entity. in view you can access entities like
<%:Model.Ent1.SomeProperty%>
<%:Model.CurrentDate%>
<%:Model.Ent2.SomeProperty2%>
What I would do is to create a ViewModel that will only hold the information specific for a View, not the whole entity.
Do you really require a separate entity? What I am getting at is why not create an anonymous object which returns the entities required or just the properties required. A potential problem may occur by having an entity for every data scenario.
Earlier I created an AddClient page that (when posted) passed a client object and I used db.AddToClient(obj) in my repository to persist it. Easy stuff.
Now, I have a details page whose save submits a post to an action "UpdateClient". Before that action is hit, my custom model binder creates my Client object and it's conveniently handed to the action. The thing is, this client object is not connected to a EF context yet. Where is the correct place to do that? In the modelbinder, or maybe when we get it from the controller, or perhaps we wait until we do a repository call and link it up there? What's the recommended process?
From what I remember, you will either need to attach the object again to the context and set it as modified
Or reload the object from the database and apply your changes.
This article explains it better:
http://msdn.microsoft.com/en-us/magazine/ee321569.aspx#id0090022
What version of EF are you using ?
If an EF object has been created outside a context, you need to Attach the object to the context.
See: http://msdn.microsoft.com/en-us/library/bb896271.aspx
I know this is an old thread but in the interest of new readers:
Based on my observations using VS 2012, MVC 4 and EF 4.0 with a view that has an EF object for a model that submits a form back to the controller.
On the controller:
public ActionResult SubmitEFObject(tblData data, FormCollection col)
"data" will only have the properties used in the view (#Html.xxxFor) filled.
It appears that when "data" is created, the posted FormCollection is used to set data's properties. If you had a property that wasn't used, DataID for example, then data.DataID will have a null/default value. Add a "#Html.Hidden(m => m.DataID)" to your view and THEN DataID will be filled.
As a 'quick n dirty' way to work with this, I created a method that would merge the incoming 'data' with the 'data' in the database and return the merged object:
// Note: error handling removed
public tblData MergeWithDB(DBContext db, tblData data, params string[] fields)
{
tblData d = db.tblData.Where(aa => aa.DataID == data.DataID).Single();
if (fields.Contains("Field1")) d.Field1 = data.Field1;
if (fields.Contains("Field2")) d.Field2 = data.Field2;
if (fields.Contains("Field3")) d.Field3 = data.Field3;
// etc...
return d;
}
On the controller:
public ActionResult SubmitEFObject(tblData data, FormCollection col)
{
DataEntities db = new DataEntities();
tblData d = MergeWithDB(db, data, col.AllKeys);
db.SaveChanges();
}
You could make this more generic using reflection or maybe more efficient by looping through the string[] fields instead of all the ifs but for my purposes this was 'good enough'.
Database work should be put in the repository call.
Are you directly binding to an entity framework object in your model binding? If not, you should consider do some mapping between your custom object and entity framework object.