asp.net MVC 4 - Many to Many data not updating - Updating Entity Framework with ViewModel Edit View - asp.net

asp.net mvc 4, Entity Framework 5, SQL Server 2012 Express
I have a Place model:
public virtual int PlaceID { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
public virtual string Name { get; set; }
and a related Tag model:
public virtual int TagID { get; set; }
public virtual string Name { get; set; }
public virtual string NamePlural { get; set; }
public virtual ICollection<Place> Places { get; set; }
many to many relationship.
There is a view with the following ViewModel associated. In it I can edit place details - and edit what tags are associated with the place (for example, the place might have a 'restaurant' tag and a 'bar' tag - and perhaps I want to add a 'cafe' tag, and remove 'restaurant' tag).
PlacesWithTagsViewModel:
public Place place { get; set; }
public ICollection<Tag> SelectedTags { get; set; }
When the view does an httpost back to controller - I update tags like this:
place.Tags = SelectedTags
db.Entry(ptvm.place).State = EntityState.Modified;
db.SaveChanges();
However, place properties update (eg Name) - but Tags always stay the same.
How can I update tags?

Avoid db.Entry(ptvm.place).State = EntityState.Modified; as it causes conflicts with no updation.
You must use UpdateModel(table object, "model");
Full Example is as belows :
[HttpPost]
public ActionResult PlaceTag(PlacesWithTagsViewModel model)
{
if (ModelState.IsValid)
{
tag tagtest = GetTagById(model.tagid);
tag.name= model.tag.name;
tag.nameplural = model.tag.nameplural;
UpdateModel(tag, "model");
db.SaveChanges();
return RedirectToAction("Index", "Dashboard", new { id = 5 });
}
}
The advantage of UpdateModel is that you have to mention only those fields which you update avoiding those which remain static. In this way you can update your related data with Viewmodel in Edit View.

Related

ASP.NET Razor - How to create a POST form for List of objects?

I need to create a POST form to add new objects to database. I have to create a Razor page where I can add new lesson form on click of a button. And after it on click of another button all the lessons should be added to DB context. I still don't know how to do it so I want you to help me
public class Course
{
public Guid Id { get; set; }
public string Category { get; set; }
public string Title{ get; set; }
public List<Lesson> Lessons { get; set; } = new List<Lesson>();
}
public class Lesson
{
public Guid Id { get; set; }
public string Title { get; set; }
public string Text { get; set; }
}
Here is some image of what I mean:
DB has a Course table and a Lesson table. Please tell me how I can create a page to create new 'Course' with dynamic amount of 'Lessons'
Please try with this in your controller method you need to pass from the page.
public ActionResult PostMethod(Course course, FormCollection formCollection)
{
string title = course.Title; // Title value
string category = course.Category; // Category value
//add course into database
if (course.Lessons != null && course.Lessons.Count > 0)
{
foreach (var item in course.Lessons)
{
//add Lessons into database
}
}
}
Please note in this example you are going to perform many operations in databases
you have to manage transaction as well for this.
Using Transactions or SaveChanges(false) and AcceptAllChanges()?

EF6 automatically includes nested objects

Probably a simple question, but there is something I can't get my head around.
My structure Bundle -> BundleMembers -> InsuranceTypes
When I retrieve a single record form BundleMembers, and I include Bundle. EF6 automatically includes all BundleMembers in the Bundle
Example:
public async Task<List<BundleMember>> GetBundleMembers(string userId, bool includeBundle, bool includeInsuranceTypes)
{
var bundleMembers = db.BundleMembers.Where(m => string.Equals(m.UserId, userId, StringComparison.CurrentCultureIgnoreCase));
if (includeBundle)
{
bundleMembers = bundleMembers.Include(o => o.Bundle);
}
if (includeInsuranceTypes)
{
bundleMembers = bundleMembers.Include(m => m.BundleMemberInsuranceType);
}
return await bundleMembers.ToListAsync();
}
I call the function like this:
GetBundleMembers(_userManager.GetUserId(User), true, false)
Do I have to access the data from Bundle, to avoid this?
EDIT 1:
My data model looks like this:
public class Bundle
{
public int BundleId { get; set; }
public State State { get; set; }
public ICollection<BundleMember> Members { get; set; }
public ICollection<InviteLink> InviteLinks { get; set; }
public string BundleName { get; set; }
public string Description { get; set; }
public string ImagePath { get; set; }
}
public enum State
{
NotApproved,
Approved,
Disabled,
Rejected
}
public class BundleMember
{
public ApplicationUser User { get; set; }
public string UserId { get; set; }
public int BundleMemberId { get; set; }
public int BundleId { get; set; }
public Bundle Bundle { get; set; }
public bool Admin { get; set; }
public int Price { get; set; }
public int Coverage { get; set; }
public ICollection<BundleMemberInsuranceType> BundleMemberInsuranceType { get; set; }
}
I did not include BundleMemberInsuranceType and InviteLink as they are working fine.
Relevant part of ApplicationDbContext:
public DbSet<Bundle> Bundles { get; set; }
public DbSet<BundleMember> BundleMembers { get; set; }
As suggested in comments:
The described behavior is actually expected. Since includeBundle is set to true, both Bundles and referenced BundleMembers are in the context, and relationship fixup will set all navigation properties according to the FK relationships.
Obviously, this works both from BundleMembers to Bundles and from Bundles to BundleMembers since .Include does nothing more than create the SQL statements to load the related entries into the context as well and relationship fixup will do the rest.
To have the Bundles not have BundleMembers, you'll have to load them without the BundleMembers in the context and set the navigation properties yourself (EF will always set both direct and inverse navigation properties). In order to do this, there are two main ways:
Either load your bundles in a fresh context without the previous loaded BundleMembers (best practice is to load them into memory since EF navigation properties are loaded due to eager loading; you could have entries attached to two contexts and an exception will be thrown) or
Detach your BundleMembers from the context before loading the Bundles into it.

Implement one-to-many relationship in ASP.NET MVC 5

This is my project model :
public class Project
{
public int ProjectID { get; set; }
public string ProjectTitle { get; set; }
public string ProjectDetails { get; set; }
public virtual ICollection<Proposal> Proposals{ get; set; }
}
This is my Proposal model :
public class Proposal
{
public int ProposalID { get; set; }
public string BidTitle { get; set; }
public string BidDetails { get; set; }
public virtual Project Project { get; set; }
}
As you can see, there is one-to-many relationship between Project and Proposal. In
mydomain/Project/Details/ProjectID
view, I want to put a button, when this button is clicked, user can create a new Proposal for that project. My question is how I can pass that project's information to bid? If you can give me some tips about it, I'd be really glad. Thanks.
Create a model known as a viewmodel, which includes both the models you want to use under the same view. Your would look something like this:
public class ProposalAndProjectModel
{
public Proposal Proposal { get; set; }
public Project Project{ get; set; }
}
Save it as something like ProposalAndProjectModel.cs and then in your view, reference this model.
Now in your view you will be able to do the following:
Model.Proposal.propertyName
or
Model.Project.propertyName
This should help you as for getting the correct parameters for creating new objects.
You say when user click button user goto another page. You can sen projectID as get to that page. Thats how you can get that projectID.

Linq to Entities - Delete / Add related data (many to many relationship)

I have places - with related tags:
have a Place model:
public virtual int PlaceID { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
public virtual string Name { get; set; }
and a related Tag model:
public virtual int TagID { get; set; }
public virtual string Name { get; set; }
public virtual string NamePlural { get; set; }
public virtual ICollection<Place> Places { get; set; }
many to many relationship.
So for example - a place could have a 'bar' and a 'cafe' tag. (and many places could have those tags also).
I can add places with related tags fine. However, how can I manipulate the tags that are related to places with Linq? (add / delete / edit).
Also - if I try and save a place model, with related tags - the place is updated but not the tags - how could I do this?
place.Tags = SelectedTags
db.Entry(ptvm.place).State = EntityState.Modified;
db.SaveChanges();
Thanks.
You should use micromanagement with entity state of entities only if you know what and why are you doing.
In this case, you should give control of it to entity framework.
db.Places.Attach(place);
context.Entry(place).Collection(p => p.Tags).Load();
foreach(var tag in SelectedTags)
{
place.Tags.Add(tag);
}
db.SaveChanges();
Additional note: You cannot change navigation property ICollection to new collection. You can only add, remove and clear. If you want to remove or clear this collection, you should load it from database before.

asp.net MVC ViewModel construction

I'm tying myself in knots - and thought it best to take a big step back, and back to basics.
I understand I should be using a ViewModel, so that is what I'm trying to contruct.
My demo app will have 4 sections (4 different parts of a form to complete):
Get Date/Number of days from user
Use that data to query the database, and return a list of qualifying records - each of these will have a unique ID of TypeID - and for each of these, they should also have 2 dynamic DropDownLists associated with them (so that whatever is selected in ListBox3 for each of the lists, corresponds to the TypeID3 (and whatever ID that has)
user will then be able to select Extras, again from a drop down list populated dynamically
users Name/Add/Tel will be collected
My "View" of what the ViewModel needs to look like/hold is:
I beleive my viewModel should look something like this:
public class SearchViewModel
{
public DateTime Date{ get; set; }
public int Days { get; set; }
public IQueryable<TypeID> TypeIDs { get; set; }
public IQueryable<LB1Item> LB1Items { get; set; }
public IQueryable<LB2Item> LB2Items { get; set; }
public IQueryable<Extras> Extras { get; set; }
public string Name { get; set; }
public string Add { get; set; }
public string Tel { get; set; }
public string email { get; set; }
}
First of all - is this how you would construct a ViewModel for what I've described above? I'm not certain of the DropDown Boxes, as for each form, there could be 1, 2, 3....10, 11, 12 for each TypeID retrieved - based on the Date selected.
Each of the drop down boxes for LB1Item and LB2Item - need to have their selected values stored against the TypeID for each line also.
This is what I think the class should look like for 1 drop down:
public class LB1Item
{
public String TypeName { get; set; }
public long TypeID { get; set; }
public int NumSelected { get; set; }
public int TypeCount { get; set; }
public IEnumerable<SelectListItem> CarsAvail
{
get
{
return new SelectList(
Enumerable.Range(0, TypeCount+1)
.OrderBy(typecount => typecount)
.Select(typecount => new SelectListItem
{
Value = typecount.ToString(),
Text = typecount.ToString()
}
), "Value", "Text");
}
}
}
Does that look ok also? Or am I overcomplicating what I'm trying to achieve?
I'd also like, after POSTing back the data after each stage (1, 2, 3, 4) to be actively populating the ViewModel with the selected values - and passing it back down to the view, so that I can retrieve it for the next Step.
What I want to end up with is something like this:
Date: 01/09/2012
Days: 4
{ List:
TypeID: 3059 ListBox1: 2 ListBox2: 8748,
TypeID: 2167 ListBox1: 7 ListBox2: 2378,
TypeID: 4983 ListBox1: 4 ListBox2: 5873
}
{List:
ExtraID: 4324,
ExtraID: 3878,
ExtraID: 4872,
ExtraID: 7698,
ExtraID: 2873
}
Name: Mark
Add: My town
Tel: 0912378
Email: me#me.com
Thanks for any help/pointers/samples...
Mark
For this type of solution I would separate out each section you want to render as individual views and use Ajax calls using Jquery ajax method. I would also use KnockoutJS to handle the views in your client. So you will essentially have two ViewModels, one in JavaScript in the client and one in MVC for returning the pieces you need as JSON for the Ajax calls from the client. Section 1 of your view is entered by the user into the client so you do not need it on the Controller side. Section 1 is basically the data used to query for Section 2. No need to make your collections IQueryable either since you will not being querying the lists that are returned. Your ViewModel on the Controller side might look something like this:
public class Section2
{
public List<TypeID> TypeIDs { get; set; }
public List<LB1Item> LB1Items { get; set; }
public List<LB2Item> LB2Items { get; set; }
}
public class Section3
{
public List<Extras> Extras { get; set; }
}
public class Section4
{
public string Name { get; set; }
public string Add { get; set; }
public string Tel { get; set; }
public string email { get; set; }
}
So the steps that would be taken are that when an event is thrown that the date and days have been entered by the user an Ajax call is made back to a controller with entered data and days to query for the information that will populate Section 2. The controller returns the Section2 ViewModel as JSON and it is rendered in the HTML using Knockout. Then when the user selects from the lists in Section 2 an event is thrown to query the controller again to return the information needed to populate Section 3, and the cycle is repeated.
There is an excellent example on using Knockout to do exactly what you are trying to do here.

Resources