I'm designing forms in my ASP.NET MVC application that will receive objects. Here's what a typical edit action looks like:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, Person person)
{
peopleService.SavePerson(person);
return Redirect("~/People/Index");
}
The SavePerson call in the service does this:
public void SavePerson(Person person)
{
_peopleRepository.SavePerson(person);
}
And the SavePerson call in the repository does this:
public void SavePerson(Person person)
{
using (var dc = new SIGAPDataContext())
{
if (person.IsNew)
{
dc.People.InsertOnSubmit(person);
}
else
{
dc.People.Attach(person, true);
}
dc.SubmitChanges();
}
}
Now, this works well when I create the new record. However, when I update it without all the form elements, it nulls other fields. For instance, my Person model has a NationalityID property that is nulled if it doesn't show on the edit form.
What's the best practice to make the model update with just the fields that come from the form? Do I have to get the record from the database first and update it's properties manually like:
Person persistedPerson = peopleService.GetPerson(person.ID);
persistedPerson.Name = person.Name;
persistedPerson.DateOfBirth = person.DateOfBirth
// etc...
Or is there any other, cleaner way to do this?
Stephen Walther just posted an article describing this very thing in ASP.NET MVC. I would recommend you actually create your own set of DTO's and business objects on top of LINQ to SQL and treat LINQ to SQL as a language-integrated object database due to the gross complexities introduced by managing the DataContext, which according to Pro LINQ, should be kept alive as little as possible by design.
If you are going to use your LINQ to SQL entities as your DTO's, you should get the entity first, detach it, update it, re-attach it, then submit it.
In your controller, retrieve the existing person from the repository, then use UpdateModel/TryUpdateModel and pass in a whitelist of properties that you want to be updated. This would use the ValueProvider on the controller so there is no need to take the Person object in the action parameter list.
public ActionResult Edit( int id )
{
Person person = peopleService.GetPerson(id);
UpdateModel(person,new string[] { list of properties to update } );
peopleService.SavePerson(person);
...
}
Related
I am trying to update an entity using Entity Framework and save it to the database. When the update is called, my service method retrieves the DTO, assigns its the values of the entity object that the UI passed to it, and then saves it to the database. Instead of manually assigning those values, i'd like to use Automapper, however when I do this, the values that I am not mapping are updated to null. Is there a way in Entity Framework or Automapper to prevent this?
Service method finds the existing object in the database, assigns the new entity's properties to it, then saves:
public void Update(MyEntity updatedEntity, int id)
{
var existingObject = db.tblmyentity.Find(id);
existingObject.name = updatedEntity.name;
existingObject.address = updatedEntity.address;
existingObject.phone = updatedEntity.phone;
db.SaveChanges();
}
However, there are values stored in fields of this object not accessible by the UI, such as who modified the object and when. Using AutoMapper to simplify this code (shown below) causes these fields to update to null:
public void Update(MyEntity updatedEntity, int id)
{
var existingObject = db.tblmyentity.Find(id);
Mapper.Map(updatedEntity, existingObject);
db.SaveChanges();
}
A good practice is to create a (service, api) model that contains only the relevant properties that can be updated. E.g.:
public class MyEntityServiceModel
{
public string name { get; set; }
public string address { get; set; }
public string phone { get; set; }
}
// this looks differently in recent versions of AutoMapper, but you get the idea
Mapper.CreateMap<MyEntityServiceModel, MyEntity>();
// your update functions looks the same, except that it receives a service model, not a data model
Update(MyEntityServiceModel updatedEntity, int id)
{
// same code here
}
This approach has the following advantages:
you obtain what you are asking for
safety: you do not risk updating more properties than you should since the service model clearly specify the properties that should be updated
serialization: the service model is more appropriate if you need serialization (EF models may include unwanted navigation properties)
Update function consumer becomes unaware of the data persistence library you are using.
I got one question related to my model you can see in the picture below.
As you can see I got 3 entities and 1:n and m:n relations between them.
I want that I can edit these models through a web interface. Therefore I scaffold (add controller with entity framework) these three models and got edit/delete/create/ views and of course one controller for each entity.
But there is no input/fields created for the relations automatically by VS. So I thought to implement them manually. Before I want to do that is there an simpler way to implement/scaffold this model, so I can even edit the relations(Checkboxes or (multi)select would be the best)?
Thanks in advance!
For one-many you can use a DropDownList for Tip in the Partner View (see Scott Allen's solution. Many-many can be handled by ViewModels and JavaScript frameworks like Knockout.
No, the scaffolds are intentionally unopinionated here, as there's many different ways you could handle this. Perhaps you just want to choose from a select list? Maybe you want checkboxes, instead? Or, maybe you want to actually add/edit related items inline? And with that last one, would you like to post all at once or use AJAX?
So, instead of picking for you, the framework rightly leaves the decision up to you, since only you know how your application should be built. Regardless, relying on the scaffolds is going to bite you more often than not. They only work in the most basic and ideal scenarios, and when have application requirements ever been either basic or ideal? I don't even bother with them at this point, preferring to just create my controllers/views manually. It ends up being quicker than dealing with the scaffold and undoing all the things that aren't applicable.
So, since you're looking for select boxes (either single-select or multi-select), first, I'd recommend creating view models for your entities. For example, with Tip:
public class TipViewModel
{
[Required]
public string Name { get; set; }
[Required]
[DataType(DataType.MultilineText)]
public string Description { get; set; }
[Required]
public int? SelectedPartnerId { get; set; }
public IEnumerable<SelectListItem> PartnerChoices { get; set;}
[Required]
public int? SelectedBookId { get; set; }
public IEnumerable<SelectListItem> BookChoices { get; set; }
}
Here, I've added nullable int (using a nullable allows them to be initially unselected, instead of just set to the first option) properties to track the id of the selected Book/Partner because it doesn't appear you have explicit properties on your entities for the foreign keys. That's fine, but it doesn't make it slightly more complicated to save the relationship, as you'll see in a bit. If you did have explicit foreign key properties, then you should mirror those in your view models instead.
Now in the GET version of your action, you'll need to do something like the following:
public ActionResult Create()
{
var model = new TipViewModel();
PopulateChoices(model);
return View(model);
}
...
protected void PopulateChoices(TipViewModel model)
{
model.PartnerChoices = db.Partners.Select(m => new SelectListItem
{
Value = m.Id.ToString(),
Text = m.Name
});
model.BookChoices = db.Books.Select(m => new SelectListItem
{
Value = m.Id.ToString(),
Text = string.Format("{0} by {1}", m.Name, m.Author)
});
}
I've abstracted out the code for populating these select lists because the code will be used multiple times throughout your controller. Also, I used string.Format on the Text value for the books just to show that you can do whatever you want with the text for the select list item. Also, the code above would be for a create action, obviously. Doing an edit would be similar but slightly different:
public ActionResult Edit(int id)
{
var tip = db.Tips.Find(id);
if (tip == null)
{
return new HttpNotFoundResult();
}
var model = new TipViewModel
{
Name = tip.Name,
Description = tip.Description,
SelectedPartnerId = tip.Partner != null ? tip.Partner.Id : new int?(),
SelectedBookId = tip.Book != null ? tip.Book.Id : new int?()
}
PopulateChoices(model);
return View(model);
}
The main difference is that you're obviously dealing with an existing instance so you need to pull it from the database. Then, you just need to map the data from your entity onto your view model. Since, again, you don't have explicit foreign key properties, you have to do a little extra leg work to get the currently chosen Partner/Book values, otherwise you could just copy the values for the foreign key properties over directly. Also, here, I'm just doing a manual mapping, but there's third-party libraries to make this task easier (see: AutoMapper).
With that, you can implement your views. Everything will work the same as it did when you were using the entity directly, you just need to make a couple of modifications. First, you'll need to change your view's model declaration:
#model Namespace.To.TipViewModel
Then, add the select lists for your two related properties:
#Html.DropDownListFor(m => m.SelectedPartnerId, Model.PartnerChoices)
...
#Html.DropDownListFor(m => m.SelectedBookId, Model.BookChoices)
The fun happens in the POST version of your actions. Most of the code will stay the same from the GET version, but now you'll have an if (ModelState.IsValid) block:
[HttpPost]
public ActionResult Create(TipViewModel model)
{
if (ModelState.IsValid)
{
// map the data from model to your entity
var tip = new Tip
{
Name = model.Name,
Description = model.Description,
Partner = db.Partners.Find(model.SelectedPartnerId),
Book = db.Books.Find(model.SelectedBookId)
}
db.Tips.Add(tip);
db.SaveChanges();
return RedirectToAction("Index");
}
// Form has errors, repopulate choices and redisplay form
PopulateChoices(model);
return View(model);
}
The edit version, again, is similar, except you're going to map onto you existing instance, for example:
tip.Name = model.Name;
tip.Description = model.Description;
tip.Partner = db.Partners.Find(model.SelectedPartnerId);
tip.Book = db.Books.Find(model.SelectedBookId);
That's all there is to it for reference properties. You don't actually have any thing that's M2M or even one-to-many on your entities in your question. Everything is one-to-one, but if you did have a collection property, you'd need to handle it slightly differently. You still need a property on your view model to hold the selected values and the available choices:
public List<int> SelectedFooIds { get; set; }
public IEnumerable<SelectListItem> FooChoices { get; set; }
Populating the choices would also be the same. The options are the options; it doesn't matter if you're select just one or many as far as that is concerned.
Mapping onto your entity in your create action would be different though, as you'd need to select all of the chosen items from the database and set your collection property on your entity to that:
var tip = new Tip
{
...
Foos = db.Foos.Where(m => model.SelectedFooIds.Contains(m.Id)),
}
And, you'd need to make changes to both the GET and POST versions of your edit action. For the GET, you need to condense your collection property down to a list of ids:
var model = new TipViewModel
{
...
SelectedFooIds = tip.Foos.Select(m => m.Id).ToList(),
}
And in the edit version, you set new selected items:
tip.Foos = db.Foos.Where(m => model.SelectedFooIds.Contains(m.Id);
Finally, in your views, you'd use ListBoxFor instead of DropDownListFor to enable the multiselect:
#Html.ListBoxFor(m => m.SelectedFooIds, Model.FooChoices)
I'm having trouble figuring out how to edit an existing entity when posting it to my controller. When I save a new Person, it works just fine, because Id isn't set, so NHibernate views it as a new entity and attaches it to the session. However, when I try to edit an existing entity, the MVC model binder can't set the Id, even though the JSON being posted has it properly set. So even though it's an existing entity, NHibernate again sees it as a new one, and then throws an exception because I'm calling .Update() on an entity that's not in the database or session.
Here's the code I'm using (obviously Person has a lot more properties, I just left them off to keep the code short)
Person class:
public class Person
{
public virtual int Id {get; private set;}
//... other properties
}
The JSON being posted to my edit action
{"Id": 10}
And in the controller
public JsonResult EditPerson(Person person)
{
Session.Update(person);
return Json(new { success = true});
}
I was always under the impression that you had to load the entity to get it into the session so that you could edit it.
so you would need code like
var entity = Session.Get<Entity>(entity.Id);
//make your changes
Session.Save(entity);
Try
public virtual int Id {get; protected set;}
NHibernate uses proxies to load and set the properties of your classes, if your setter is private (rather than public or protected) the proxy (which inherits from your mapped class) cannot access it and assign the value it loaded from the database.
I've been tasked with the glorious mission of modernizing a CRUD system (Ironic). I'm using EF 6.1.1 and WebApi. I send the Entity using a DTO (dynamic object for now will create a static type when all is working, will also make the calls async)
[HttpGet]
public dynamic Get(int id)
{
var p = personRepository.Get(id);
return new
{
p.PersonId,
p.BirthYear,
p.DeathYear,
p.Comment,
PersonNames = p.PersonNames.Select(pn => new { pn.PersonId, pn.PersonNameId, pn.NameTypeId, pn.Firstname, pn.Prefix, pn.Surname })
};
}
I then update fields on the Person object or add/remove/update PersonNames collection using Knockout and ko.mapping
I post the result to this WebApi method
[HttpPost]
public void Post([FromBody]Person person)
{
personRepository.Merge(person);
}
All the id's etc are correct, in personRepository.Merge i've tried
public void Merge(Person person)
{
db.People.AddOrUpdate(person);
db.SaveChanges();
}
This works for the fields directly on the personobject, but it does not work with add/remove/update on the PersonNames
Can I support this without writing manual merge code?
Solution
Ended up doing this
public void Merge(Person person)
{
db.People.AddOrUpdate(person);
var existingNames = db.PersonNames.Where(pn => pn.PersonId == person.PersonId).ToList();
var deleted = existingNames.Where(pn => person.PersonNames.All(d => d.PersonNameId != pn.PersonNameId));
db.PersonNames.RemoveRange(deleted);
foreach (var name in person.PersonNames)
{
db.PersonNames.AddOrUpdate(name);
}
db.SaveChanges();
}
Given you've mentioned this is a CRUD system this all looks sensible to me. I can't see how you can really avoid mapping from your DTO's back to your domain entities (e.g. Person)
I expect you've thought of it already, but how about removing as much boiler plate merge code as possible using something like AutoMapper? https://github.com/AutoMapper/AutoMapper
Mapping does get complicated for navigational properties (i.e. mapping between an object graph of DTO's back to an object graph of entities). This link goes into far more detail than I can: http://rogeralsing.com/2013/12/01/why-mapping-dtos-to-entities-using-automapper-and-entityframework-is-horrible/
Consider a typical NHibernate context class.
public class SampleContext : NHibernateContext
{
public SampleContext(ISession session)
: base(session)
{ }
public IQueryable<Person> People
{
get { return Session.Linq<Person>(); }
}
public Person GetPerson(int id)
{
get { return Session.Linq<Person>().SingleOrDefault(p => p.ID == id); }
}
}
My question:
How could I rewrite the GetPerson method to ignore the cache and retrieve data directly from the database?
There are a couple of ways to approach this problem:
The Hibernate guys will tell you that you probably should be opening a different session in order to retrieve the latest data from the database. They would point out that the intention of the session is to be scoped to a relatively short-lived unit of work.
You could either put in a call to Session.Refresh() inside your GetPerson() method to always get the most current state from the database or you could expose that functionality through your own Refresh() method.
Alternatively, if you have a handle on the Person object itself, you could also try a Session.Evict() to remove the Person object your session cache prior to loading it again.
In my experience, I've tried both #2 and #3 and have eventually always come around to refactoring to do #1.