I have two classes say Author and Book.
I show you simplified code here
class Author
{
int Id
string Name
virtual ICollection<Book> Books;
}
class Book
{
int Id
int AuthorId
string Name
bool Flag
}
In one of my views, I want to edit Author but when saving the object, I want to update some of his books and set the Flag to true;
My save method looks like this
public ActionResult SaveAuthor(Author data)
{
// call Web API to save Author and also update Flag for some of the data.Books.
// but data.Books is empty at this point.
}
In this method, the data parameter, includes author information but, it does not include the Books information so data.Books is empty. The reason is I don't have Books information in my View and View does not bind the Books information.
My question is how I can populate the Books collection inside SaveAuthor method or preferably inside Web API which saves the author.
One answer is I should include all books (as hidden field) for the author in view so I can get it back in data parameter.
Is there any other way achieve this goal without adding Books into View?
I'm using VS 2015 and probably MVC4
Related
I have 2 classes, like the below.
They can have very large collections - a Website may have 2,000+ WebsitePages and vice-versa.
class WebsitePage
{
public int ID {get;set;}
public string Title {get;set;}
public List<Website> Websites {get;set;}
}
class Website
{
public int ID {get;set;}
public string Title {get;set;}
public List<WebsitePage> WebsitePages {get;set;}
}
I am having trouble removing a WebsitePage from a Website. Particularly when removing a WebsitePage from mutliple Websites.
For example, I might have code like this:
var pageToRemove = db.WebsitePages.FirstOrDefault();
var websites = db.Websites.Include(i => i.WebsitePages).ToList();
foreach(var website in websites)
{
website.WebsitePages.Remove(pageToRemove)
}
If each website Include() 2k pages, you can imagine it takes ages to load that second line.
But if I don't Include() the WebsitePages when fetching the Websites, there is no child collection loaded for me to delete from.
I have tried to just Include() the pages that I need to delete, but of course when saving that gives me an empty collection.
Is there a recommended or better way to approach this?
I am working with an existing MVC site and I would rather not have to create an entity class for the join table unless absolutely necessary.
No, you can't... normally.
A many-to-many relationship (with a hidden junction table) can only be affected by adding/removing items in the nested collections. And for this the collections must be loaded.
But there are some options.
Option 1.
Delete data from the junction table by raw SQL. Basically this looks like
context.Database.ExecuteSqlCommand(
"DELETE FROM WebsiteWebsitePage WHERE WebsiteID = x AND WebsitePageID = y"));
(not using parameters).
Option 2.
Include the junction into the class model, i.e. map the junction table to a class WebsiteWebsitePage. Both Website and WebsitePage will now have
public ICollection<WebsiteWebsitePage> WebsiteWebsitePages { get; set; }
and WebsiteWebsitePage will have reference properties to both Website and WebsitePage. Now you can manipulate the junctions directly through the class model.
I consider this the best option, because everything happens the standard way of working with entities with validations and tracking and all. Also, chances are that sooner or later you will need an explicit junction class because you're going to want to add more data to it.
Option 3.
The box of tricks.
I tried to do this by removing a stub entity from the collection. In your case: create a WebsitePage object with a valid primary key value and remove it from Website.WebsitePages without loading the collection. But EF doesn't notice the change because it isn't tracking Website.WebsitePages, and the item is not in the collection to begin with.
But this made me realize I had to make EF track a Website.WebsitePages collection with 1 item in it and then remove that item. I got this working by first building the Website item and then attaching it to a new context. I'll show the code I used (a standard Product - Category model) to prevent typos.
Product prd;
// Step 1: build an object with 1 item in its collection
Category cat = new Category { Id = 3 }; // Stub entity
using(var db = new ProdCatContext())
{
db.Configuration.LazyLoadingEnabled = false;
prd = db.Products.First();
prd.Categories.Add(cat);
}
// Step 2: attach to a new context and remove the category.
using(var db = new ProdCatContext())
{
db.Configuration.LazyLoadingEnabled = false;
db.Products.Attach(prd);
prd.Categories.Remove(cat);
db.SaveChanges(); // Deletes the junction record.
}
Lazy loading is disabled, otherwise the Categories would still be loaded when prd.Categories is addressed.
My interpretation of what happens here is: In the second step, EF not only starts tracking the product when you attach it, but also its associations, because it 'knows' you can't load these associations yourself in a many to many relationship. It doesn't do this, however, when you add the category in the first step.
So if I want to add an artist to my website, and I create a model that holds that and some additional details like:
namespace SuperMusic.Models
{
public artist NewArtist { get; set; }
public IEnumerable<RecordCompanies> RecordCompanies
{
get { //some code to populate possible record companies }
}
Now I can have a view to create a new artist and my model could populate a drop down of record companies that the artist can be associated with.
But in my controller, I have to define "New Artist". I believe there are two ways to do this:
newArtistModel.NewArtist = context.artist.Create();
newArtistModel.NewArtist = new artist();
Is one of these more correct than the other? Or is there actually a difference in code and one of these is incorrect?
Thanks again for answer my noob questions!
the first option newArtistModel.NewArtist = context.artist.Create(); is the correct method for creating new instances as the Context will create Entity Framework aware proxy objects.
Proxy objects provide full support for navigation properties etc.
There's a more complete answer here
This artist is being used for the view, so it is only needed to render the form. There is no need for the EF context.
So when initializing the view:
newArtistModel.NewArtist = new artist();
Then, when you post the form and want to save the artist, you will need the context and can use: context.artist.Create();
There's probably a really basic answer to this question but I am new to Entity and MVC and am getting used to the basics.
I'm trying to automatically generate a MVC controller for the main table Sites with a dropdown for server. It seems like I would need a model like this:
public class Sites
{
public TTSites TTSites { get; set; }
public List<servers> server { get; set; }
public Sites()
{
server = new List<servers>();
}
}
This is using the classes TTSites and servers both with string server
But if I set this as my model class and my entity database as data context it says I need to define a key. Should I be using the base classes instead of the model or what? Do i need to set something up in the model or base class?
It seems like you've got some terminology confused. You code the controller actions in a controller class, and the routing engine determines what controller action to call based on the URL. For example, if you have a HomeController class with a default Index action, it might look like this:
public ActionResult Index()
{
// code here
}
This would be invoked with the default routing, if you went to your site with a URL like this (let's say your site can be hit via the www.mysite.com URL:
http://www.mysite.com/Home
That would get you into the Index action in the controller.
Ordinarily, one would use a view model to use on the UI side, and that would be populated from an entiy with the data you need in the view itself. If you had two entities like TTSite and Server, you'd populate the Sites view model like so, as a (very simple) example:
public ActionResult Index()
{
var servers = yourDbContext.Servers.ToList();
var ttSite = yourDbContext.TTSites.GetByID(1); // retrieve one entity by its ID value, this would be acquired dynamically based on some sort of user input rather than hard-coded
var viewModel = new Sites(servers);
viewModel.TTSite = ttSite;
return View(viewModel);
}
I'm not including anything regarding making drop-downs, just illustrating getting data into a view model and then creating a view with that view model.
Note that you would not use the Sites class as an entity but rather a view model, and setting its data based on entities from your database. You wouldn't be setting any primary keys in a view model class; those are the concern of the data model, and you've presumably already got those entities (such as TTSite) set up in a usable fashion in your data layer.
Once you've got a controller action and a view up and working, you can turn to getting the view model data into a form usable by a drop-down list, and going from there.
I have a Note domain object which belongs to a Document object. Only an owner of a Document can add Notes so in the Document class there is a canUserAccess() method. In my service layer I can call canUserAccess() to ensure a user only adds Notes to Documents they own.
This works well for create but I have hit a problem with my Note edit action. On post, the viewmodel is mapped to a Note object, providing it with a DocumentID. Problem is, a malicious user could send in a DocumentID on which they do have permission and thus edit a Note belonging to a Document they don't. In my service layer I cannot reliably use the supplied DocumentID yet I need to get the related DocumentID in order to verify that the user can edit the Note. This is an example:
public void editNote(Note note)
{
note.Document = documentRepository.Find(note.DocumentID);
if(note.Document.canUserAccess())
}
How do I get around this? It seems I need to avoid passing the DocumentID with the edit viewmodel but how do I hydrate the related Document in the service layer? There is probably a really simple solution to this and I am just tying myself up in circles!
You do this with BindAtrribute for the model or for the action method by adding a white list with the properties you want to be bound :
for the model
[Bind(Include="NoteText,NoteTitle")]
public Model{}
for the action method
public ViewResult Edit([Bind(Include="NoteText,NoteTitle"]Note note)){}
or use a black list for the properties you don't want to bind :
[Bind(Exclude="DocumentID")]
public Model{}
I would personally use white list with the model class. You might find this article interesting. The last section for under-posting is your case.
Then you don't have the documentID passed, but in your action you can do this:
public ViewResult Edit(Note note)
{
Note updateNote = nodesRep.Get(note.noteID);
if(updateNote.Document.canUserAccess())
{
//replace the values of the updateNote
//you can place this line in your base Repository class
context.Entry<Note>(updateNote).CurrentValues.SetValues(note); //I assume EF 4.1
}
}
I have one view page (MyView.aspx) and many data sources to bind on this page.
Lets say it has Books, Publishers, Comments, etc.
Each one of those has an object which provides a List, List, etc.
In my view, what are my optiosn for multilple model biding?
I want to check to see if each one is empty, and then enumerate it. But I can't check Model.Count() because wouldn't Model be made of all those objects if I set the page to inheriet from ?
What are my options? Should I load each content area in a control/partial view?
Or can I just dump each object into ViewData and then check the count by casting in the view?
Thanks so much for taking a look at my problem.
Have you considered using a ViewModel that contains all Lists of all of your different data fields and using that to populate your View?
Example:
ViewModel:
public class MyViewModel
{
List<Book> Books {get; set;}
List<Publisher> Publishers {get; set;}
List<Comment> Comments {get; set;}
//Other fields...
//Constructors...
}
Then in your View you could simply check if a specific field was null prior to enumerating through it:
View:
if(Model.Books.Count() != 0)
{
//Enumerate through results here
}
Rionmonster's is probably the best solution - another is to use strongly typed partial views. You could load everything into the view data and then inject each segment into the respective partial view.