Entity Framework code first - Many to Many - Include conditional - linq

I have two Entities Store and Catalog, having many to many relationship using fluent Api. I want to get a Store by id with all the catalogs having status equals to "Published".
Below I try to write the following query but not getting the expected results.
var store = context.Stores.Include("Catalogs").Where(s => s.StoreID == id && s.Catalogs.Any(c => c.Status == "Published")).SingleOrDefault();

What you're asking for there is "give me the store with this ID, but only if it has a published catalog" (the "Any" call).
The easiest way to only get published catalogs would be to project them into an anonymous type:
var result = (from s in context.Stores
where s.StoreID == id
select new
{
Store = s,
Catalogs = s.Catalogs.Where(c => c.Status == "Published")
}).SingleOrDefault();
... or, in the fluent interface:
var result = context.Stores.Where(st => st.StoreID == id)
.Select(s => new
{
Store = s,
Catalogs = s.Catalogs.Where(c => c.Status == "Published"),
}).SingleOrDefault();
So result.Catalogs holds all the published catalogs that apply to result.Store.

Related

LINQ - optional/nullable where conditions

I do have the following LINQ query, selecting movies from my database that either contain the given search string or have one or more of the tags that I give to the function.
The problem is, that if the search string or the tags parameter are empty/null, I get no movies. The desired action however, is to get all the movies, so, if one parameter is null or empty, I don't want to apply it.
How can I do that?
public IEnumerable<Group<Genre, Movie>> GetMoviesGrouped(string search, List<Tag> tags)
{
var movies = from movie in Movies
where ( movie.Name.Contains(search)) && movie.Tags.Any(mt => tags.Any(t => t.ID == mt.ID))
group movie by movie.genre into g
select new Group<Genre, Movie> { Key = g.Key, Values = g };
....
}
Just for readability, I like to do it step by step
var movies = Movies;
if (!string.IsNullOrEmpty(search))
movies = movies.Where(m => m.Name.Contains(search));
if (tags != null && tags.Any()) {
var tagIds = tags.Select(m => m.ID);
movies = movies.Where(m => m.Tags.Any(x => tagIds.Contains(x.ID));
}
var result = from movie in movies
group ...
You can do something like this to skip the check if nothing is provided:
where (string.IsNullOrEmpty(search) || movie.Name.Contains(search)) &&
(!tags.Any() || movie.Tags.Any(mt => tags.Any(t => t.ID == mt.ID)))

(Linq & EF) Where MyType = (int)Enum does not work

I have a database in Azure that I created from scratch (not using code first in EF) and made a column in one of my tables to represent an Enum from my C# classes.
The enum is
public enum ItemType
{
Build,
Stock,
Shell,
Parts,
[Display(Name="All Vehicles")]
AllTypesOfVehicles
}
I am trying to select certain records in an EF Linq Query by using this enum in the where clause. I simply cast the enum to its underlying int32 data type but it does not retrieve me the results I want.
var results = context.Items.Include(P => P.Manufacturer)
.Include(P => P.Category).Include(P => P.VehicleMake)
.Include(P => P.VehicleModel).Include(P => P.VehicleYear);
//Fetches specific type of item
if (TypeOfPart != StaticData.ItemType.AllTypesOfVehicles)
{
results.Where(P => P.Type == (int)TypeOfPart); //This does not work
}
else //fetches all vehicle types
{
results.Where(P => P.Type == 0 | P.Type == 1 | P.Type == 2);
}
Is this expected in EF? I know EF supports Enum from a code first approach but I don't see why this would cause a problem. I went into my Azure portal and wrote a manual query to see if I would get the results that I wanted and I did.
SELECT * FROM Items WHERE Type = 2
This yielded me only results that were of enum type "Shell". Even hardcoding the number 2 into the where clause in my EF query does not get me the results I want. I rechecked my code to make sure I wasn't overriding that where clause anywhere else and everything seems clean. Just to be sure I even called .ToList() right after the where clause but still got bad results.
I'm not quite sure what I'm missing here???
============Edit after first answer=======================
var results = context.Items.Include(P => P.Manufacturer)
.Include(P => P.Category).Include(P => P.VehicleMake)
.Include(P => P.VehicleModel).Include(P => P.VehicleYear);
//Fetches specific type of item
if (TypeOfPart != StaticData.ItemType.AllTypesOfVehicles)
{
var part = (int)TypeOfPart;
results.Where(P => P.Type == part);
List<Item> t = results.ToList();
}
else //fetches all vehicle types
{
results.Where(P => P.Type == 0 | P.Type == 1 | P.Type == 2);
}
You have to assign results.Where() back to results to get it work and try performing the cast outside EF query context:
//Fetches specific type of item
if (TypeOfPart != StaticData.ItemType.AllTypesOfVehicles)
{
var part = (int)TypeOfPart;
results = results.Where(P => P.Type == part ); //This does not work
}
else //fetches all vehicle types
{
results = results.Where(P => P.Type == 0 | P.Type == 1 | P.Type == 2);
}
And btw, should you just skip the P.Type related Where when you're trying to fetch all types of vehicles? It would mean you don't need else.
//Fetches specific type of item
if (TypeOfPart != StaticData.ItemType.AllTypesOfVehicles)
{
var part = (int)TypeOfPart;
results.Where(P => P.Type == part ); //This does not work
}

RIA Servcies Filter Out Included Entities

Below returns all of the people in a building and all of their computers, and this works.
I want to change this to only include the Computers where Active == 1 and only the ActivityLogs where ActivityTypeId == 5. But if they don’t have either I still want the person returned.
public IQueryable<Person> GetPeople(int BuildingId)
{
return this.ObjectContext.People
.Include("Computers")
.Include("ActivityLog")
.Where(p => p.buildingId == BuildingId && !p.migrated)
.OrderBy(p => p.name);
}
Unfortunately this is not possible using Include syntax. As an alternative, you can select each entity individually like so:
var queryWithAllData = this.ObjectContext
.People
.Select(p => new
{
Person = p,
Computers = p.Computers.Where(c => c.Active == 1),
ActivityLogs = p.ActivityLog.Where(a => a.ActivityTypeId == 5)
});
// Do what you need with the records now that you have all the data.

linq with Include and many-to-many

I have rather specific requirement for my Linq statement. I've looked through dozens of examples, but could't find satisfying answer.
I have a collection of courses, which holds collection of groups, which holds collection of members. My goal is to create linq statement for IQuerable which returns a collection of courses that user belongs to, and includes collection of groups within each course that user belongs to if there is any.
I did manage to build a linq statement which returns what I wanted but filters out all of the courses that don't have any groups that user belongs to inside them:
var courses2 = (from c in _set
from s in c.Students
where (s.UserName == userName)
from g in c.Groups
from m in g.Members
where (m.UserName == userName)
select c)
.Include(c => c.Groups)
.OrderBy(c => c.Title)
How could I change it to include those as well?
Assuming your student entity has a nav property to entity Class for class membership, and Class entities have a nav property Groups to the Group entity, which in turn have a nav property to Students:
// get the students to query against
var students = db.Students.Where( s => s.UserName == userName );
// get classes per group
var classesViaGroups =
students.SelectMany( s => s.Groups )
.SelectMany( g =>
g.Classes.Select( c =>
new { GroupName = g.Name, ClassObj = g.Class } ) );
// get classes by the student entities alone
var classesViaStudents =
students.SelectMany( s => s.Classes )
.Select( c => new { GroupName = null, ClassObj = c } );
You can then do what you will with classesViaGroups and classesViaStudents - combine them, build a lookup or dictionary by group or class, etc.

Linq To Entities - how to filter on child entities

I have entities Group and User.
the Group entity has Users property which is a list of Users.
User has a property named IsEnabled.
I want to write a linq query that returns a list of Groups, which only consists of Users whose IsEnabled is true.
so for example, for data like below
AllGroups
Group A
User 1 (IsEnabled = true)
User 2 (IsEnabled = true)
User 3 (IsEnabled = false)
Group B
User 4 (IsEnabled = true)
User 5 (IsEnabled = false)
User 6 (IsEnabled = false)
I want to get
FilteredGroups
Group A
User 1 (IsEnabled = true)
User 2 (IsEnabled = true)
Group B
User 4 (IsEnabled = true)
I tried the following query, but Visual Studio tells me that
[Property or indexer 'Users' cannot be assigned to -- it is read only]
FilteredGroups = AllGroups.Select(g => new Group()
{
ID = g.ID,
Name = g.Name,
...
Users = g.Users.Where(u => u.IsInactive == false)
});
thank you for your help!
There is no "nice" way of doing this, but you could try this - project both, Group and filtered Users onto an anonymous object, and then Select just the Groups:
var resultObjectList = AllGroups.
Select(g => new
{
GroupItem = g,
UserItems = g.Users.Where(u => !u.IsInactive)
}).ToList();
FilteredGroups = resultObjectList.Select(i => i.GroupItem).ToList();
This isn't a documented feature and has to do with the way EF constructs SQL queries - in this case it should filter out the child collection, so your FilteredGroups list will only contain active users.
If this works, you can try merging the code:
FilteredGroups = AllGroups.
Select(g => new
{
GroupItem = g,
UserItems = g.Users.Where(u => !u.IsInactive)
}).
Select(r => r.GroupItem).
ToList();
(This is untested and the outcome depends on how EF will process the second Select, so it would be nice if you let us know which method works after you've tried it).
I managed to do this by turning the query upside down:
var users = (from user in Users.Include("Group")
where user.IsEnabled
select user).ToList().AsQueryable()
from (user in users
select user.Group).Distinct()
By using the ToList() you force a roundtrip to the database which is required because otherwise the deferred execution comes in the way. The second query only re-orders the retrieved data.
Note: You might not be able to udpate your entities afterwards!
try something like this and you'll still have your entities:
FilteredGroups = AllGroups.Select(g => new
{
Group = g,
Users = g.Users.Where(u => u.IsInactive == false)
}).AsEnumerable().Select(i => i.Group);
That way you should still be able to use Group.Users
If you want to retain your entity structure, try this:
var userGroups = context.Users.Where(u => !u.IsInactive).GroupBy(u => u.Group);
foreach (var userGroup in userGroups)
{
// Do group stuff, e.g.:
foreach (var user in userGroup)
{
}
}
And you certainly can modify your entities!
Use inner linq query
var FilteredGroups = (from g in AllGroups
select new Group()
{
ID = g.ID,
Name = g.Name,
...
Users = (from user in g.Users
where user.IsInactive == false
select user).ToList()
});

Resources