Concatenating lists inside a LINQ query - linq

My data structure is set up this way
A user takes a number of modules
A module contains a number of courses
Here's how the relationship looks like:
How do I get a list of courses the user takes?
The query I have now is:
var courses = (from ClassEnrollment enrolment in entities.ClassEnrollment
where enrolment.UserID == UserID
join Module module in entities.Module
on enrolment.ModuleID equals module.ID
select module.Course
).ToList();
However, this doesn't result in a list of courses, but rather a list of list of courses.
How can I flatten this query to a list of distinct courses?

According to your data structure screenshot, you have a one-to-many relationship between the ClassEnrollment and Module, as well as navigational property called Module. You also have a many-to-many relationship between Module and Course, but the navigational property should be called Courses. Given your code, you want something like this:
var courses = entities.
ClassEnrollment.
Where(e => e.UserID == UserID).
SelectMany(e => e.Module.Courses).
ToList();
Your question, however, mentions a user: A user takes a number of modules, How do I get a list of courses the user takes?. I don't see any User entity anywhere else, though, so it would be nice if you could clarify. Are you using LINQ-to-SQL, btw?

Something like this:
var courses = from ClassEnrollment enrolment in entities.ClassEnrollment
from module in entities.Module
where enrolment.ModuleID equals module.ID && enrolment.UserID equals UserID
select module.Course

Use SelectMany.

You can use
courses.SelectMany(c => c);
In your query you don't need explicitly specify the type for the range variables
Or you can join course to the query
var query = from enrolment in entities.ClassEnrollment
join module in entities.Module on enrolment.ModuleID equals module.ID
join course in entities.Course on module.CourseID equals course.ID
where enrolment.UserID == UserID
select course;
var course = query.ToList();

Related

Laravel inject SQL Query into Model Collection

I have a Laravel Controller method with the following code:
$listings = LikedListing::where('user_id', '=', $user->id)
->with('Listing.Photos')
->get();
This should return a collection of LikedListing records with Photos attached to each likedlisting record.
I have this SQL Query I need to inject into each record as well:
select u.id, l.address, u.first_name, u.last_name, ll.score
from listings l
left join liked_listings ll on ll.listing_id = l.id
left join users u on u.id = ll.user_id
where u.id in (
select secondary_user
from user_relationships
where primary_user = $primaryUser
)
and ll.listing_id = $listingId
Where $primaryUser = $user->id And $listingId is equal to the listingid inside each record in the collection.
I have absolutely no idea how to do this.
Maybe theres a model way of performing this? UserRelationship model has a primary_user column, which connects to a $user->id, and there is a secondary_user column, which acts like a follower userid, which is what we need in the final result (a list of all related users per listing)`
Can someone who has much far superior knowledge with Laravel please assist
My goal is to have the current collection of listing records with associated photos as well as following users (secondary_user) from the user_relationship table related to the primary_user (the logged in user) who have a record using user_id with the secondary_user value for that listing in the likedlisting table (obv assoicated with the listing_id). I already provided a raw sql query if thats the only option.
So in simple terms all related users who have liked a listing that the primary user has liked as well should be added to each listing record

Joining multiple tables on an already joined table using Eloquent

I have the following which brings back all users in a group along with their posts.
$group = Group::where('id', $id)->with('users.posts')->firstOrFail();
However, what I need is an additional join on the users to bring back additional (hasMany) information.
What I want is something like this (although this doesn't work)
$group = Group::where('id', $id)->with('users.posts,houses')->firstOrFail();
The sql would look something like
SELECT * FROM groups
JOIN group_users ON groups.id = group_users.group_id
JOIN users ON users.id = group_users.user_id
JOIN posts ON posts.user_id = users.id
JOIN house_users ON house_users.user_id = users.id
JOIN houses ON houses.id = house_users.house_id
WHERE groups.id = 123
If you pass a single argument to with(), it will look for a relationship with a matching name. Using a single string with a comma won't work as it won't parse and respect it. Since you're trying to use multiple relationships, this needs to be multiple signature, which there are a couple ways to accomplish.
First, array syntax:
->with(["users.posts", "houses"])
Second, multiple arguments:
->with("users.posts", "houses")
Either method will specify that you want multiple relationships loaded to your initial query; preference is given to whichever you find easier to read.

LINQ Noob, how do I write this common query?

I'm a long time dev, but still kind of new to LINQ. I'm OK when dealing with one set of object, but things get tougher when I need to pull from several sources, and I could use some guidance in getting what I need here.
I have three tables in my database, two related tables and one that holds the PK/FK to tie them together. So something like:
Users
UserID
UserName
Surveys
SurveyID
SurveyName
UserSurveys
UserID
SurveyID
I am using EF and so all of this data has been pulled into Objects.
So... what I want to do is return a List of all Surveys that are associated with a given User. So something like (pseudo-code):
// currentUserID = the UserID I need to get matching Surveys for
var surveys = from Survey where (s => s.SurveyID == UserSurvey.SurveyID && UserSurvey.UserID == currentUserID);
I assume I need to make a sub-query and use a Contains() or something like that, but I keep tripping over myself. Help?
Should be something like this:
from us in UserSurveys
where us.UserId == currentUserID
join s in Surveys on us.SurveyID equals s.SurveyID
select s
If this is EF you should be able to do someUser.Surveys.
Assuming your database and entity model has all of your FK references you should be able to do something like this....
// currentUserID = the UserID I need to get matching Surveys for
var surveys = from s in Survey
where s.User.UserID == currentUserID
select s;

EF 4 LINQ Expression load items based on related entity

Here is the expression
x => x.stf_Category.CategoryID == categoryId
x refers to an Product Entity that contains a Category. I am trying to load all Products that match given categoryId.
In the db the Product table contains a Foreign Key reference to Category (via CategoryId).
Question: I think I am doing it wrong. Is there something else one has to do in EF4 to create a LINQ expression of this type?
Are there any good examples of EF4 Linq expressions out there? Specifically something that queries on the basis of related entities such as my problem ?
Thanks !
You're looking for the Include method.
var query = db.Products.Include("Categories");
This is commonly referred to as eager loading.
Entity Framework will 'infer' the JOIN constraint based on the mapping you have specified.
The "magic string" needs to match the Entity Set name on your EDMX.
Check out this post for more info.
EDIT
I'm a little confused as to whether you want the Products and Categories, or just the Products which have a specific Category ID.
If the latter, this is the way to go:
var query = from p in db.products
join c in db.categories
on p.CategoryId equals c.CategoryId
where c.CategoryId == someCategoryId
select p;
Keep in mind though, the above query is exactly the same result as your original query.
If p is a product, then p.Categories will look at the Navigational Property of your Product entity on the EDMX, in which case it will be your Category FK.
As long as you setup your Navigational properties right, p.Categories is fine.
If you are using EF4 and the association between Category and Product classes has been picked up and defined in your Model, then all products with a specific categoryID can be selected as simple as:
x => x.CategoryID == categoryID
You don't need to join nor an eager loading for that.

Can I force the auto-generated Linq-to-SQL classes to use an OUTER JOIN?

Let's say I have an Order table which has a FirstSalesPersonId field and a SecondSalesPersonId field. Both of these are foreign keys that reference the SalesPerson table. For any given order, either one or two salespersons may be credited with the order. In other words, FirstSalesPersonId can never be NULL, but SecondSalesPersonId can be NULL.
When I drop my Order and SalesPerson tables onto the "Linq to SQL Classes" design surface, the class builder spots the two FK relationships from the Order table to the SalesPerson table, and so the generated Order class has a SalesPerson field and a SalesPerson1 field (which I can rename to SalesPerson1 and SalesPerson2 to avoid confusion).
Because I always want to have the salesperson data available whenever I process an order, I am using DataLoadOptions.LoadWith to specify that the two salesperson fields are populated when the order instance is populated, as follows:
dataLoadOptions.LoadWith<Order>(o => o.SalesPerson1);
dataLoadOptions.LoadWith<Order>(o => o.SalesPerson2);
The problem I'm having is that Linq to SQL is using something like the following SQL to load an order:
SELECT ...
FROM Order O
INNER JOIN SalesPerson SP1 ON SP1.salesPersonId = O.firstSalesPersonId
INNER JOIN SalesPerson SP2 ON SP2.salesPersonId = O.secondSalesPersonId
This would make sense if there were always two salesperson records, but because there is sometimes no second salesperson (secondSalesPersonId is NULL), the INNER JOIN causes the query to return no records in that case.
What I effectively want here is to change the second INNER JOIN into a LEFT OUTER JOIN. Is there a way to do that through the UI for the class generator? If not, how else can I achieve this?
(Note that because I'm using the generated classes almost exclusively, I'd rather not have something tacked on the side for this one case if I can avoid it).
Edit: per my comment reply, the SecondSalesPersonId field is nullable (in the DB, and in the generated classes).
The default behaviour actually is a LEFT JOIN, assuming you've set up the model correctly.
Here's a slightly anonymized example that I just tested on one of my own databases:
class Program
{
static void Main(string[] args)
{
using (TestDataContext context = new TestDataContext())
{
DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<Place>(p => p.Address);
context.LoadOptions = dlo;
var places = context.Places.Where(p => p.ID >= 100 && p.ID <= 200);
foreach (var place in places)
{
Console.WriteLine(p.ID, p.AddressID);
}
}
}
}
This is just a simple test that prints out a list of places and their address IDs. Here is the query text that appears in the profiler:
SELECT [t0].[ID], [t0].[Name], [t0].[AddressID], ...
FROM [dbo].[Places] AS [t0]
LEFT OUTER JOIN (
SELECT 1 AS [test], [t1].[AddressID],
[t1].[StreetLine1], [t1].[StreetLine2],
[t1].[City], [t1].[Region], [t1].[Country], [t1].[PostalCode]
FROM [dbo].[Addresses] AS [t1]
) AS [t2] ON [t2].[AddressID] = [t0].[AddressID]
WHERE ([t0].[PlaceID] >= #p0) AND ([t0].[PlaceID] <= #p1)
This isn't exactly a very pretty query (your guess is as good as mine as to what that 1 as [test] is all about), but it's definitively a LEFT JOIN and doesn't exhibit the problem you seem to be having. And this is just using the generated classes, I haven't made any changes.
Note that I also tested this on a dual relationship (i.e. a single Place having two Address references, one nullable, one not), and I get the exact same results. The first (non-nullable) gets turned into an INNER JOIN, and the second gets turned into a LEFT JOIN.
It has to be something in your model, like changing the nullability of the second reference. I know you say it's configured as nullable, but maybe you need to double-check? If it's definitely nullable then I suggest you post your full schema and DBML so somebody can try to reproduce the behaviour that you're seeing.
If you make the secondSalesPersonId field in the database table nullable, LINQ-to-SQL should properly construct the Association object so that the resulting SQL statement will do the LEFT OUTER JOIN.
UPDATE:
Since the field is nullable, your problem may be in explicitly declaring dataLoadOptions.LoadWith<>(). I'm running a similar situation in my current project where I have an Order, but the order goes through multiple stages. Each stage corresponds to a separate table with data related to that stage. I simply retrieve the Order, and the appropriate data follows along, if it exists. I don't use the dataLoadOptions at all, and it does what I need it to do. For example, if the Order has a purchase order record, but no invoice record, Order.PurchaseOrder will contain the purchase order data and Order.Invoice will be null. My query looks something like this:
DC.Orders.Where(a => a.Order_ID == id).SingleOrDefault();
I try not to micromanage LINQ-to-SQL...it does 95% of what I need straight out of the box.
UPDATE 2:
I found this post that discusses the use of DefaultIfEmpty() in order to populated child entities with null if they don't exist. I tried it out with LINQPad on my database and converted that example to lambda syntax (since that's what I use):
ParentTable.GroupJoin
(
ChildTable,
p => p.ParentTable_ID,
c => c.ChildTable_ID,
(p, aggregate) => new { p = p, aggregate = aggregate }
)
.SelectMany (a => a.aggregate.DefaultIfEmpty (),
(a, c) => new
{
ParentTableEntity = a.p,
ChildTableEntity = c
}
)
From what I can figure out from this statement, the GroupJoin expression relates the parent and child tables, while the SelectMany expression aggregates the related child records. The key appears to be the use of the DefaultIfEmpty, which forces the inclusion of the parent entity record even if there are no related child records. (Thanks for compelling me to dig into this further...I think I may have found some useful stuff to help with a pretty huge report I've got on my pipeline...)
UPDATE 3:
If the goal is to keep it simple, then it looks like you're going to have to reference those salesperson fields directly in your Select() expression. The reason you're having to use LoadWith<>() in the first place is because the tables are not being referenced anywhere in your query statement, so the LINQ engine won't automatically pull that information in.
As an example, given this structure:
MailingList ListCompany
=========== ===========
List_ID (PK) ListCompany_ID (PK)
ListCompany_ID (FK) FullName (string)
I want to get the name of the company associated with a particular mailing list:
MailingLists.Where(a => a.List_ID == 2).Select(a => a.ListCompany.FullName)
If that association has NOT been made, meaning that the ListCompany_ID field in the MailingList table for that record is equal to null, this is the resulting SQL generated by the LINQ engine:
SELECT [t1].[FullName]
FROM [MailingLists] AS [t0]
LEFT OUTER JOIN [ListCompanies] AS [t1] ON [t1].[ListCompany_ID] = [t0].[ListCompany_ID]
WHERE [t0].[List_ID] = #p0

Resources