Linq To Entities - how to filter on child entities - linq

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()
});

Related

Linq to sql, query many-to-many with count of other

I have a many-to-many relation and I'm trying to create the query which will fetch me the left side and a property which counts the number of records which are refferecend by it.
following is my query
var dbSet = await (from user in _dbContext.Users
where (from courseUsers in _dbContext.CourseUsers select courseUsers.UserId).Contains(user.Id)
select new
{
Name = user.Name,
Id = user.Id,
CourseUsersCount = _dbContext.CourseUsers.Where(item => item.UserId == user.Id).Count()
})
.ToListAsync();
What I don't like is how CourseUsersCount is computed. I would also like to include the total count property and the way I would do it is to add another property on the select which would just do a count over the _dbContext.CourseUsers and after that do another in-memory transformation.
I the end I would like a result with this structure to be created
{
count: 1000,
data: [{
Id: 1,
Name: "c",
CourseUsersCount: 2
}]
}
and I want to know how can I do this directly using linq-to-sql.
As mentioned in comments you have to use GroupBy for such calculation:
var query
from user in _dbContext.Users
from courseUsers in user.Courses
group user by new { user.Id, user.Name } into g
select new
{
g.Key.Id,
g.Key.Name,
CourseUsersCount = g.Count()
};
You need a Group By to merge all the CourseUsers into a single set, followed by a Join to attach the Users to it.
from course in _dbContext.CourseUsers // outer sequence
group course by course.UserId into courseGrp
join user in _dbContext.Users //inner sequence
on courseGrp.Key equals user.UserId// key selector
select new { // result selector
CourseUsersCount = courseGrp.Count(),
user.Name,
user.Id
};

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.

Entity Framework code first - Many to Many - Include conditional

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.

showing multiple rows from database in datagridview using entity framework

I am trying to show multiple records from database in datagridview but I'm having only a single record all the time.
2 tables are involved in this query, from 1st table I acquire all the id's those fulfill the condition and from 2nd table I am getting the user information.
1st table is tblUsers_Roles and 2nd is tblUsers.
These tables have primary/foriegn key relationship.
Here is my code:
IEnumerable<tblUsers_Role> id = db.tblUsers_Role.Where(a => a.User_Role == selectRole);
foreach (var user in id)
{
var userinfo = from b in db.tblUsers
where b.User_Id == user.User_Id
select new { b.First_Name, b.Last_Name, b.Status, b.Authenticated };
dgvResults.DataSource = userinfo.ToList();
dgvResults.Show();
}
You are assigning the grid in the loop. That is not going to work. Maybe something like this will work:
var userinfo =(from ur in db.tblUsers_Role
join u in db.tblUsers
on ur.User_Id equals u.User_Id
where ur.User_Role == selectRole
select new
{
u.First_Name,
u.Last_Name,
u.Status,
u.Authenticated
}).ToList();
dgvResults.DataSource = userinfo;
dgvResults.Show();
Or a alteretive would be like this:
var roles=db.tblUsers_Role
.Where(a => a.User_Role == selectRole)
.Select (a =>a.User_Id).ToList();
var userinfo=
(
from u in db.tblUsers
where roles.Contains(u.User_Id)
select new
{
u.First_Name,
u.Last_Name,
u.Status,
u.Authenticated
}
).ToList();
dgvResults.DataSource = userinfo;
dgvResults.Show();
Not as nice as the first one. But maybe you understand the concept.

Linq: How to load a second table directly?

I have 2 tables here:
Auction and Article.
1 Auction has 1 Article.
1 Aricle has x Auctions.
Now I load a list of auctions:
using (APlattformDatabaseDataContext dc = new APlattformDatabaseDataContext())
{
List<Auktion> auctionlist = (from a in dc.Auktion
where a.KäuferID == null
select a).ToList();
return auctionlist;
}
But when I want to "txtBla.Text = auction[0].Article.Text;" it isn't loaded.
The question isn't why (it is logic that it isn't loaded allready and can't be loaded because the DC is closed), but how can I solve this without letting the DC open?
You can do the following:
DataLoadOptions options = new DataLoadOptions();
options.LoadWith<Auktion>(a => a.Article);
dc.LoadOptions = options;
If you want to eager load the associations like that you should use the DataContext.LoadOptions property like so...
using (APlattformDatabaseDataContext dc = new APlattformDatabaseDataContext())
{
var dlo = new DataLoadOptions();
options.LoadWith<Auktion>(o => o.Article);
dc.LoadOptions = dlo;
List<Auktion> auctionlist = (from a in dc.Auktion
where a.KäuferID == null
select a).ToList();
return auctionlist;
}
That way your articles will be loaded when your Auktion objects are retrieved from the database.

Resources