Quantcast
Channel: West Wind Message Board Messages
Viewing all articles
Browse latest Browse all 10393

Re: Saving changes with EfCodeFirst business wrapper?

$
0
0
Re: Saving changes with EfCodeFirst business wrapper?
West Wind Web Toolkit for ASP.NET
Re: Saving changes with EfCodeFirst business wrapper?
Dec. 16, 2012
09:50 pm
3O31AU0DGShow this entire thread in new window
Gratar Image based on email address
From:Matt Slay
To:Rick Strahl
Ok... I'm now inheriting my QuoteViewModel from your BaseViewModel class, and I've got the validation error message rendering working in the UI form.


I see in your MVC code base for the BaseViewModel class that the ErrorDisplay object is defined as null, and I don't see anywhere where it get sets to an instance of the ErrorDisplay class. So, I had to add a line in my Controller action to create a new ErrorDisplay instance and set it on the local view model, or else it gave errors when trying to set validation errors, because the ErrorDisplay object property was still null.

In any case, that whole thing actually works, such that when I have failed validation, it re-paints the form and shows a bulleted list of errors. Very nice!!

Here's my whole deal (see below) on handling the save post-back. Notice how I'm doing a little work to rebuild the view model, since it needs populated child objects which were not pasted back in the model at initial post-back. But they have to be there when I render the view again.

if (!quoteBO.Validate()) { [HttpPost] public ActionResult Edit(QuoteViewModel model) { var quoteBO = new busQuote(model.Quote.id); // passing id param will auto load the Quote Mapper.Map(model.Quote, quoteBO.Entity); if (!quoteBO.Validate()) { var viewModel = new QuoteViewModel(); viewModel.Quote = quoteBO.Entity; Mapper.Map(model.Quote, viewModel.Quote); AddCustomerSelectListToViewModel(viewModel); viewModel.ErrorDisplay = new Westwind.Web.Mvc.ErrorDisplay(); viewModel.ErrorDisplay.AddMessages(quoteBO.ValidationErrors); return View(viewModel); } if (!quoteBO.Save()) { model.ErrorDisplay = new Westwind.Web.Mvc.ErrorDisplay(); model.ErrorDisplay.ShowError(quoteBO.ErrorMessage); } return RedirectToAction("Index"); }div>

Here's what I added to the view to check for and render the error messages:

@if (Model.ErrorDisplay != null) { @Html.Raw(Model.ErrorDisplay.Render()); }



Matt,

ViewModels are a good idea, but it doesn't automatically mean you can't bind to underlying entities. Creating binding only ViewModels is great, but it's a lot of work, but you still can bind to the underlying entities as long as you bind them below the top level.

To me the important reason for ViewModels are: Ability to send more than just a single entity into the View (and out)! So the ability to send error messages, authentication information and potentially multiple entities to bind to.

So you can easily have:

publicclass MyViewModel : BaseViewModel {// a full model entitypublic Quote Quote {get; set; }// custom properties for logic in the viewpublicstring ErrorMessage {get; set; }publicbool IsAdmin {get; set; }// special formatting fieldspublicstring QuoteTime {get; set; } }

So although you end up creating a new type you're not recreating the entire Quote object's properties in mappings.

This isn't the purist approach, but I found it actually works pretty well because 95% of bindings end up being 1-1 between model and the captured view data - recreating new types for the ViewModel is often silly. Creating custom properties on the ViewModel for just the things that require special formatting or handling on the View model makes this palatable.

In some cases it also makes sense to create completely custom ViewModels especially in those cases where you capture input that has nothing to do with the model. This can often happen for admin tasks where you're just looking for generic user input to act upon.

Having a standard ViewModel base approach makes all of these feel the same though. For this reason it's often a good idea to use a ViewModel base class. If you download the Web Toolkit look in Westwind.Web.Mvc and the BaseViewModel to get an idea. I have an ErrorDisplay on this thing that can handle displaying errors easily in the page, plus a user management component that can automatically read/write user cookie state and make that availalbe in the UI. You can do all sorts of cool stuff with the ViewModel in this fashion.

+++ Rick ---



Tools like AutoMapper certainly help with transferring property values from the view model to the data entity, but they do not help with the initial headache of defining the view model classes, which often essentially wind up looking very much like the data entity classes, in many cases every property must be duplicated, and all this is done by hand. That's the big headache to me.

EF doesn't work if you just 'assign' an entity. Entities have to be created through EF or be attached in some way. IOW, you can't just use the quote object and assign it to Entity and expect that to work.

Using the bus object you can use quoteBO.Attach(quote). With plain EF the dbSet has a similar function with options for setting the change state (which the BO method does automatically).

This should work.

HOWEVER! This is not the right way to do this.

For one thing it's really bad practice to pick up a quote object as a parameter and then post it to the database. What if some robot decides to send you a quote object with a valid id and only one value filled? At that point your entity would actually write out a single property and a bunch of empty property effectively overwriting the entire record in the database. It also means that if you have POST data on a View that every field has to be there! If it's not those missing fields effectively get wiped out.

A better way is to read the values from the quote and assign them - either manually or using something like AutoMapper or WebUtils.FormVarsToObject(). These tools have options to avoid writing missing values into the object. This should ensure that only values that are actually passed are updated, and not the entire entity.

This is something that bugs me around model binding: Rather than being able to update an existing object the object has to be a parameter and get instantiated. While ModelBinding actually does the right thing (reads only POST values), that effect is unfortunately lost when you do it on a newly created instant.

That aside though - Attach() should work.

+++ Rick ---



Rick - I have a basic MVC4 CRUD app up and running, but I cannot figure out how to call the Save() method from the post-back of the Edit action on my controller.

If I understand this EF stuff properly, somehow, I've got to get the Quote entity back into the EfCodeFirst Context, and mark it as "modified" so it will be saved back to the database. I just cannot figure out the right sequence. I've tried to "attach" the Quote entity but that fails for some reason, and when I try calling .Save(quote), it does not give an error, but yet the changes do not get saved.

Here's what I've tried:

[HttpPost]public ActionResult Edit(Quote quote) {try {if (ModelState.IsValid) { var quoteBO = new busQuote();//quoteBO.Attach(quote);//quoteBO.Context.Entry(quote).State = System.Data.EntityState.Modified; quoteBO.Entity = quote; quoteBO.Save(quote);return RedirectToAction("Index"); } return RedirectToAction("Index"); }catch {return View(); } }

The Quote entity is a complex entity, as it has a related Customer object, and a child collection of line items:

Quote entity has:
- various properties from its own table field
- has a Customer object (per PK/FK relationship defined in model attributes)
- has a collection of QuoteItems which are line items on the Quote.

Eventually, I will need to save changes to the QuoteItems collection as well, but for now, I'm just wanting to accomplish saving basic properties on the top Entity in the busQuote object (i.e. the to the Quote table).

Here's the busQuote class:

publicclass busQuote : EfCodeFirstBusinessBase<Quote, QuoteContext> { .... }

Here's the Context:

publicpartialclass QuoteContext: EfCodeFirstContext {public DbSet<Quote> Quotes { get; set; } public DbSet<QquoteItem> QuoteItems { get; set; }public DbSet<Customer> Customers { get; set; }protectedoverridevoid OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new quoteMap()); modelBuilder.Configurations.Add(new quoteitemMap()); modelBuilder.Configurations.Add(new customerMap()); } }






Viewing all articles
Browse latest Browse all 10393

Trending Articles