Linq query with multiple objects in parent child relationship - c#

I have a schema like this
Package -> Lists -> Users
All 'one to many' down the line...
So I want to run a query where I get all packages that a userID matches in users.
var pck = (from pk in context.Package
where pk.Lists[here's my problem]
I would assume the navigation properties here would be: pk.Lists. *Users.UserId* == MyUserId, however I'm not seeing the navigation properties at the Lists level.
I haven't gotten to more complex EF queries like this yet. I've looked around the web but haven't found anything to make it click. I turn to you stack. Somebody help me see the light!
EDIT: Thanks again stack, I will do my best to pay it forward!
Also, all of these answers enlightened me to the power of ef4!

I assume that a package contains multiple lists, and a list contains multiple users? You could try:
var pck = content.Package
// Outdented just for Stack Overflow's width
.Where(pk => pk.Lists.Any(list => list.Any(u => u.UserId == myUserId)));
Or use a cross-join:
var pck = from pk in content.Package
from list in pk.Lists
from user in list.Users
where user.UserId == myUserId
select ...; // Select whatever you're interested in

context.Packages.Where(p => p.Lists.Any(l => l.Users.Contains(MyUserId)))
or, if your user is something other then just a user id,
context.Packages.Where(p => p.Lists.Any(l => l.Users.Any(u => u.Id == MyUserId)))

var packages =
context.Package.Where(p =>
p.Lists.Any(l =>
l.Users.Any(u => u.UserId == MyUserId
)
);

If the links are non-nullable and direction is package has many lists and list has many users, then the query is pretty easy.
var pck = from user in context.Users
where user.UserId == userId
select user.List.Package;

try this:
pk.Lists.Any(l => l.Users.Any(u => u.UserId == MyUserId))

Related

How to select top result for a given ID then join that into another table EF Core LINQ

For the life of me I am unable to google my way out of this one.
I have 2 tables within a database
1. Computers
2. UserLogins
Essentially, I'm trying to get the latest login entry from the "UserLogins" table, and join it with the corresponding entry in the "Computers" table.
This sounds simple enough, but I haven't sat through enough LINQ/EF Core courses yet to figure out how to do this correctly it seems.
Here is some SQL that I know functions how I expect it to:
SELECT * FROM ComputerInfo
LEFT JOIN (
SELECT LoginID, UserID, l.ComputerName, IpAddress, l.LoginTime FROM UserLogins as l
INNER JOIN (
SELECT ComputerName, MAX(LoginTime) as LoginTime
FROM UserLogins
GROUP BY ComputerName) as max on max.ComputerName = l.ComputerName and max.LoginTime = l.LoginTime
) as toplogin on toplogin.ComputerName = ComputerInfo.ComputerName
For reference, I am going to be implementing this in my Controller.cs class, and I am using :
EF Core (3.1.2)
ASP.NET Core (3.1)
I do have a couple queries I was experimenting with that return the results, but I can't join them without errors:
var computerQuery = _context.ComputerInfo
.OrderBy(on => on.ComputerName)
var userQuery = _context.UserLogins
.Select(p => p.ComputerName)
.Distinct()
.Select(id => _context.UserLogins
.OrderByDescending(p => p.LoginTime)
.FirstOrDefault(p => p.ComputerName == id))
.ToListAsync();
So I kind of found a shotty way to get this done I think. Not sure if it is correct but here is what I came up with:
I Created a new class called "ComputerInfoFull" which basically was just "ComputerInfo" && "UserLogins" combined, and used this for the linq query:
var initial = from computerInfo in _context.ComputerInfo
from userInfo in _context.UserLogins
.Where(o => o.ComputerName == computerInfo.ComputerName)
.OrderByDescending(o => o.LoginTime).Take(1)
select new ComputerInfoFull(computerInfo, userInfo);
I'm very sure there is a cleaner Lambda way of writing this, but I can't figure out how to make it work right. Too much stuff going on for my tiny brain to handle lol. If anyone has any ideas on how I can make this cleaner please let me know so I can learn.

Filtering Related Entites with Entity Framework

According to this StackOverflow answer:
Linq to Entities - how to filter on child entities
you should be able to filter down the list of related entities in Entity Framework by utilizing a projection, like I've done here:
Company company = _context.Company
.Where(g => g.CompanyId == id)
.Select(comp => new
{
group = comp,
operators = comp.Operator,
formFamilies = comp.FormFamily.Where(ff => ff.IsActive ?? false)
}).AsEnumerable().Select(i => i.group).FirstOrDefault();
To give a quick overview of what I'm trying to obtain here, I'm trying to get a list of all of the active form families associated with this company object, however, whenever I restrict the results in any way, the result set is empty.
If the line were formFamilies = comp.FormFamily then it returns two results, one active one inactive
If the line is formFamilies = comp.FormFamily.Where(ff => true) then it returns nothing
If the line is formFamilies = comp.FormFamily.OrderBy(ff => ff.FormFamilyId) then it returns nothing.
Any sort of modification that I do to comp.FormFamily means the result set returns nothing, I've dug through the deepest sections of SA to try to find a solution, and tried every solution I've found, but nothing seems to cause this list to return anything.
Assuming that Company and FormFamily entities has one to many relationship I would suggest to use a join statement.Something like this should give you what you are looking for.
var company = from c in _context.Company
join f in _context.FormFamily
on c.Id equals f.CompanyId
where c.Id == id
select new Company()
{
Id = c.Id,
operators = c.Operator.ToList(),
formFamilies = c.FormFamily.Where(x=>x.IsActive ==
false).ToList()
} .FirstOrDefault();
Hope this helps.
I didn't quite understand what is your query is supposed to do. But it seems to me that you cannot just call Select method on another Select result method.
Anyway, you could simply use Include methods instead of projecting.
var company = _context.Company
.Where(c => c.Id == id)
.Include(c => c.FormFamily).Where(ff => ff.IsActive ?? false)
.ToList();
Did not test it. To prove it works or not be sure put an entity model in the question. Then I may produce more accurate answer.

How to do join with objects in Entity Framework

So I am converting a old project with ordinary SQL queries to a ORM using the Entity Framework. So I have created database model like this:
So I had this old query which I want to translate to a linq expression
SELECT UGLINK.USERNAME
FROM GMLINK
INNER JOIN UGLINK
ON GMLINK.GROUPID = UGLINK.GROUPID
WHERE (((GMLINK.MODULEID)=%ID%))
And the problem I have is that I can't figure out how to do a join query using the objects.
Instead I have to go though the properties like this (which seems to be working):
// So this is one of the module objects that is located in a listView in the GUI
Module m = ModuleList.selectedItem as Module;
/* Now I want to fetch all the User objects that,
* via a group, is connected to a certain module */
var query = context.gmLink
.Join(context.ugLink,
gmlink => gmlink.GroupId,
uglink => uglink.GroupId,
(gmlink, uglink) => new { gmLink = gmlink, ugLink = uglink })
.Where(gmlink => gmlink.gmLink.ModuleId == m.ModuleId)
.Select(x => x.ugLink.User);
So as I said this works, but as you see I kind of have to connect the modules via the link tables properties .GroupId and .ModuleId and so on. Instead I would like to go through the objects created by EF.
I wanted to write a question a bit like this, but can't figure out how to do it, is it at all possible?
var query = context.User
.Select(u => u.ugLink
.Select(uglink => uglink.Group.gmLink
.Where(gmLink => gmLink.Module == m)));
This should be working:
var query = context.gmLink
.Where(gmlink => gmlink.ModuleId == m.ModuleId)
.SelectMany(gmlink => gmlink.Group.ugLink)
.Select(uglink => uglink.User);
It's impossible to filter gmLinks using .Where(gmlink => gmlink.Module == m) in EF, so this comparison needs to be done using identifiers. Another option is .Where(gmlink => gmlink.Module.ModuleId == m.ModuleId)
If you have lazy loading enabled, you do not need to apply specific join notation (you can access the navigation properties directly) - but the queries that are ran against SQL are inefficient (generally the results are returned in a number of different select statements).
My preference is to disable lazy loading on the context, and use .Include() notation to join tables together manually, resulting in generally more efficient queries. .Include() is used to explicitly join entities in Entity Framework.
Join() is misleading, and not appropriate for joining tables in EF.
So, to replicate this statement:
SELECT UGLINK.USERNAME
FROM GMLINK
INNER JOIN UGLINK
ON GMLINK.GROUPID = UGLINK.GROUPID
WHERE (((GMLINK.MODULEID)=%ID%))
You would use the following:
var query = context.gmLink
.Include(x => x.Group.gmLink)
.Where(x => x.ModuleId == myIdVariable)
.Select(x => new {
UserName = x.Group.ugLink.UserName
});
Assuming that your navigation properties are correctly set up. I have not tested this, so I'm not 100% on the syntax.
You should really run SQL profiler while you write and run LINQ to Entity queries against your database, so you can understand what's actually being generated and run against your database. A lot of the time, an EF query may be functioning correctly, but you may experience performance issues when deployed to a production system.
This whitepaper might help you out.
I haven't tested it, but something like this:
var users = context.User
.Where(x => x.ugLink
.Any(y => context.gmLink
.Where(z => z.ModuleId == m)
.Select(z => z.GroupId)
.Contains(y.GroupId)
)
)
.ToList();

Many to Many select in LINQ to Entities

I have two tables Service and Provider. Between them is a joining table ServiceProvider which has only 2 fields - one for each of the two PKs. When added to the edmx the joining many-to-many table is abstracted away and can't be seen (as expected).
This is all fine except when I want to get Providers based on a given service. From this question:
it looks like the answer would be simply:
var query = from p in entities.Providers
from s in entities.Services
where s.Id == 15
select p;
but this returns ALL providers. What am I doing wrong here?
var query = entities.Providers.FirstOrDefault(p => p.Id == 15).Services.ToList();
Isn't it as simple as
var matchingProviders = entities.Services.Single(s=>s.Id==15).Providers;
Try this:
var res = from s in entities.Services
where s.Id == 15
select s.Provider;
EDIT
Corrected and tested the query on real-life model and data. It works now.
You may try using join, like so:
entity.Providers.Join(entity.Services, c => c.ID, p => p.ID,(c, p) => new { Providers= c, Services= p })

LINQ to SQL - Restrict Relationship by Condition (One to Many) (1 - M)

OK, first off, I'm brand new to LINQ2SQL so excuse my ignorance.
I've done some searches, but after 1 hour, I decided to come here.
Here goes:
I'm trying to do something that I think is very simple. It could just be that I don't understand. I'm using LINQ 2 SQL, via the VS designer. I have 2 tables: Clients and Categories. A client can have multiple categories.
I have a very simple query to find clients:
Client c = db.Clients.SingleOrDefault(client => client.ID == id);
What I want to do is modify this so that the collection: c.Categories, will only contain a subset of that client's categories.
This is what I've tried:
Client c = db.Categories.Where(cat => cat.IsActive == true).Select(cat.Clients).SingleOrDefault(client => client.ID == id);
I get an error reporting that more than one client is being returned.
Am I missing something? Is this not something that LINQ is designed to do?
Am I supposed to use the first query then do another query specifically on Categories when I need that list??
c.Categories.Categories.Where(cat => cat.IsActive == true)
Thank you in advance.
SingleOrDefault() is for when there's ONLY one result. It seems like there's more than one record that has that id. Try using FirstOrDefault() instead.
Client c = db.Categories.Where(cat => cat.Name == "Name").Select(cat.Clients).FirstOrDefault(client => client.ID == id);
The problem is that the Categories.Where part in combination with the Select, is returning a collection of collections. What you may need to do is either use SelectMany, or use Single (or SingleOrDefault, or First or FirstOrDefault) instead of Where.
Examples:
Client c = db.Categories.Where(cat => cat.IsActive)
.SelectMany(cat.Clients)
.SingleOrDefault(client => client.ID == id);
or
Client c = db.Categories.Single(cat => cat.IsActive)
.Clients
.SingleOrDefault(client => client.ID == id);

Categories