I want to build up an object graph using Entity Framework 4.2.
Right now, I have POCO entities, using ICollection for navigation properties. I want to avoid using EntityCollection or anything EF-specific.
I want to avoid massive joins caused by using Include excessively. Given an object, I want to populate its navigation properties, resulting in a separate database query.
Is there a way to populate an ICollection directly? Right now, I am working around the problem, but it is really painful.
// grab the user, brand users and brands
User user = entities.Users
.Include(item => item.BrandUsers.Select(brandUser => brandUser.Brand))
.Where(item => item.Name == userName)
.SingleOrDefault();
// grab the pending share grants and brands
entities.Users
.Include(item => item.ToShareGrants.Select(shareGrant => shareGrant.Brand))
.Where(item => item.Id == user.Id)
.Load();
return user;
One thing I don't like about this approach is that I am re-querying the top-level object. If I don't do this, the navigation property isn't populated (left NULL) when there are no objects returned. For instance, the following code only works if results are returned:
entities.ShareGrants
.Include(item => item.Brand)
.Where(item => item.ToUserId == user.Id)
.Load();
I was curious if there was just a method I wasn't aware of in entity framework for building these types of relationships. If anyone knows an easy approach to filling out navigation properties in steps, I'd appreciate a code sample.
Try turn off lazy loading for your current query, you may just put this in a using block
entities.ContextOptions.LazyLoadingEnabled = false;
Your question is not very clear. Why not use multiple Includes in the same query
User user = entities.Users
.Include(item => item.BrandUsers.Select(brandUser => brandUser.Brand))
.Include(item => item.ToShareGrants.Select(shareGrant => shareGrant.Brand))
.Where(item => item.Name == userName)
.SingleOrDefault();
The short answer to this question is that EF4 did not directly support the functionality I wanted. In order to prevent a massive join and break the results out across multiple calls to the database, the top-most entity must be downloaded from the database again. This means the left-most columns in the result set will be the columns of that entity repeated for each record.
Related
I was following this tutorial http://www.asp.net/mvc/tutorials/mvc-music-store
when I stumbled on this piece of code.
public ActionResult AddToCart(int id)
{
// Retrieve the album from the database
var addedAlbum = storeDB.Albums
.Single(album => album.AlbumId == id);
// Add it to the shopping cart
var cart = ShoppingCart.GetCart(this.HttpContext);
cart.AddToCart(addedAlbum);
// Go back to the main store page for more shopping
return RedirectToAction("Index");
}
I don't understand two things:
1)
var addedAlbum = storeDB.Albums
.Single(album => album.AlbumId == id);
What is this code doing? I don't know what the operator => does. Also I guess .Single is some function for the database?
2)
This function is having a call to itself? I don't see how it adds the album to the cart this way. Wouldn't this cause a function to go into an infinite loop?
It seems there are a lot of core C# that you aren't quite familiar with yet.
the => operator is the lambda operator, which is a succinct way of writing an inline function.
The Single function is an extension method which in this case is makes a call to the database. This method makes use of a neat feature known as expression trees to convert the strongly typed C# comparison into the corresponding SQL code. How it works is a pretty advanced topic, so for now just consider it "magic".
The AddToCart method of the cart object is different from the AddToCart controller action method the code is currently in. I don't have a link for that, since that's fairly basic object-oriented programming.
I would assume that cart.AddToCart will actually update the database.
Also read up on LINQ for a better understanding. This is most likely either Linq To Sql or LINQ to Entities using the Entity Framework.
The Action is being passed the ID of a album which can then be retrieved from the database with storeDB.Albums.Single() call. (the lambda is saying "find the entry in the database where the value in the AlbumId column matches the ID passed to the controller.") Think of .Single as the LINQ facsimile of:
SELECT TOP(1) *
FROM Albums
WHERE Albums.AlbumId = <id>
It's then grabbing the user's shopping cart and adding that fetched album object to the cart.
Then you're redirected to the index where it can list all entries in the cart.
I have a view that calls on four different partial views (.ascx) to populate a portion of the view via RenderAction call. Each of the partial views use the same view model, but each returns a different set of data via their own EF query in the underlying model. As you would assume from the sharing of the viewmodel, the partial views all return pretty much the same type of information -- the difference is in the filtering. E.g. "New Products" vs. "Popular Products" vs. "Recommended Products", etc.
I'm getting the data that I want, but I need to address the structure because my performance is pretty poor. The performance of each individual query doesn't seem too bad (I've used LinqPad and tested the generated SQL in SQL Server and the performance is great). However, altogether, the page load time is pretty poor as I switch categories and reload the page.
Rather than calling 4 queries against the SQL server, can I call one that pulls everything (for all 4) and then filter the results into the individual partial views? Would this be better?
Many thanks for your suggestions/advice.
Yes. Doing one query and filtering would be much better.
List<Widgets> widgetsFromQuery = (from w in db.Widgets
where w.Name.EndsWith("-sprocket") ||
w.Name.EndsWith("-seat") ||
w.Name == "toaster"
select c).ToList();
Calling ToList() at the end, forces Linq to perform the query immediately.
Then you use widgetsFromQuery as your Model, and in each view, filter like this:
var filteredModel = Model.Select(w => w.Name.EndsWith("-sprocket"));
var filteredModel = Model.Select(w => w.Name.EndsWith("-seat"));
var filteredModel = Model.Select(w => w.Name == "toaster"));
Further performance enhancements would be something like:
1) Cache the output of the query in the session (if it's user specific) or application cache if not.
2) Make each view tied to an action, load with AJAX, and use output cache attributes on the actions.
Let me explain the whole context:
I'm using ASP.NET MVC 2, EF4 (POCO).
I trying to do a generic repository for my app.
I'm having problem on updating a many to many relationship.
I have an item that is related to other by a many to many table. In the View, the user picks the desired Categories, and send just the chosen id's to the Controller.
Then, the Controller queries the Category Repository, adding it to the main item:
item.Categories.Add(CategoriesRepository.Single(id);
But, when I go the Repository and try to save like this:
Entities.ApplyCurrentValues(entity);
Context.SaveChanges();
But, the state of my entity is Added.
Then, I Cannot save my entity :(.
How can I solve this problem?
Thanks for your answers.
I have in the View, the following code:
<%= Html.CheckBoxList("Categories", ((IEnumerable<Categories>)ViewData["Categories"]).ToDictionary(c => c.ID.ToString(), c => c.Name)
, Model.Categories.ToDictionary(c => c.ID.ToString(),c => c.Name )) %>
Where CheckBoxList is a HTMLHelper.
Im putting the ids as values in the View, because I dont know other way to put and then get this information from the View.
How can I use the ObjectStateManager.ChangeRelationshipState method?
Like this? :
itemRepository.Db.ObjectStateManager.ChangeRelationshipState(item, item.Categories, "Categories", System.Data.EntityState.Modified);
I trying in this way, but it returns error.
Help! lol
You've got a few problems.
1) ApplyCurrentValues only works for scalar-properties. Since your trying to add a Category to the Categories navigational property on Item, this will not work.
2) You say this:
the user picks the desired Categories, and send just the chosen id's to the Controller.
How can your Controller accept a bunch of id's? How is this model binding done? We need more info on how your View is bound to your model, what's being passed to the action method. But it sounds like you need to redesign this particular View with the help of a ViewModel.
3) Change tracking with POCO's in MVC is a royal pain in the butt. In your scenario, you'll need to use ObjectStateManager.ChangeRelationshipState to manually set the Categories relationship to **Modified.
Honesty though, it's more pain than it's worth. I went through this same problem.
Cop it on the chin - go grab the entity first and use Controller.UpdateModel:
[HttpPost]
public ActionResult Item(Item item)
{
// get the existing item
var existingItem = ItemRepository.Single(item.Id);
// use MVC to update the model, including navigational properties
UpdateModel(existingItem);
// save changes.
Context.SaveChanges();
}
I have 1 to many relationship with the following tables - Person and Email. When using linq to sql and ASP.Net MVC, I'd like to show the first email or an empty string in my Person view using code like this:
<%= Html.Encode(Model.Emails.FirstOrDefault().EmailAddress) %>
In cases where there are no email rows, I receive a NullReferenceException. I can return null safe values from SQL by using a view or sproc, but I'd like to just stick with generic linq to sql objects bound to tables.
Model.Emails.Select(x => x.EmailAddress).FirstOrDefault() ?? string.Empty
<%= Html.Encode((Model.Emails.FirstOrDefault() ?? new Email { EmailAddress = string.Empty }).EmailAddress) %>
Would work, not super clean to read though.
My vote for:
Model.Emails.Select(z => z.EmailAddress).DefaultIfEmpty("zzz").FirstOrDefault();
I thought that you could do it all inside the FirstOrDefault, but I was wrong-o! However, I also forgot that when you use DefaultIfEmpty you can just call First().
Model.Emails.Select(z => z.EmailAddress).DefaultIfEmpty("zzz").First();
Of course, replace ZZZ with just "" (not string.empty, that is unnecessary), but it is nice to see those records where the default is being chosen explicity when you are first writing it.
I'm using NerdDinner as a guide for my first MVC/LINQ to SQL project. It discusses the use of the ViewModel pattern when a View needs data from multiple sources - in their example: Dinners and Countries (serves as the drop down list).
In my application, the problem is a bit different. It's not so much different data, rather data linked via a key constraint. I have a Story table that links to aspnet_users via the UserId key. I would like to have easy access to the UserName for each story.
Since I'm using the repository pattern and returning IQueryable in some cases for deferred execution, I'm struggling with the correct way to shape the data. So I'm using this VideModel pattern right now to make it work but not sure if it's the right way.
Instead of returing IQueryable Story (which wouldn't work since I need the UserName), I'm returning a new custom class UserStory, which has a Story property and a string Username property.
What are your thoughts?
It seems like your question has less to do with MVC as it is simply a question about how to access the story data based on the username string.
Would it be possible to create a view in your database with all the UserStory data, the username, along with userid in it? That way, you could select from the view based on the username you have.
To create the view, you would simply have to do a join between the user table and the userstory table based on the userid.
After that, you could still use the repository pattern with the IQueryable being returned.
If you are wanting to do updates, it would be simple to do since you still have the userid, and would be able to link back to the actual table which would need the update.
If you look at Kigg, you will see that they mess about with the initial model to create custom ViewModels. That's the thing that NerdDinner doesn't cover in any detail. You could create a StoriesWithUserName class that inherits from Stories, but adds a new property - UserName. Then you return that to your View which would inherit from IEnumerable<StoriesWithUserName>
[EDIT]
Oops. Didn't spot that you already did this :o)
Using the repository pattern and returning an IQueryable of Stories is fine. The relationship allows you to access the the username value something like this >>
Assuming you are returning the IQueryable in your model object:
foreach(Story story in Model.Stories)
{
// do something with the value
response.write(story.aspnet_user.UserName);
};
Your Repository method would look like this:
public List<Stories> GetStories(Guid UserId)
{
return datacontext.Stories.Where(u => u.UserId = UserId).ToList();
}
The relationship will automatically provide you with access to the UserName value in the foreach loop i first mentioned. nothing more is required.
I'm not sure why your pagination control failed on Count() though??
Hope this helps