DbContext.SaveChanges - asp.net

I have an issue where inserting one record to the entity ProfessionalEmploymentRecord EF ends up duplicating existing Professional, Program, Role records in each entitiy, respectively. I have tried to explicitely initilize Professional, Program, Role with existing key/value pairs in my database, but EF still creates duplicate values with new keys.
public class ProfessionalEmploymentRecord
{
public int ProfessionalEmploymentRecordId {get; set; }
public virtual Professional Professional { get; set; }
public virtual Program Program { get; set; }
public virtual Role Role { get; set; }
}
public class Program
{
public int ProgramId { get; set; }
[MaxLength(20)]
public string Name { get; set; }
}
public class Role
{
public int RoleId { get; set; }
[MaxLength(50)]
public string Name { get; set; }
}
public class Professional
{
public int ProfessionalId { get; set; }
[MaxLength(20)]
public string Name { get; set; }
}
When using Create below I end up with new records in the three related entities although they exist and have a key set. In other words I end up with copies of values with new primary keys (incremented by one) in the three related entities.
public ActionResult Create(ProfessionalEmploymentRecord professionalemploymentrecord)
{
if (ModelState.IsValid)
{
db.ProfessionalEmploymentRecords.Add(professionalemploymentrecord);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(professionalemploymentrecord);
}
As requested
#model My.Models.ProfessionalEmploymentRecord
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend> Professional Employment Record</legend>
<div class="editor-label">
Professional
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Professional.Name)
#Html.ValidationMessageFor(model => model.Professional.Name)
</div>
<div class="editor-label">
Program
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Program.Name)
#Html.ValidationMessageFor(model => model.Program.Name)
</div>
<div class="editor-label">
Role
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Role.Name)
#Html.ValidationMessageFor(model => model.Role.Name)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
I have used code below to test. (I see that executing SaveChanges sets *Id++ in reco).
public void TestDummyAdd()
{
Professional prof = new Professional { ProfessionalId = 1, Name = "Chris" };
Program prog = new Program { ProgramId = 1, Name = "A" };
Role role = new Role { RoleId = 1, Name = "B" };
ProfessionalEmploymentRecord reco = new ProfessionalEmploymentRecord { Professional = prof, Role = role, Program = prog, ProfessionalEmploymentRecordId = 0 };
if (ModelState.IsValid)
{
db.ProfessionalEmploymentRecords.Add(reco);
db.SaveChanges();
}
}

Add is operation executed on whole object graph. If you add your record you are also adding all its relations, their relations, etc. There are two options how to solve this issue and both require you to manually set state of some entities (and sometimes also relations) in your persisted object grap:
Call Add as you did but after that you must set state for all existing entities in the graph to either Unchanged or Modified (entity will be udpated in database)
Call Attach instead of Add and after that set state for all new entities to Added
Settings state can be done by:
db.Entry(entity).State = EntityState.Added;

Related

Kendo DropDownListFor not s ending info to controller

This is baffling. In one of my MVC models, Project, I have two properties which both save the IDs for other models, like so.
public class Project
{
...
public int PlatformID { get; set; }
public int DepartmentID { get; set; }
...
}
I use a custom editor in which I edit these fields with a kendo dropdown, using their respective names and IDs as text and value fields.
<div class="col-md-11 details-item">
#Html.LabelFor(m => m.PlatformID, new { #class = "col-md-4 details-label" })
#(Html.Kendo().DropDownListFor(m => m.PlatformID)
.Name("PlatformID")
.DataValueField("ID")
.DataTextField("PlatformName")
.BindTo((System.Collections.IEnumerable)ViewData["Platforms"])
.HtmlAttributes(new { #class = "col-md-7 details-editor" })
)
</div>
<div class="col-md-11 details-item">
#Html.LabelFor(m => m.DepartmentID, new { #class = "col-md-4 details-label" })
#(Html.Kendo().DropDownListFor(m => m.DepartmentID)
.Name("DepartmentID")
.DataValueField("ID")
.DataTextField("Name")
.BindTo((System.Collections.IEnumerable)ViewData["Departments"])
.HtmlAttributes(new { #class = "col-md-7 details-editor" })
)
</div>
What's crazy is that PlatformID saves perfectly, but DepartmentID does not even get sent to the controller. If DepartmentID is the only thing I change while editing, it does not even register a change has been made. Otherwise, it gets sent as default 0.
The Department class is insanely simple.
public class Department
{
[Key]
public int ID { get; set; }
[DisplayName("Department Name")]
public string Name { get; set; }
}
PlatformID is essentially identical. Maybe I'm missing something stupid, or maybe there is evil afoot. Thanks for any help you can provide!
Figured out the answer - turns out having a DropDown in an editor template does not work if the item is nullable! So I just changed public int? DepartmentIDto public int DepartmentIDin my Project model, and life was good again.

how and where to connect viewmodel to dbcontext

I inherited a mvc app. This app uses entity framework with database first. It was made with no viewmodels and viewbags everywhere for the dropdowns and error messages. Now I am tasked with making many changes to it because you can not validate the related properties that are not in the main class among other things.
I am trying to create a viewmodel so I can display only necessary data, validate it and not be linked directly to the model. So far I get null for all my fields on a form using the viewmodel I created. I have tried to use automapper but get a mapping error: "Missing type map configuration or unsupported mapping"
Here is part of the controller:
public ActionResult ChangeOwner(int id = 0)
{
var combine = new combineValidationAssetViewModel();
Mapper.CreateMap<ToolingAppEntities1, combineValidationAssetViewModel>();
Mapper.CreateMap<combineValidationAssetViewModel, ToolingAppEntities1>();
Asset asset = db.Assets.Find(id);
Mapper.Map(combine, asset, typeof(combineValidationAssetViewModel), typeof(Asset));
.....
return View(combine);
}
Here is part of the view model:
public class combineValidationAssetViewModel
{
public Asset Assets { get; set; }
public Transaction Transactions { get; set; }
public LocationType LocationTypes { get; set; }
public ToolType ToolTypes { get; set; }
public OwnerType OwnerTypes { get; set; }
public int AssetId { get; set; }
public int fkToolTypeId { get; set; }
[Required]
[Display(Name = "Owner")]
public int fkOwnerId { get; set; }
[Required]
[Display(Name = "Location")]
public int fkLocationId { get; set; }
public int LocationTypeId { get; set; }
public int OwnerTypeId { get; set; }
Here is part of the view:
#model ToolApp.ViewModels.combineValidationAssetViewModel
.....
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Asset</legend>
#Html.HiddenFor(model => model.AssetId)
#Html.HiddenFor(model => model.CreatedByUser)
#Html.HiddenFor(model => model.CreateDate)
#Html.HiddenFor(model => model.SerialNumber)
#Html.HiddenFor(model => model.LocationTypeId)
<div class="editor-label">
#Html.LabelFor(model =>model.SerialNumber)
</div>
<div class="editor-field">
#Html.DisplayFor(model => model.SerialNumber)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.fkToolTypeId, "Tool Name")
</div>
<div class="editor-field">
#Html.DisplayFor(model => model.Description)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.fkOwnerId, "New Owner")
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.fkOwnerId, new SelectList(ViewBag.fkOwnerId, "Value", "Text"), new{style="width:320px;height:25px;"})
#Html.ValidationMessageFor(model => model.fkOwnerId),
The form displays but it is null (no values in any of the fields displayed. I would like to map it manually so I understand it. Have tried the automapper but it's not working yet. I have tried some ideas from here and other websites but same result. I don't completely understand the linq to ef yet so my problem may lie there also.
This main controller has 10 different action results on it and is filled with data calls and viewbags. I'm looking for advice on the direction I should go. I need to get the thing working but also want to make changes to it that will move it in the direction of a viable mvc app. Main issue at the moment is how to connect the viewmodel with the dbcontext. I found the context at the top of the controller like this:
{ private ToolingAppEntities1 db = new ToolingAppEntities1();
followed by many includes...
any suggestions would be appreciated
You map into the wrong direction:
Mapper.Map(combine, asset,
typeof(combineValidationAssetViewModel), typeof(Asset));
This maps the empty combine object to asset. You should reverse it, and use a strong-typed (generic) overload:
var combine = Mapper.Map<combineValidationAssetViewModel>(asset);

Remote Validate for DropDownList, MVC3, not firing in my case

I am using ASP.NET MVC3 and EF 4.1
I have two DropDownList in my Model, It is required and not duplicated too.
And I want the Remote validate function: ValidateDuplicateInsert get firing when user submit data. But I can NOT get the ValidateDuplicateInsert function firing.
Where am I wrong?
My Model
[Key]
public int CMAndOrgID { get; set; }
[Display(Name = "CM")]
[Required(ErrorMessage = "CM is required.")]
[Remote("ValidateDuplicateInsert", "CMAndOrg", HttpMethod = "Post", AdditionalFields = "CMID, OrganizationID", ErrorMessage = "CM is assigned to this Organization.")]
public int? CMID { get; set; }
[Display(Name = "Organization")]
[Required(ErrorMessage = "Organization is required.")]
public int? OrganizationID { get; set; }
public virtual CM CM { get; set; }
public virtual Organization Organization { get; set; }
The ValidateDuplicateInsert function in my CMAndOrg controller
[HttpPost]
public ActionResult ValidateDuplicateInsert(string cmID, string orgID)
{
bool flagResult = true;
foreach (CMAndOrg item in db.CMAndOrgs)
{
if (item.CMID.ToString() == cmID && item.OrganizationID.ToString() == orgID)
{
flagResult = false;
break;
}
}
return Json(flagResult);
}
And my View
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>CMAndOrg</legend>
<div class="editor-label">
#Html.LabelFor(model => model.CMID, "CM")
</div>
<div class="editor-field">
#Html.DropDownList("CMID", String.Empty)
#Html.ValidationMessageFor(model => model.CMID)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.OrganizationID, "Organization")
</div>
<div class="editor-field">
#Html.DropDownList("OrganizationID", String.Empty)
#Html.ValidationMessageFor(model => model.OrganizationID)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
There is a bug in MVC3 related to unobtrusive validation on dropdownlist. Please reference to this http://aspnet.codeplex.com/workitem/7629[^] link for more detail explaination.
Briefly, you can't use the same name for category collection and category field, so just change your collection name and update following line in your view
#Html.DropDownList("CategoryID", String.Empty)
with this
#Html.DropDownListFor(model => model.CategoryID, new SelectList((System.Collections.IEnumerable)ViewData["Categories"], "Value", "Text"))
Thanks again Henry He
Original link
http://www.codeproject.com/Articles/249452/ASP-NET-MVC3-Validation-Basic?msg=4330725#xx4330725xx

Asp.net mvc save One-to-many and passing master on submit

I have a problem saving the detail of a master in asp.net mvc.
For reference I am using nhibernate.
I have a One-to-many relationship between the Store and Employee entities.
I save the master and the detail in 2 steps.
You create the Store first, save it, then you create the employees.
Here are both classes:
public class Store
{
public Store()
{
Employees = new List<Employee>();
}
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Employee> Employees { get; set; }
}
public class Employee
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Store Store { get; set; }
}
So to create the Store I have the following code in the store's controller and view:
public virtual ActionResult Create()
{
var model = new StoreModel();
return View(model);
}
[HttpPost]
public ActionResult Create(StoreModel model)
{
if (ModelState.IsValid)
{
Store entity = new Store(model);
Repository<Store> repository = new Repository<Store>();
repository.Create(entity);
return RedirectToAction("Index");
}
return View(model);
}
#model StoreModel
#{
ViewBag.Title = "Create store";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>
Create store
</h2>
#Html.ValidationSummary(true)
#using (Html.BeginForm())
{
<div>
#Html.LabelFor(store => store.Name)
</div>
<div>
#Html.TextBoxFor(store => store.Name)
#Html.ValidationMessageFor(store => store.Name)
</div>
<p>
<input type="submit" value="Create"/>
</p>
}
<div>
#Html.ActionLink("Back", "Index")
</div>
That works fine, but my problem is when i try to save the employees, the store is always null.
So to create the employees I have the following code in the employee's controller and view:
public virtual ActionResult Create(int storeId)
{
var model = new EmployeeModel();
Repository<Store> repository = new Repository<Store>();
model.Store = repository.Read(storeId);
return View(model);
}
[HttpPost]
public ActionResult Create(EmployeeModel model) //Problem here, store is null in the model
{
if (ModelState.IsValid)
{
Employee entity = new Employee(model);
Repository<Employee> repository = new Repository<Employee>();
repository.Create(entity);
return RedirectToAction("Index");
}
return View(model);
}
#model EmployeeModel
#{
ViewBag.Title = "Create employee";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>
Create employee
</h2>
#Html.ValidationSummary(true)
#using (Html.BeginForm())
{
<div>
#Html.LabelFor(employee => employee.Name)
</div>
<div>
#Html.TextBoxFor(employee => employee.Name)
#Html.ValidationMessageFor(employee => employee.Name)
</div>
<p>
<input type="submit" value="Create"/>
</p>
}
<div>
#Html.ActionLink("Back", "Index")
</div>
What am I doing wrong?
You're not setting the employee's store property anywhere.
You're not passing the storeId anywhere. I suggest you create a dropdown in your 'Create Employee' form and populate it with your list of stores.
I was able to fix my problem by adding int storeId in the httppost create and read the store from the database and setting the emplyee's store.
[HttpPost]
public ActionResult Create(EmployeeModel model, int storeId) //Problem here, store is null in the model
{
if (ModelState.IsValid)
{
Repository<Store> storeRepository = new Repository<Store>();
Store store = storeRepository.Read(storeId);
Employee employee = new Employee(model);
employee.Store = store;
Repository<Employee> employeeRepository = new Repository<Employee>();
employeeRepository.Create(employee);
return RedirectToAction("Index");
}
return View(model);
}
and using a hidden field:
#model EmployeeModel
#{
ViewBag.Title = "Create employee";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>
Create employee
</h2>
#Html.ValidationSummary(true)
#using (Html.BeginForm())
{
#Html.Hidden("storeId", Model.Store.Id)
<div>
#Html.LabelFor(employee => employee.Name)
</div>
<div>
#Html.TextBoxFor(employee => employee.Name)
#Html.ValidationMessageFor(employee => employee.Name)
</div>
<p>
<input type="submit" value="Create"/>
</p>
}
<div>
#Html.ActionLink("Back", "Index")
</div>

Asp.net MVC 3 "parameter conversion from type 'System.String' to type failed" when using SelectList Dropdown box

I'm stuck and after looking this up for hours, I think I need more eyeballs.
The situation is the following:
It's an Asp.Net MVC3 with Entity Framework 4 project. And I have two classes. One ConfigurationFile and another one Action. There is a one-to-many relationship between the two. Here is a simplified view on the code:
public class ConfigurationFile
{
[Key, Required]
[Column(TypeName = "uniqueidentifier")]
public Guid Id { get; set; }
[Required]
public string Name { get; set; }
[Column(TypeName = "uniqueidentifier")]
[Required]
public Guid ActionId { get; set; }
public virtual Models.Action Action { get; set; }
}
public class Action
{
[Key, Required]
[Column(TypeName = "uniqueidentifier")]
public Guid Id { get; set; }
[Required]
public string ActionValue { get; set; }
}
Then I want to create a new ConfigurationFile, and are my two controller methods (and at this point, this is 95% Visual Studio 10 generated code):
// db is my context class.
//
// GET: /Configuration/Create
public ActionResult Create()
{
ViewBag.ActionId = new SelectList(db.Actions, "Id", "ActionValue");
return View();
}
//
// POST: /Configuration/Create
[HttpPost]
public ActionResult Create(Models.ConfigurationFile configurationfile)
{
if (ModelState.IsValid)
{
configurationfile.Id = Guid.NewGuid();
db.ConfigurationFiles.Add(configurationfile);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.ActionId = new SelectList(db.Actions, "Id", "ActionValue", configurationfile.ActionId);
return View(configurationfile);
}
And here is a snippet of my Create view:
#model MyProject.Areas.ConfigurationFile.Models.ConfigurationFile
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Configuration File</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>
<div class="editor-label">
#Html.LabelFor(model => model.ActionId, "Action")
</div>
<div class="editor-field">
#Html.DropDownList("ActionId", String.Empty)
#Html.ValidationMessageFor(model => model.ActionId)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
When I open the Create page, I can clearly see that my dropdown for the Action class is fine (correct value -- the Action.Id -- and text -- Action.ActionValue -- ) but when I submit the form, I have the following error: "The parameter conversion from type 'System.String' to type 'MyProject.Models.Action' failed because no type converter can convert between these types."
Help please !!
Right now MVC has no way of connecting your dropdownlist from your view to the ActionId of your ConfigurationFile object.
I would try replacing this line:
#Html.DropDownList("ActionId", String.Empty)
for this
#Html.DropDownListFor(model => model.ActionId, ViewBag.ActionId)
Other than that, I can't think of what else you might have done wrong.
I hope that helps!
This is how I did to circumvent the problem. I just changed my controller this way:
Models.Action act = db.Actions.Find(configurationfile.ActionId);
ModelState.Clear();
configurationfile.Action = act;
TryValidateModel(configurationfile);
And after that, the validation was Ok. A bit hacky (and another possible hit on the DB), but at least, I can keep going.

Resources