LINQ query for getting data from 3 tables - c#

I am using SQL Server 2017, .Net 4.5 and EF core.
I have the following tables: Student_Course: has Student_Id, Course_ID and Student
_Course_ID - pk.
Student: First_Name, Last_Name, Email, Student_ID - PK
Course: Id pk, Name
I am trying to retrieve a list of students with their respective
courses. I also need the course count for each student.
For the above mentioned tables I have the following entities in C#
Student_Course: Has navigational properties for list of students and courses
Student:
Course:
I am trying to make this query using LINQ:
Controller:
public IList<StudentCourse> GetStudents()
{
Student_CourseContext scContext = new Student_CourseContext();
var studentCourses = (from sc in scContext.Student_Course
from student in sc.Students.Where( x => x.Student_ID ==
sc.Student_ID)
from course in sc.Courses.Where (x => x.ID == sc.Course_ID)
where sc.Student_ID == student.Student_ID
&& sc.Course_ID == course.ID
&& student.First_Name != "None"
//join course in sc.Courses
//from sc in studentCourseEntity.Student_Courses.GroupBy( x => x.Student_ID)
select new StudentCourse
{
StudentName = student.First_Name + ' ' + student.Last_Name,
//CourseCount = sc.Gr,
Course = string.Join(",", course.Name)
}).ToList();
return studentCourses.ToList();
}
}
It is returning a query like this:
SELECT ([s0].[First_Name] + CAST(N' ' AS nvarchar(max))) + [s0].[Last_Name], [c].[Name]
FROM [Student_Course] AS [s]
INNER JOIN [Student] AS [s0] ON ([s].[Student_Course_ID] = [s0].[Student_Course_ID]) AND ([s].[Student_ID] = [s0].[Student_ID])
INNER JOIN [Course] AS [c] ON ([s].[Student_Course_ID] = [c].[Student_Course_ID]) AND ([s].[Course_ID] = [c].[ID])
WHERE (([s].[Student_ID] = [s0].[Student_ID]) AND ([s].[Course_ID] = [c].[ID])) AND (([s0].[First_Name] <> N'None') OR [s0].[First_Name] IS NULL)
Instead of joining on the columns pecfied it is joining on STudent_Course_ID which is the primary key of the Student_Course table. Due to this join I am getting the error:
Invalid column name 'Student_Course_ID'. Invalid column name 'Student_Course_ID'.
since the Student and Course tables do not have this field. Please let
me know how I can make this work.
I tried mode.OnCreating method to define the relationship betweeen
these tables but I am able to map between Student, Student_Course and
Course tables.
Thanks in advance.

So you want for every Student his name and the list (collection?) of the names of all courses he attends.
var studentsWithTheCoursesTheyAttend = dbContext.Students
.Select(student => new
{
Name = student.First_Name + ' ' + student.Last_Name,
Courses = dbContext.Student_Course
.Where(studentCourse => studentCourse.StudentId == student.Id)
.Select(studentCourse => dbContext.Courses
.Where(course => course.Id == studentCourse.CourseId)
.Select(course => course.Name)
.FirstOrDefault())
.ToList(),
}
Explanatation: from every Student in the complete table of Students, take his name:
... = dbContext.Students
.Select(student => new
{
Name = student.First_Name + ' ' + student.Last_Name,
Also select all the Student_Courses of this Student (= all Student_Courses that have a foreign key StudentId that equals the primary key of the Student):
Courses = dbContext.Student_Course
.Where(studentCourse => studentCourse.StudentId == student.Id)
From every selected Student_Course of this Student you want to get the Name of the Course that this Student_Course refers to. For this we will first get all Courses with a primary key equal to the foreign key StudentCourse.CourseId:
.Select(studentCourse => dbContext.Courses
.Where(course => course.Id == studentCourse.CourseId)
Well, we don't want the complet course, we only want its Name:
.Select(course => course.Name)
Well, we know that every studentCourse will only have exactly one Course that it refers to, not zero, not more than one. After all, the foreign key studentCourse.CourseId refers to exactly one Course.
Therefore we only have to take the first one. I could have used First(), but some Providers in the IQueryable report problems when using First, hence I use FirstOrDefault().
.FirstOrDefault(),
So by now, from ever Student, I have his Name, and the Names of all Courses he attends. Make this a List of courses:
Courses = ...
.ToList(),
So now you have something like this:
{
Name = "Sukumar Krishnamurthy",
Courses =
{
"Advanced .NET",
"All aspects of LINQ",
"Ups and downs of C#",
},
},
{
Name = "Harald Coppoolse",
Courses =
{
"Windows Services",
"REST in a proper manner",
},
},
Property Courses is the List of Names of Courses that the Student attends. The length of the List represents the number of attended Courses.
If desired you can transform this list of Courses into one string with Names. However I am not sure that this will improve readability:
"Windows Services REST in a proper manner"

Related

LINQ join multiple tables by same column

I have one table Texting, that I need to join with another two tables Student and Staff to search for information about these 2 tables.
Student fields:
Id
Name
... and a bunch of other fields specific to student
Staff fields:
Id
Name
... and a bunch of other fields specific to staff
Texting fields:
Id
PersonId // contains either student ID or staff ID
PersonTypeId // indicates whether PersonId is of type student or staff (student = 1, staff = 2)
Now I need to write a linq query to search the table Texting either by student or staff name but I am stuck on the linq to achieve this.
var query = (from t in texting
join s in studentBo.GetListQuery()
on t.PersonId equals s.Id
join st in staffBo.GetListQuery()
on t.PersonId equals st.Id
where ...
select t);
This joins the tables together but it doesnt care what the PersonId type is so it's all mixed. How do i specify so that it joins the PersonId correctly according to the right PersonTypeId? It seems like nothing else can be appended on the on clause or where clause to make this happen = (.
So you have a name, and you want all Textings that refer to a Student with this name, and all Textings that refer to a member of Staff with this Name.
My advice would be to concat the Student textings with the Staff textings. You could do that in one big LINQ statements, however this would make it quite difficult to understand. So I'll do it in two steps, then Concat it in one query:
const int student = 1;
string name = "William Shakespeare";
var studentTextings = textings.Where(texting => texting.PersonTypeId == student)
.Join(students.Where(student => student.Name == name),
texting => texting.PersonId, // from every Texting take the foreign key
student => student.Id, // from every Student take the primary key
// parameter resultSelector:
// from every texting with its matching student make one new:
(texting, studentWithThisTexting) => new
{
// Select the Texting properties that you plan to use
Id = texting.Id,
...
}
In words: from all Textings, keep only those Textings that refer to a student, so you know that the foreign key refers to a primary key in the table of Students. From all Students keep only those Students that have the requested name.
Join all remaining Textings and the few remaining Students that have this name on primary and matching foreign key.
Do something similar for members of Staff:
const int staff = 2;
var staffTextings = textings.Where(texting => texting.PersonTypeId == staff)
.Join(staffMembers.Where(staffMember => staffMember.Name == name),
texting => texting.PersonId, // from every Texting take the foreign key
staffMember => staffMember.Id, // from every Staff member take the primary key
// parameter resultSelector:
(texting, staffMembers) => new
{
// Select the Texting properties that you plan to use
Id = texting.Id,
...
}
Now all you have to do is Concat these two. Be aware: you can only Concat similar items, so the resultSelector in both Joins should select objects of exactly the same type.
var textingsOfPersonsWithThisName = studentTextings.Concat(staffTextings);
There is room for improvement!
If you look closely, you'll see that the textings table will be scanned twice. The reason for this, is because your database is not normalized.
Can it be, that a Texting for a Student will ever become a Texting for a member of Staff? If not, my advice would be to make two tables: StudentTextings and StaffTextings. Apart from that queries will be faster, because you don't have to check PersonType, this also has the advantage that if later you decide that a StudentTexting differs from a StaffTexting, you can change the tables without running into problems.
If you really think that sometimes you need to change the type of a texting, and you don't want to do this by creating a new texting, you also should have two tables: one with StudentTextings, and one with StaffTextings, both tables having a one-to-one relations with a Texting.
So Students have one-to-many with StudentTextings, which have one-to-one with Textings. Similar for Staff and StaffTextings.
So Student [4] has 3 StudentTextings with Id [30], [34], [37]. Each of these StudentTextings have a foreign key StudentId with value [4]. Each StudentTexting refers to their own Texting with a foreign key: [30] refers to texting [101], so it has foreign key 101, etc.
Now if texting [101] has to become a texting for Staff [7], you'll have to delete the StudentTexting that refers to [101] and create a new StaffTexting that refers to Staff [7] and Texting [101]
By the way, since the combination [StudentId, TextingId] will be unique, table StudentTextings can use this combination as primary key. Similar for StaffTextings
You will have to merge Student and Staff tables, otherwise all your queries will be too complicated, since you will have to use Union
Person
Id
Name
PersonType
Texting
Id
PersonId
and query
var query = (from t in texting
join p in person
on t.PersonId equals p.Id
where ...
select t);
PS if you still want a query with 2 tables instead of one, you will have to post the real code.
You'll need to do these as two separate queries, project to a new type and then union the results. Messy, but here's how.
First get your students:
var textingStudents = (
from s in students
join t in texting on s.Id equals t.PersonId
where t.PersonTypeId == 1
select new { id = s.Id, personTypeId = 1, name = s.Name }).ToList();
Now get your staff in almost the exact same way:
var textingStaff = (
from s in staff
join t in texting on s.Id equals t.PersonId
where t.PersonTypeId == 2
select new { id = s.Id, personTypeId = 2, name = s.Name }).ToList();
Now you can union the two:
var allTextingPeople = textingStudents.Union(textingStaff);
If you need additional properties then add then to the anonymous type declared in the select statement - remember, the type will need to have the same properties in both the textingStudents and textingStaff result. Alternatively, define a class and do a select new MyUnionClass { ... } in both queries.
Edit
You're going to probably get into a world of hurt with the current approach you've outlined. If you're using a relational database (i.e. sql server) you almost certainly are not defining constraints such as foreign keys on your Texting table meaning you'll end up with ID clashes and will definitely end up with bugs later down the road. Best approach is probably to have one table to represent Staff and Student (let's call it Person with a column defining the "type" of person - the column itself will be foreign key link to another table with your list of PersonTypes

c# LINQ to object Autoincrement in subquery

Good Afternoon! Could anyone suggest how I can implement autoincrement in linq to object subquery which must be started from 1 on every group of main linq query? I have query
from student in students
group student by student
into keyStudents
select new StudentClass
{
Student = keyStudent.Key,
Subjects = from key in keyStudent
select new SubjectClass
{
Subject = key.Subject,
SubjectId = (stuck here)
}
}
My result must be like this:
Student
John
1. Math
2. Bio
Fred
1. Math
2. Physics
3. Bio
My query returns accurate data without SubjectId, but I need to show SubjectId as well. So the SubjectId is just index number started with one in every group.
You'd have to use method syntax so you can use the override of Select that includes an index
from student in students
group student by student
into keyStudents
select new StudentClass
{
Student = keyStudent.Key,
Subjects = keyStudent.Select((x, i) =>
new SubjectClass
{
Subject = x.Subject,
SubjectId = i + 1,
}),
}

How to get two table value using Linq

I have two table one is Administrator table and another is Teacher table .
I want to display these both tables values in single Gridview .
I have make id as a primary key in Administrator table and make this tech_id as foreign key in Teacher table .
Now how to get these table values together in single gridview as shown in pic
Now please any body help me how to get these two value together using Linq .
I have try but I can't make any more
private void loadgri()
{
StudentDatabaseEntities empl = new StudentDatabaseEntities();
var query=from g in empl.Teachers
join m in empl.Administrators on g.id equals m.id
where m.username=="cs"
select new{
Name = g.username,
};
}
You don't need a join if you have already a navigation-property:
var query= from t in empl.Teachers
where t.Administrator.username == "cs"
select new { Teacher = t.username, Administrator = t.Administrator.username };
This is just an example, but you see that you can access all properties of both entities.
Don’t use Linq’s Join. Navigate!
To show all the teachers and their administrator, you don't have to use "join", you could just use the navigation property:
var query = from g in empl.Teachers
where g.Administrator.username=="cs"
select new {
Teacher_Id = g.Id,
Teacher_Name = g.username,
Administrator_Id = g.Id,
Administrator_Name = g.Administrator.username,
//etc...
};

Conditional LINQ query on self-joining table with 2 sets of data

I am trying to figure out how to perform a conditional query on an Employees table to bring back all of their assigned Projects, but the caveat I don't quite understand on how to implement is that for every Employee, there is 0 to 1 EmployeeAssistant (self-joining entity). So when I select EmployeeID=2 and it has an EmployeeAssistantID=5, I would like to display all of the Projects for both of these individuals, i.e. the main Employee (EmployeeID=2) and their assistant (EmployeeID=5).
The tables look like:
Employees
- EmployeeID -- (Pkey)
- EmployeeAssistantID -- (Fkey to Employees.EmployeeID)
- other fields
-
Projects
- ProjectID -- (PKey)
- EmployeeID -- (Fkey to Employees.EmployeeID)
- other fields
I attempted the following in LINQ:
var projects = Projects.Include(proj => proj.Employee)
.Select(x => new
{
proj.ProjectID,
proj.ProjectName,
proj.Employee.Name
// ... not sure how to bring back another layer of projects for the EmployeeAssistant?
})
You can use the following Linq query to get the data you want:
var query = from e in Employees
join a in Employees on e.EmployeeAssistantID equals a.EmployeeID
where e.EmployeeID == 2
select new
{
EmployeeID = e.EmployeeID,
AssistantID = a.EmployeeID,
EmployeeProjects = Projects.Where(p => p.EmployeeID == e.EmployeeID),
AssistantProjects = Projects.Where(p => p.EmployeeID == a.EmployeeID)
};
The anonymous type returned by the query contains all of the data from both employee entities, as well as all of the Project data of each employee (some can be the same, others might differ).
I believe you mean to do the following:
var employeeIDs = new[] { myEmployee.EmployeeID, myEmployee.EmployeeAssistantID };
var projects = Projects.Where(p => employeeIds.Contains(p.EmployeeID));
This will grab all projects that both the employee and the assistant have done, given a previously grabbed Employee record (which I've called myEmployee).

Linq select Item where it is equal to ID in another table

I am not sure how possible this is but I have two tables and I want to grab a value from table 2 via the value of table 1.
Table 1 has the a Foreign Key called "rank" which is an int. Table 2 has a value called "name" which is a string. Now Table 1's "rank" correlates to Table 2's "ID".
So when I say
var result =
db.Table1.Select(x => new { x.name, x.rank }).ToList();
//Bob - 2
I really want to say something like
var result =
db.Table1.Select(x => new { x.name, Table2.rank.Where(ID == x.rank) }).ToList();
//Bob - Gold
I am still new to LINQ though and I am not sure how to get rank's string value from the other table within a query like this.
EDIT
Tables I am using and their relational values.
User: ID (PK), s1elo (FK to PastElos), champ (FK to ChampionList), elo (FK to EloList)
PastElo: ID (PK), Rank
ChampionList: ID (PK), name
EloList: ID (PK), Rank
Working example for Users and PastElo
var result =
db.Users.Join(db.PastEloes,
x => x.s1elo, y => y.ID, (x, y)
=> new { y.Rank, x.name, x.other_items_in_Users }).ToList();
Note: PastElo is PastEloe's due to EF making everything plural when I synced up my DB, thus why User is also Users, I think that is referred to as the "context".
You could try something like the following:
var result = db.Table1.Join(db.Table2,
x=>x.rank,
y=>y.ID,
(x,y) => new { x.rank, y.Name }).ToList();
In the above linq query we make a Join between the two tables, Table1 and Table2 based on the association and then we select that we want.
Another way you could try to write this query would be the following:
var result = (from t1 in db.Table1
join t2 in db.Table2
on t1.rank equals t2.ID
select new { t1.rank, t2.Name, }).ToList();
Another way to do this would be to include your Database relationships in your C# entities. You could use EntityRef here. See the following documentation:
https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql/linq/how-to-map-database-relationships

Categories