How to include navigation properties eagerly for child objects - c#

Say my object graph looks like:
User
=> Friends
=> Clicks
=> Urls
So when I load a User, I also want to eagirly load the navigation property Friends. And I want the friend object to eagirly load Clicks and so on.
Using the code I have now I can only do this for 1 level:
public User GetUserById(int userId)
{
return Get(x => x.Id == userId, includeProperties: "Friends").FirstOrDeafult();
}
Is this possible?
I'm using MVC 4.1
I'm currently using the repository pattern based on http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application

Apparently this repository implementation is just splitting the includeProperties parameter by comma (includeProperties.Split(new char[] { ',' }) and then calls
query = query.Include(includeProperty);
for each element in the result array of the split. For your example you can use a dotted path then:
return Get(x => x.Id == userId, includeProperties: "Friends.Clicks.Urls")
.FirstOrDefault();
It will load all entities on the path from the root User entity to the last navigation property Urls.
If you had another navigation property in User - say Profile - and you would want to eagerly load that as well, it seems that this syntax is supported then:
return Get(x => x.Id == userId, includeProperties: "Profile,Friends.Clicks.Urls")
.FirstOrDefault();

Related

LINQ include a subset of a query

I have an Entity in EF Core that is using the structure like:
Course has an entity CourseUserRoles which has the CourseId, UserId and RoleId.
CourseViewModel has the same structure as Course, except CourseUserRoles, instead it has two booleans IsAdministrator and IsContributor, that are related to the RoleId.
I am trying to make a query that won't bring all CourseUserRoles for every course queried, but only the ones specific for that user.
I saw that syntactically the below is correct:
query = query.Include(x => x.CourseUserRoles.Where(y => y.UserId == userId));
where the query is trying to return a list of courses, I just want to include the ones that have the same Id as the user.
The problem is that the above is throwing an exception.
Is it possible to only include the CourseUserRoles when the course has the UserId? If it doesn't have it would return null or empty list.
I typically do this by creating separate queries and concatenating as follows:
query = query.Where(x => x.CourseUserRoles.UserId != userId)
var queryWithInclude = query.Where(x => x.CourseUserRoles.UserId == userId)
.Include(x => x.CourseUserRoles)
var fullDataset = query.Concat(queryWithInclude);
This keeps both query objects as type IQueryable and not IEnumerable, allowing execution to happen in SQL/server-side rather than in memory.

Return Selected Properties of Single Object in EF Method Syntax

//This works, but seems incorrect to me
Object selection = db.ExampleTable
.Where(s => s.Id == id)
.Select(s => new { s.Id, s.PropIWantToShow })
.SingleOrDefault();
//This seems correct, but does not work
Object selection = db.ExampleTable
.SingleOrDefault(s => s.Id == id)
.Select(s => new { s.Id, s.PropIWantToShow });
db is our Entity Framework data context.
My goal is to select a single entry matching the provided id in ExampleTable. If an entry is not found, this is to return null. However, EF doesn't seem to let me select a single object and then only return specific properties. How do I accomplish this or is the first example I provided correct?
I did check this question:
select properties of entity ef linq:
Unfortunately you cannot conditionally load properties of related entity - you either load whole door entity, or don't include that entity.
But the answer just doesn't seem right, but obviously "seems" is a very weak statement.
Your first method is correct:
//This works, but seems incorrect to me
Object selection = db.ExampleTable
.Where(s => s.Id == id)
.Select(s => new { s.Id, s.PropIWantToShow })
.SingleOrDefault();
Your second method gets you a single object, not an IQueryable<T> object that LINQ would work with. If you want to convert from one type of object to another, that isn't a LINQ thing. You can still, but it'll be more convoluted. Something like:
var selection =...;
var newselection=new { Id=selection.Id, PropIWantToShow=selection.PropIWantToShow };
but this is very bad because you DID retrieve the entire object from the DB, and then just threw away most of it. Your first method only returns 2 fields from the DB.
If you want your function to return null if condition doesn't match then use FirstorDefault() instead of SingleorDefalut(). So if you want to match an id and return an object then do it like this :
return db.ExampleTable.FirstorDefault(c=>c.Id == id);

asp.net MVC Where in List

I building my first application with c# and sp.net MVC 5, so far so good :)
Now I have a problem, we using 2 User Tables, first one contains the username, other the user data.
string user = User.Identity.Name;
var data = db.FE_Benutzer;
var collection = data.Where(o => o.Benutzername == user).Select(x => new
{
id = x.ID,
name = x.Name,
hauptbereiche = x.Hauptbereich.ToList()
});
var dataHauptbereich = db.Hauptbereich;
var collectionHauptbereich = dataHauptbereich.Where(o => collection.ElementAt(0).hauptbereiche.Contains(o)).Select(x => new
{
id = x.ID,
name = x.Name
});
return Json(collectionHauptbereich, JsonRequestBehavior.AllowGet);
I getting this error
LINQ to Entities does not recognize the method '<>f__AnonymousType63[System.Int32,System.String,System.Collections.Generic.List1[scorring.Models.Hauptbereich]] ElementAt[<>f__AnonymousType63](System.Linq.IQueryable1[<>f__AnonymousType63[System.Int32,System.String,System.Collections.Generic.List1[scorring.Models.Hauptbereich]]], Int32)' method, and this method cannot be translated into a store expression.
hauptbereiche = x.Hauptbereich.ToList()
contains a list of ids where the user have premission to.
When I fetching the data
dataHauptbereich.Where
I wont to include only the ids I have in the list
how is this possible?
Entity Framework doesn't know how to turn ElementAt into SQL. See this answer for more information: Getting the first result from a LINQ query - why does ElementAt<T>(0) fails when First<T>() succeeds?
Try
dataHauptbereich.Where(o => collection.ElementAt(0).hauptbereiche.Any(h => h.ID == o.ID))
Or
dataHauptbereich.Where(o => collection.Any(c => c.hauptbereiche.Any(h => h.ID == o.ID)))
I'm having a bit of a time deciphering exactly what you're trying to achieve with your code here, but it looks to me like your simply querying Hauptbereichs that belong to a particular user. Your first query selects an anonymous object composed of id, name and hauptbereiche, but of these you only ever use the hauptbereiche property. Then, in your second query, you merely selecting Hauptbereichs that match an item in this hauptbereiche property's collection. Actually, here, you're only comparing values from the first item in the original collection, which begs the question of why you're selecting anything other than the first item. That, and this second query is entirely redundant because if the items match that means you already had the items in the first place. You could get the same info directly from collection.ElementAt(0).hauptbereiche without issuing the second query.
So, here's a couple of simpler options:
If you're trying to get all the Hauptbereichs that belong to all the FE_Benutzers where Benutzername == user then just do:
var collectionHauptbereich = db.FE_Benutzer.Where(m => m.Benutzername == user)
.Include(m => m.Hauptbereich)
.SelectMany(m => m.Hauptbereich);
If you want just the first FE_Benutzer item's Hauptbereichs, then do:
var benutzer = db.FE_Benutzer.Where(m => m.Benutzername == user)
.Include(m => m.Hauptbereich)
.FirstOrDefault();
var collectionHauptbereich = benutzer != null
? benutzer.Hauptbereich.ToList()
: new List<Hauptbereich>();

Lazy Loading, Collections and Single or First

I have the following model:
A User has a collection of Photos. In the Photo model, there is a property called IsProfilePhoto.
When I do the following, the results are not as expected.
var user = dbContext.Users.SingleOrDefault(u => u.Id == 1);
var profilePhoto = user.Photos.SingleOrDefault(p => p.IsProfilePhoto);
With lazy loading on, this performs two queries.
The first one gets the user by id as expected.
The second one however, gets the collection of photos by user id and then in memory does the match on IsProfilePhoto.
I was hoping that with lazy loading on it would add the SingleOrDefault to the query as well.
Is this just not possible and I must always do the inverse? E.g.
var profilePhoto = dbContext.Photos.SingleOrDefault(p => p.UserId == 1 && p.IsProfilePhoto);
var user = profilePhoto.User;
I get the reasoning, there are just certain reasons why it's more convenient to go from the User to get the profile photo.
You can get the result with a single database query by using a projection:
var userWithProfilePhoto = dbContext.Users
.Where(u => u.Id == 1)
.Select(u => new
{
User = u,
ProfilePhoto = u.Photos.Where(p => p.IsProfilePhoto).FirstOrDefault()
})
.SingleOrDefault();
userWithProfilePhoto.User and userWithProfilePhoto.ProfilePhoto are the two entities you are looking for.
You have to use Eagerly loading to load multiple levels. Lazy load, loads the level when you access this.
var user = dbContext.Users.Include(u => u.Photos).SingleOrDefault(u => u.Id == 1);
var profilePhoto = user.Photos.SingleOrDefault(p => p.IsProfilePhoto);
This is the subtle difference in LINQ methods.
You can do the filtering as part of the query as:
var profilePhoto = user.Photos.Where(p => p.IsProfilePhoto).SingleOrDefault();
Due to this behavior in LINQ to Entities, I try to always use the Where method for the condition and the parameterless overloads for First, Single, FirstOrDefault, SingleOrDefault, Any, and Count.
Edit:
My bad, MSDN mentions it directly, but any reference to a navigation property (when lazy loading is enabled) loads all of the related records.
My best suggestion then is to a) accept extra database access, or b) query as you did in your alternative example, with the first table being the 'many' of the 'one-to-many' relationship.
First, and FirstOrDefault are lazy loaded, Single, and SingleOrDefault eagerly loaded. If you don't need a an exception thrown in case of several items returned by the query, you can change it to FirstOrDefault.

Conditionally Include() in Entity Framework

I'm using EF4.3 with DbContext.
I have an entity that I store in cache, so I need to eager load the necessary data before converting to a list and popping it in cache.
My database is normalised so data is spread over several tables. The base entity is "User", a User may or may not be a "Subscriber" and a Subscriber can be one of 3 types "Contributor", "Member" or "Administrator"
At present the whole fetch is not very elegant due to my lack of knowledge in EF, Linq et al.
public static User Get(Guid userId)
{
Guard.ThrowIfDefault(userId, "userId");
var r = new CrudRepo<User>(Local.Items.Uow.Context);
var u = r.FindBy(x => x.UserId == userId)
.Include("BookmarkedDeals")
.Include("BookmarkedStores")
.SingleOrDefault();
if (u.IsNotNull() && u.IsActive)
{
if (u.IsAdmin)
{
u.GetAdministrator();
}
else if (u.IsContributor)
{
u.GetContributor();
}
else if (u.IsMember)
{
u.GetMember();
}
else
{
string.Format("Case {0} not implemented", u.UserRoleId)
.Throw<NotImplementedException>();
}
}
return u;
}
Each of the 'Get' methods gets a Subscriber entity plus the relevant Include() entities for the role type.
I'm pretty sure it can be done a whole lot more elegently than this but struggling with the initial thought process.
Anyone help?
UPDATED with example of one of the Get methods
public static void GetMember(this User user)
{
Guard.ThrowIfNull(user, "user");
var r = new ReadRepo<Subscriber>(Local.Items.Uow.Context);
user.Subscriber = r.FindBy(x => x.UserId == user.UserId)
.Include("Kudos")
.Include("Member.DrawEntries")
.Include("Member.FavouriteCategories")
.Include("Member.FavouriteStores")
.Single();
}
If your "User" entity is connected to your other entities you can use the Load method of the connected entity collection to get the related entities. For example if your "User" entity has a property "Subscriber" you could call u.Subscriber.Load() to get the related entity. Here is the related MSDN article
var u = r.FindBy(x => x.UserId == userId)
.Include("BookmarkedDeals")
.Include("BookmarkedStores")
.SingleOrDefault();
if(someCondition)
{
u = u.Include("something");
}
Don't have a place to test this, but have you tried that?

Categories

Resources