How to Create ViewModel with multiple related tables and Save Form - asp.net-mvc-4

I am trying to figure out the best way to accomplish this given the modern versions. I have am using VS2012 MVC4 EF5 and have built a edmx file from my database. I built a form that will allow submission of vendor information. The main table is Vendor table that contains mainly contact information and there are additional tables that store their multiple category choices (checkbox list) and another that stores their minority info (collection of radio buttons). So my ViewModel is the vendor table and I populate the checkboxes and radio buttons with view bags that query the lookup tables for their values.
So I assume I should either build the categories and minority parts into the ViewModel and somehow wire up the magic so that the database knows how to save the returned values or should I just use viewbags and then somehow on post read those values and loop through them to store them to the database? Either way I am stuck and don't know how to do this.
I have serached numerous examples online but none of them fit this situation. The is not a complex data model but should be rather common real world situation. I am new to MVC so forgive me if I am missing something obvious.
Any guidance is appreciated.
UPDATE: Here is the baseic code to save the ViewModel to the db but how do you save the checkbox list and radio buttons. I think there are two approaches 1) to somehow include them in the ViewModel or 2) perform a separate function to save the form checkbox and radio button values.
[HttpPost]
public ActionResult Form(VendorProfile newProfile)
{
if (ModelState.IsValid)
{
newProfile.ProfileID = Guid.NewGuid();
newProfile.DateCreated = DateTime.Now;
_db.VendorProfiles.Add(newProfile);
_db.SaveChanges();
return RedirectToAction("ThankYou", "Home");
}
else
{
PopuplateViewBags();
return View(newProfile);
}
}
Perhaps another way of stating my problem is what if you had to build an form to where people would sign up and select all their favorite flavors of ice cream from a list of 31 flavors. You need to save the person's contact information in the primary table and then save a collection of their flavor choices in another table (one-to-many). I have a ViewModel for the contact form and a list of flavors (checkbox list) displayed from a lookup table. How do you write code to save this form?

SOLUTION: There might be a better way, but wanted to post what I discovered. You can pass in the collection of checkboxes and then send them to another method that handles the db inserts.
[HttpPost]
public ActionResult Form(VendorProfile newProfile, int[] categories)
{
if (ModelState.IsValid)
{
newProfile.ProfileID = Guid.NewGuid();
newProfile.DateCreated = DateTime.Now;
_db.VendorProfiles.Add(newProfile);
_db.SaveChanges();
InsertVendorCategories(newProfile.ProfileID, categories);
return RedirectToAction("ThankYou", "Home");
}
else
{
PopuplateViewBags();
return View(newProfile);
}
}
private void InsertVendorCategories(Guid ProfileID, int[] categories)
{
try
{
var PID = new SqlParameter("#ProfileID", ProfileID);
var CID = new SqlParameter("#CatID", "");
foreach (int c in categories)
{
CID = new SqlParameter("#CatID", c);
_db.Database.ExecuteSqlCommand("Exec InsertVendorCategory #ProfileID, #CatID", PID, CID);
}
}
catch { Exception ex; }
}

Related

Why is my record being deleted from the db when I attempt to update the record from entity framework MVC?

When I attempt to update a record from entity framework the record is being deleted from the table. There are no errors thrown so it really has me baffled what is happening.
I am fairly new to entity framework and asp.net. I've been learning it for about a month now.
I can update the record without any issues from SQL Server but not from vs. Here is the code to update the db:
// GET: /Scorecard/Edit/5
public ActionResult Edit(int id, string EmployeeName)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
CRS_Monthly crs_monthly = GetAgentById(id);
crs_monthly.EmployeeName = EmployeeName;
if (crs_monthly == null)
{
return HttpNotFound();
}
return View(crs_monthly);
}
// POST: /Scorecard/Edit/5
// 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 Edit([Bind(Include="REC_ID,Cur_Plan,Plan_Update,Comments,Areas_Improve,Strengths,UPDATED_BY,UPDATED_TIME,Agent_Recognition")] CRS_Monthly crs_monthly)
{
if (ModelState.IsValid)
{
crs_monthly.UPDATED_TIME = DateTime.Now;
crs_monthly.UPDATED_BY = Request.LogonUserIdentity.Name.Split('\\')[1];
db.Entry(crs_monthly).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(crs_monthly);
}
When I run the debugger crs_monthly is valid and looks fine until db.SaveChanges(). Any help is greatly appreciated!
You should never save an instance of your entity created from a post, especially when you're utilizing Bind to restrict which properties are bound from the post data. Instead, always pull the entity fresh from the database and map the posted values on to it. This ensures that no data is lost.
Using Bind is a horrible practice, anyways. The chief problem with it is that all your properties are listed as string values, and you're introducing maintenance concerns. If remove one of these properties or change the name, the Bind list is not automatically updated. You must remember to change every single instance. Worse, if you add properties, you have to remember to go back and include them in this list or else your data just gets silently dropped with no notice.
If you need to only work with a subset of properties on your entity, create a view model containing just those properties. Then, again, map the posted values from your view model onto an instance of your entity pulled fresh from the database.

how do i create a dynamic view for data entry

What i want in the view is to spit out the fields that are part of the Department and Employee models depending on whichever one gets mentioned in the URL.
say for example department model has 5 fields
How do i create a view (dynamic/not strongly typed) that automatically displays the fields based on the model and let the user enter the values?
[HttpGet]
public ActionResult Create(string process)
{
if (process.Equals("Department"))
{
var model = new Department();
return View(model);
}
else if (process.Equals("Employee"))
{
var model = new Employee();
return View(model);
}
else
return View();
}
You can pass it as an object. You could also pass it in the viewdata (or viewbag). For both of these ways you would also need to include a flag so you know which one you should cast to. Both of these ways in my opinion though are hokey and prone to problems.
Another way would be to create a view model that combines both models. I personally would try to keep them separate and use separate calls \ views for each, depending on the requirements.

The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[X]', but this dictionary requires a model item of type 'X'

*CORRECTION
The problem occurs when my view is called to populate a list from my user table.
The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[Mike.Models.User]', but this dictionary requires a model item of type 'Mike.Models.User'.
Here is my controller action:
public ActionResult Registration(Mike.Models.User user)
{
if (ModelState.IsValid)
{
using (var db = new UserContext())
{
var crypto = new SimpleCrypto.PBKDF2();
var encrypPass = crypto.Compute(user.password);
var sysUser = db.Users.Create();
sysUser.LastName = user.LastName;
sysUser.FirstName = user.FirstName;
sysUser.Email = user.Email;
sysUser.password = encrypPass;
sysUser.passwordSalt = crypto.Salt;
sysUser.UserID = user.UserID;
db.Users.Add(sysUser);
db.SaveChanges();
return RedirectToAction("Index", "Home");
}
}
return View(user);
}
Can someone please help me.... There are responses to similar questions on the internet but I believe mine is unique.. I have searched for weeks to no avail.
Thanks in advance,
Renior
Here is my simple controller action...
public ActionResult Index()
{
return View(db.Users.ToList());
}
and my razor syntax.
#model IEnumerable
Im trying to populate a view of my user table list..
In your Registration view at the top where your model declaration is, instead of this:
#model List<Mike.Models.User>
you need to have:
#model Mike.Models.User
You probably used strongly typed scaffolding feature to generate your view but instead of details option you chose a list option...
Take this at face value - yours is not unique. Your problem is you are passing an array of user to a controller action that expects a user.
You need to post your HTML but it is probably something like #model List user or something instead of a single user.
If your model represents a single user then pass that to the controller. If opposite, do opposite,
If you want to pass a list to the controller use list users
edit
make your razor syntax
#model Mike.Models.User

Yii form and model for key-value table

I have a table which has only two column key-value. I want to create a form which allow user insert 3 pair of key-value settings.
Do I need pass 3 different models to the view? Or is there any possible way to do this?
Check out this link:
http://www.yiiframework.com/doc/guide/1.1/en/form.table
This is considered best form in Yii for updating for creating multiple models.
In essence, for creation you can create a for loop generate as many inputs a you wish to have visible, and in your controller loop over the inputs to create new models.
View File:
for ( $settings as $i=>$setting ) //Settings would be an array of Models (new or otherwise)
{
echo CHtml::activeLabelEx($setting, "[$i]key");
echo CHtml::activeLabelEx($setting, "[$i]key");
echo CHtml::error($setting, "[$i]key");
echo CHtml::activeTextField($setting, "[$i]value");
echo CHtml::activeTextField($setting, "[$i]value");
echo CHtml::error($setting, "[$i]value");
}
Controller actionCreate:
$settings = array(new Setting, new Setting, new Setting);
if ( isset( $_POST['Settings'] ) )
foreach ( $settings as $i=>$setting )
if ( isset( $_POST['Setttings'][$i] ) )
{
$setting->attributes = $_POST['Settings'][$i];
$setting->save();
}
//Render View
To update existing models you can use the same method but instead of creating new models you can load models based on the keys in the $_POST['Settings'] array.
To answer your question about passing 3 models to the view, it can be done without passing them, but to validate data and have the correct error messages sent to the view you should pass the three models placed in the array to the view in the array.
Note: The example above should work as is, but does not provide any verification that the models are valid or that they saved correctly
I'm going to give you a heads up and let you know you could potentially make your life very complicated with this.
I'm currently using an EAV patterned table similar to this key-value and here's a list of things you may find difficult or impossible:
use CDbCriteria mergeWith() to filter related elements on "value"s in the event of a search() (or other)
Filtering CGridView or CListView
If this is just very straight forward key-value with no related entity aspect ( which I'm guessing it is since it looks like settings) then one way of doing it would be:
create a normal "Setting" CActiveRecord for your settings table (you will use this to save entries to your settings table)
create a Form model by extending CFormModel and use this as the $model in your form.
Add a save() method to your Form model that would individually insert key-value pairs using the "Setting" model. Preferably using a transaction incase a key-value pair doesn't pass Settings->validate() (if applicable)
optionally you may want to override the Form model's getAttributes() to return db data in the event of a user wanting to edit an entry.
I hope that was clear enough.
Let me give you some basic code setup. Please note that I have not tested this. It should give you a rough idea though.:
Setting Model:
class Setting extends CActiveRecord
{
public function tableName()
{
return 'settings';
}
}
SettingsForm Model:
class SettingsForm extends CFormModel
{
/**
* Load attributes from DB
*/
public function loadAttributes()
{
$settings = Setting::model()->findAll();
$this->setAttributes(CHtml::listData($settings,'key','value'));
}
/*
* Save to database
*/
public function save()
{
foreach($this->attributes as $key => $value)
{
$setting = Setting::model()->find(array('condition'=>'key = :key',
'params'=>array(':key'=>$key)));
if($setting==null)
{
$setting = new Setting;
$setting->key = $key;
}
$setting->value = $value;
if(!$setting->save(false))
return false;
}
return true;
}
}
Controller:
public function actionSettingsForm()
{
$model = new Setting;
$model->loadAttributes();
if(isset($_POST['SettingsForm']))
{
$model->attributes = $_POST['SettingsForm'];
if($model->validate() && $model->save())
{
//success code here, with redirect etc..
}
}
$this->render('form',array('model'=>$model));
}
form view :
$form=$this->beginWidget('CActiveForm', array(
'id'=>'SettingsForm'));
//all your form element here + submit
//(you could loop on model attributes but lets set it up static for now)
//ex:
echo $form->textField($model,'fieldName'); //fieldName = db key
$this->endWidget($form);
If you want further clarification on a point (code etc.) let me know.
PS: for posterity, if other people are wondering about this and EAV they can check the EAV behavior extention or choose a more appropriate DB system such as MongoDb (there are a few extentions out there) or HyperDex

How to add a new entity to a domain context and immediately see it in data bound controls before SubmitChanges?

I've got a Silverlight 4 RIA Services (SP1) app using Entity Frameworks 4 CTP5. I can databind a grid or listbox to the IEnumerable loaded by the domain context and it shows data from the server. Great.
Now I want to create a new instance of MyEntity and add it to the client-side data so that the user can see the newly added entity. MyEntity is a true entity descendant, not a POCO.
The only Add method I can find is domainContext.EntityContainer.GetEntitySet<MyEntity>().Add(newobj)
This does add the new entity to the domain context, and the domainContext.HasChanges does become true, but the new entity doesn't show up in the databound controls.
How do I get the new entity to show up in the databound controls prior to SubmitChanges?
(Probably related to this SO question from years ago that never got an answer)
Here's the server side declarations of the domain service, per requests:
[EnableClientAccess()]
public class MyDomainService : LinqToEntitiesDomainService<MyObjectContext>
{
protected override MyObjectContext CreateObjectContext()
{
return new MyObjectContext();
}
public IQueryable<MyEntity> GetMyEntities()
{
return this.ObjectContext.MyEntities;
}
public void InsertMyEntity(MyEntity MyEntity)
{
// ...
}
public void UpdateMyEntity(MyEntity currentMyEntity)
{
// ...
}
public void DeleteMyEntity(MyEntity MyEntity)
{
// ...
}
}
I've figured this out with a combination of my own trial and error and hints provided by some of the other responses to this question.
The key point I was missing was that it's not enough for the ViewModel to keep track of the DomainContext and hand out query results to the View for databinding. The ViewModel also has to capture and retain the query results if you want entity adds and deletes performed by the ViewModel to appear in the UI before DomainContext.SubmitChanges(). The ViewModel has to apply those adds to the collection view of the query results.
The ViewModel collection property for View databinding. In this case I'm using the Telerik QueryableDomainServiceCollectionView, but other collection views can be used:
public IEnumerable<MyEntity> MyEntities
{
get
{
if (this.view == null)
{
DomainContextNeeded();
}
return this.view;
}
}
private void DomainContextNeeded()
{
this.context = new MyDomainContext();
var q = context.GetMyEntitiesQuery();
this.view = new Telerik.Windows.Data.QueryableDomainServiceCollectionView<MyEntity>(context, q);
this.view.Load();
}
The ViewModel function that adds a new entity for the UI to display:
public void AddNewMyEntity(object selectedNode)
{
var ent = new MyEntity() { DisplayName = "New Entity" };
if (selectedNode == null)
{
this.view.AddNew(ent);
}
else if (selectedNode is MyEntity)
{
((MyEntity)selectedNode).Children.Add(ent);
}
}
Other responses mentioned ObservableCollection. The query results and the collection view may not return instances of ObservableCollection. They could be just IEnumerables. What is critical is that they implement INotifyCollectionChanged and IEditableCollectionView.
Thanks to those who contributed responses. I've +1'd each response that was helpful, but since none directly solved my problem I couldn't justify marking any as the definitive answer.
Your domainContext will have a property domainContext.MyEntities. Does it not show up in there when you add it?
Bind to that collection or watch that collection for changes.
domainContext.MyEntities.PropertyChanged += MyEventHandler;
I assume you bind your control to the IEnumerable which is provided by LoadOperation<TEntity>.Entities. In that case your binding source is not the DomainContext.GetEntitySet<MyEntity>().
DomainContext.GetEntitySet<MyEntity>() holds all your currently tracked instances of MyEntity, including the one you add with .Add().
LoadOperation<TEntity>.Entities only contains the instances of MyEntity that were actually loaded by your last LoadOperation/Query.
You have two options: Either add the new entity to the ItemsSource-collection for your control (I recommend that) or rebuild the collection with the contents of DomainContext.GetEntitySet<MyEntity>(). That may contain other elements that you have not cleared out before, though.