I have a class with with one-to-many relationship and trying to display the virtual object in the view. I am fairly new to MVC and EF so this maybe something simple.
I am trying to display a drop down list for PODObject on "Request/Create" action view.
Here are the models
public class PODObject
{
public int PODObjectID { set; get; }
public string name { set; get; }
public string CustomAttribute1 { set; get; }
public string CustomAttribute2 { set; get; }
public string CustomAttribute3 { set; get; }
public string CustomAttribute4 { set; get; }
public string CustomAttribute5 { set; get; }
public virtual ICollection<BookingObject> BookingObjectspublic { set; get; }
}
public class RequestsQueueObject
{
// Request Attributes
public int RequestsQueueObjectID { set; get; }
public string CustomAttribute1 { set; get; }
public string CustomAttribute2 { set; get; }
public string CustomAttribute3 { set; get; }
public string CustomAttribute4 { set; get; }
public string CustomAttribute5 { set; get; }
public int PODObjectID { set; get; }
public virtual PODObject PODObject { set; get; }
}
Here is the controller Create action
public ActionResult Create([Bind(Include="RequestsQueueObjectID,POCPODAccess,CustomAttribute1,CustomAttribute2,CustomAttribute3,CustomAttribute4,CustomAttribute5")] RequestsQueueObject requestsqueueobject)
{
if (ModelState.IsValid)
{
db.RequestsQueueObjects.Add(requestsqueueobject);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(requestsqueueobject);
}
Here is the cshtml
<div class="form-group">
#Html.DropDownListFor(model => model.PODObject, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.PODObject)
#Html.ValidationMessageFor(model => model.PODObject)
</div>
</div>
Maybe I am wrong but your PODOObject doesn't look like a List, an Array or whatever, so you can't really use it as a source for your dropdown...
I think what you're looking for is being able to pick from a list of existing PODObject instances to set as the related item for RequestQueueObject. You've got a few issues you need to clear up for that.
You have a property on RequestQueueObject to hold the id for the relationship with PODObject, but this currently isn't being used. Entity Framework's convention is to look for an property with the naming pattern [RelatedProperty][RelatedClassIdProperty]. In your scenario, that would be PODObjectPODObjectID or PODObject_PODObjectID. You can clear that up by adding the ForeignKey attribute to your PODObjectID property:
[ForeignKey("PODObject")]
public int PODObjectID { get; set; }
Html.DropDownListFor requires an IEnumerable<SelectListItem> for its second parameter, which you don't currently have. MVC won't do this for you; you need to create the list of choices yourself. Ideally, you would use a view model with this as a property, but for the time being, you can use ViewBag:
In your action
var podObjectChoices = db.PODObjects.Select(m => new SelectListItem
{
Value = m.PODObjectID,
Text = m.name
});
ViewBag.PODObjectChoices = podObjectChoices.ToList();
return View();
In your view
#Html.DropDownListFor(m => m.PODObjectID, (IEnumerable<SelectListItem>)ViewBag.PODOBjectChoices, new { #class = "control-label col-md-2" })
I pretty much just covered this in the sample code above, but you can't directly set PODOBject via a drop down list. Instead, you set the foreign key property for the relationship, PODOBjectID, and Entity Framework will wire it up.
Related
I want to view, edit and create with drop-down list of my lookup relationships.
Sometimes this works, sometimes it doesn't. It's a mystery that I'm hoping can be definitely solved here.
Here's my POCO's for the lookup
public class Color
{
public int Id { get; set; }
public string Value {get;set;}
}
//Red,White,Blue
public class Size
{
public int Id { get; set; }
public string Value { get; set; }
}
//S,M,L
And here is the main Object which I'd like to have the Color and Size drop-downs scaffolding to out of the box.
public class Product
{
public int Id;
public string Name;
public virtual Color Color;
public virtual Size Size;
}
This isn't working for me. Neither Size or Color are showing up when it's time to view, edit or create a Product. I only see the Name field.
Size and color are lazy loaded by default (virtual) so you need to eagerly load them:
var products = context.Products.Include(p => p.Color).Include(p => p.Size).ToList();
https://msdn.microsoft.com/en-us/data/jj574232.aspx
If your issue is with the drop downs, you will want to compose a viewmodel in your controller that has the list items, send that to your view and use DropDownListFor(m => m.ColorId, m.Colors). You may need to add ColorId and SizeId to your Product model. Here is a good explanation: http://odetocode.com/blogs/scott/archive/2013/03/11/dropdownlistfor-with-asp-net-mvc.aspx
Just change like this:
public class Color
{
public int Id { get; set; }
public string Value {get;set;}
public ICollection<Product> Products {get;set;}
}
//Red,White,Blue
public class Size
{
public int Id { get; set; }
public string Value { get; set; }
public ICollection<Product> Products {get;set;}
}
And your main object:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
[ForeignKey("Color")]
public int Color_Id { get; set; }
public virtual Color Color { get; set; }
[ForeignKey("Size")]
public int Size_Id { get; set; }
public virtual Size Size { get; set; }
}
Then, just add an scaffolded View with Create or Edit templates and VS will generate the DDL like this:
<div class="form-group">
#Html.LabelFor(model => model.Color_Id, "Color_Id", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("Color_Id", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Color_Id, "", new { #class = "text-danger" })
</div>
</div>
I am having difficulty with my understanding of MVC coming from an aspx world.
I have a Model called CustomerGarment. This has a Order and a Customer along with a few garments.
public class CustomerGarment
{
public int CustomerGarmentId { get; set; }
public virtual Customer Customer { get; set; }
public virtual Order Order { get; set; }
public virtual GarmentJacket GarmentJacket { get; set; }
public virtual GarmentShirt GarmentShirt { get; set; }
}
I have a method for get and post. When the page loads, it creates a new CustomerGarment instance and querys the database to fill the Customer and Order variables. I then use the viewbag to show on the screen a list of GarmentJackets and GarmentShirts
The page then views and using the view I can access the model perfectly. Drop downs load with the viewbag contents and I can access all Customer and Order variables using the model I have passed.
The problem I then face is when I use the HttpPost. The model is not passed back with the information I passed to it.
public ActionResult AddGarments(int orderId, int customerId)
{
CustomerGarment cg = new CustomerGarment();
cg.Order = (from a in db.Orders where a.OrderId == orderId select a).FirstOrDefault();
cg.Customer = (from a in db.Customers where a.CustomerId == customerId select a).FirstOrDefault();
var jackets = from a in db.GarmentJackets orderby a.Type, a.SleeveLengthInches, a.ChestSizeInches select a;
var shirts= from a in db.GarmentKilts orderby a.PrimarySize, a.DropLength select a;
ViewBag.GarmentJacket = new SelectList(jackets, "GarmentJacketId", "GarmentJacketId");
ViewBag.GarmentShirt = new SelectList(shirts, "GarmentShirtId", "GarmentShirtId");
return View(cg);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddGarments(CustomerGarment cg)
{
// Here, I do not have the customer info for example
db.CustomerGarments.Add(cg);
db.SaveChanges();
return RedirectToAction("Index");
return View(cg);
}
This is a bit of my view
#Html.HiddenFor(model => model.Order.OrderId)
#Html.HiddenFor(model => model.Order.CustomerId)
<div class="display-field">
#Html.DisplayFor(model => model.Customer.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.GarmentJacket, "Jacket")
</div>
<div class="editor-field">
#Html.DropDownListFor(m => m.GarmentJacket, (SelectList)ViewBag.GarmentJacket, new {style="width:312px;height:30px;margin-top:2px;margin-bottom:5px"})
</div>
EDIT
My Garment Jacket Model
public class GarmentJacket : Garment
{
public int GarmentJacketId { get; set; }
[Required]
public string Type { get; set; }
[Required]
[Display(Name = "Chest Size")]
public int ChestSizeInches { get; set; }
[Required]
[Display(Name = "Sleeve Length")]
public int SleeveLengthInches { get; set; }
}
public class Garment
{
[DataType(DataType.Date)]
public DateTime? DateRetired { get; set; }
[Required]
public string Barcode { get; set; }
[Required]
public bool Adults { get; set; }
}
In your CustomerGarment class, you should have:
public class CustomerGarment
{
public int CustomerGarmentId { get; set; }
public int CustomerId { get; set; }
public int OrderId { get; set; }
public int GarmentJacketId { get; set; }
public int GarmentShirtId { get; set; }
public virtual Customer Customer { get; set; }
public virtual Order Order { get; set; }
public virtual GarmentJacket GarmentJacket { get; set; }
public virtual GarmentShirt GarmentShirt { get; set; }
}
And, then, in your View, your DropDownList will look like:
#Html.DropDownListFor(m => m.GarmentJacketId, (SelectList)ViewBag.GarmentJacket, new {style="width:312px;height:30px;margin-top:2px;margin-bottom:5px"})
Your DropDownList only posts one value, which is the GarmentJacketId. You can't bind that Id to the whole GarmentJacket class.
By the way, you also need to replace your hidden inputs with these:
#Html.HiddenFor(model => model.OrderId)
#Html.HiddenFor(model => model.CustomerId)
I think I know your problem. As you suggested in you comment above you need to post everything you want retained in the view. This is one of the differences beteween webforms and MVC, webforms has viewstate that could contain information that you don't explicitly add to the view and post back, giving the impression of state. In MVC you have to add it to the view.
On the other hand you don't need to pass in more information than you need either. You pass inn the customerId as a hidden field. On post method you get the customer from the db using the Id, then you add the order to the customer.
I have some questions about your design, but given that a customer holds a collection of Orders, you could do something like this:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddGarments(CustomerGarment cg)
{
// Get the customer from the database
var customer = db.Customers.Find(c=>c.id==cb.Customer.Id)
var order = new Order();
//Create your order here using information from CustomerGarment model
//If the model already holds a valid Order object then just add it.
//i.e. you could get a Garment object from the DB using the GarmentId from
//the ViewModel if you really need more than just the Id to create the order
customer.Orders.Add(order);
db.SaveChanges();
return RedirectToAction("Index");
}
I have two model classes in one-to-one relationship:
class Person
{
public int PersonID { get; set; }
public int DetailPersonID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DetailPerson DetailPerson { get; set; }
}
class DetailPerson
{
public int DetailPersonID { get; set; }
public string Address { get; set; }
public string PhoneNumber { get; set; }
}
and the code for the edit page view:
#using (Html.BeginForm())
{
#Html.HiddenFor(m => m.PersonID)
#Html.LabelFor(m => m.FirstName)
#Html.TextBoxFor(m => m.FirstName)
#Html.LabelFor(m => m.LastName)
#Html.TextBoxFor(m => m.LastName)
#Html.LabelFor(m => m.DetailPerson.Address)
#Html.TextBoxFor(m => m.DetailPerson.Address)
#Html.LabelFor(m => m.DetailPerson.PhoneNumber)
#Html.TextBoxFor(m => m.DetailPerson.PhoneNumber)
<input type="submit" value="Edit">
}
The EF scaffold uses this code to update data:
db.Entry(person).State = System.Data.EntityState.Modified;
db.saveChanges();
When I submit the edit form, I got an error like this:
A foreign key value cannot be inserted because a corresponding primary key value does not exist. [ Foreign key constraint name = FK_dbo.People_dbo.DetailPersons_DetailPersonID ]
But if I do this:
Person p = db.Persons.Find(person.PersonID);
p.DetailPerson = person.DetailPerson;
p.FirstName = person.FirstName;
p.LastName = person.LastName;
db.saveChanges();
update data success without error
I want to know why the first way causse an error,
when I set the breakpoint at the line containing EntityState.Modified,
but the foreign key value ( DetailPersonID ) is 0.
Then, I added #Html.HiddenFor(m => m.DetailPersonID) on the edit form.
I got another error:
A referential integrity constraint violation occurred: The property values that define the referential constraints are not consistent between principal and dependent objects in the relationship.
I still update database on the other way,
I am just curious why the first way which is EF standart to update data got an error.
You shouldn't use two classes if there is a one-to-one relationship since EF will normalize the db into the corresponding form anyway. Combine your classes like this.
public class Person
{
public int PersonID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public string PhoneNumber { get; set; }
}
if you must have separate classes (not recommended), do it like this:
public class Person
{
public int PersonID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int DetailPersonID { get; set; }
public virtual DetailPerson DetailPerson { get; set; }
}
public class DetailPerson
{
public int PersonID { get; set; }
public virtual Person Person { get; set; }
public int DetailPersonID { get; set; }
public string Address { get; set; }
public string PhoneNumber { get; set; }
}
Then when you retrieve your object, do it like this:
// include the detail when retrieving the parent
Person person = db.People.Include(p=>p.DetailPerson).Single(p=>p.PersonId == whateverIdYou Need);
// modify whatever you like
person.DetailPerson.Address = "my new address";
// then your previous statement will work
db.Entry(person).State = System.Data.EntityState.Modified;
db.saveChanges();
here is my model
namespace chPayroll.Models.CustInformations
{
public class CustContact
{
public int cId { get; set; }
public int cNoType { get; set; }
public string cNo1 { get; set; }
public string cNo2 { get; set; }
public string cNo3 { get; set; }
public List<CustContact> contact { get; set; }
}
}
here is my editorTemplates
#model chPayroll.Models.CustInformations.CustContact
#Html.TextBoxFor(model => model.cNo1)
#Html.TextBoxFor(model => model.cNo2)
#Html.TextBoxFor(model => model.cNo3)
I need to show three textbox for taking email,three text box for taking telephone no. in view. how can I add the items to the list contact defined in the model so that it shows like this
email:--textbox1----textbox2----textbox3--
telephone:--textbox1----textbox2----textbox3--
and sends value to the controller
actually I am trying to send my data in list named contact here ie inside list at
index 0-email1-email2-email3
index 1-tel1-tel2-tel3
#Sanjay: you have a strange construct in your view model:
public class CustContact
{
public List<CustContact> contact;
}
Even if it compiles and machine understands it, I wouldn't use it as it is - you trying to lift yourself from the ground by pulling your hair up :)
It should be defined something along these lines (following your naming conventions & logic):
public class CustContact // single
{
public int cId { get; set; }
public int cNoType { get; set; }
public string cNo1 { get; set; } // those are actual phones, emails etc data
public string cNo2 { get; set; }
public string cNo3 { get; set; }
}
public class CustContacts // plural
{
public List<CustContact> Contacts;
}
View:
#model CustContacts
#EditorFor(m => Model)
Editor template:
#model CustContact
#Html.EditorFor(m => m.cNo1)
#Html.EditorFor(m => m.cNo2)
#Html.EditorFor(m => m.cNo3)
For brevity, we don't deal here with annotations, decorations, error handling etc.
Hope this helps.
Based on the comment on the question, I would build the models as below
public class CustContact
{
public int cId { get; set; }
public int cNoType { get; set; }
public string cNo1 { get; set; }
public string cNo2 { get; set; }
public string cNo3 { get; set; }
}
public class Customer
{
public CustContact Email {get; set;}
public CustContact Telephone {get; set;}
}
then create an editor template for Customer and in that editor template have following logic
#Html.EditorFor(model => model.Email)
#Html.EditorFor(model => model.Telephone)
Hope this helps
After researching all day and night, I have something that is currently working. However, I am not sure I really understand what's going on with navigation properties and entity relationships, so I'm concerned that my code might cause problems in the future. I had to manually set the navigation properties to "EntityState.Modified". My model may eventually have many levels of navigation objects and collections. Is there an easier way to update the related entities? If there is no easier way, is this approach okay?
Here is the view model
public class ViewModel {
public ViewModel() { }
public ViewModel(Context context) {
this.Options = new SelectList(context.Options, "Id", "Name");
}
public Parent Parent { get; set; }
public SelectList Options { get; set; }
}
entity classes
public class Parent {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ChildOne ChildOne { get; set; }
public virtual ChildTwo ChildTwo { get; set; }
}
public class ChildOne {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Parent Parent { get; set; }
public virtual int OptionId { get; set; }
public virtual Option Option { get; set; }
}
public class ChildTwo {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Parent Parent { get; set; }
public virtual int OptionId { get; set; }
public virtual Option Option { get; set; }
}
public class Option {
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ChildOne> ChildrenOnes { get; set; }
public virtual ICollection<ChildTwo> ChildrenTwos { get; set; }
}
context
public class Context : DbContext {
public DbSet<Parent> Parents { get; set; }
public DbSet<ChildOne> ChildrenOnes { get; set; }
public DbSet<ChildTwo> ChildrenTwos { get; set; }
public DbSet<Option> Options { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Parent>()
.HasOptional(x => x.ChildOne)
.WithOptionalPrincipal(x => x.Parent);
modelBuilder.Entity<Parent>()
.HasOptional(x => x.ChildTwo)
.WithOptionalPrincipal(x => x.Parent);
}
}
controller
private Context db = new Context();
public ActionResult Edit() {
ViewModel viewmodel = new ViewModel(db);
viewmodel.Parent = db.Parents.Find(1);
return View(viewmodel);
}
public void Save(Parent parent) {
if (ModelState.IsValid) {
db.Entry(parent).State = EntityState.Modified;
db.Entry(parent.ChildOne).State = EntityState.Modified;
db.Entry(parent.ChildTwo).State = EntityState.Modified;
db.SaveChanges();
}
}
and view
#model MvcApp7.Models.ViewModel
<div id="Parent">
#Html.HiddenFor(model => model.Parent.Id)
#Html.TextBoxFor(model => model.Parent.Name)
<div id="ChildOne">
#Html.HiddenFor(model => model.Parent.ChildOne.Id)
#Html.TextBoxFor(model => model.Parent.ChildOne.Name)
#Html.DropDownListFor(model => model.Parent.ChildOne.OptionId, Model.Options)
</div>
<div id="ChildTwo">
#Html.HiddenFor(model => model.Parent.ChildTwo.Id)
#Html.TextBoxFor(model => model.Parent.ChildTwo.Name)
#Html.DropDownListFor(model => model.Parent.ChildTwo.OptionId, Model.Options)
</div>
</div>
<input id="SaveButton" type="button" value="save" />
<script type="text/javascript">
$('#SaveButton').click(function () {
var data = $('input, select, textarea').serialize();
$.post('#Url.Action("Save")', data, function () { });
});
</script>
Yes you are doing it right. When working with detached entities like in web application you must tell EF exactly what state each entity has. In your scenario you will first call:
db.Entry(parent).State = EntityState.Modified;
In EFv4.1 this operation causes that parent is attached to context. Only attached entities can be persisted to database when SaveChanges is called. The state of the entity is set to modified so context will try to update existing record in the database when persisting the entity. There is one more important thing which happened when you called that statement: All related entities are attached as well but their state is set to unchanged. You must manually set correct state of all related entities because EF doesn't know which one is new, modified or deleted. That is why your following calls are correct as well.
Edit:
Be aware that this updates values in parent and child but it doesn't update relation itself. If you swap one child with another the process is much more complicated when using independent associations.