LINQ join multiple tables by same column - c#

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

Related

LINQ query for getting data from 3 tables

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"

Create a table in SQL Server where the name is a variable in C#?

Using Visual Studio and SSMS.
I have a form where a user registers a username and it's stored like this:
List<SqlParameter> sqlNewTable = new List<SqlParameter>();
sqlNewTable.Add(new SqlParameter("Username", txtUser.Text));
DAL.ExecSP("CreateUserCourses", sqlNewTable);
From there, can I create a stored procedure called CreateUserCourses in which it creates a new table where the users input (their username) is the name of a new table?
Sure you can, but why?
Supposing you have a User table and a Course table. Then just make a 3rd table which maps those tables together Called UserCourses. This is called a Many-to-Many (mapping table) and it will containing an ID of both the User, and Course and any other relevant information .
This will make your life a lot easier going forward
Many-to-many (data model)
A many-to-many relationship is a type of cardinality that refers to
the relationship between two entities1 A and B in which A may
contain a parent instance for which there are many children in B and
vice versa.
For example, think of A as Authors, and B as Books. An Author can
write several Books, and a Book can be written by several Authors
Example
student: student_id, first_name, last_name
classes: class_id, name, teacher_id
student_classes: class_id, student_id // mapping table
SQL queries could look like this
Getting all students for a class
SELECT s.student_id, last_name
FROM student_classes sc
INNER JOIN students s ON s.student_id = sc.student_id
WHERE sc.class_id = X
Getting all classes for a student
SELECT c.class_id, name
FROM student_classes sc
INNER JOIN classes c ON c.class_id = sc.class_id
WHERE sc.student_id = Y
Entity framework queries could look like this
Getting all students for a class
var students = db.Students.Where(x => x.StudentClasses
.Any(y => y.ClassId == 1);
Getting all classes for a student
var classes = db.classes.Where(x => x.StudentClasses
.Any(y => y.StudentId == 1);

Outerjoin with a table of foreign keys in EF Data-first approach

I have a table Users which contain user information. I have a table Products which contain product information. I have a table called UserProduct which acts as a junction table and whose fields are UserId and ProductId. I am using a Entity Framework database first approach.
I want to outerjoin using Linq to find the following data.
All Users in the Users table.
All Users who have bought a particular product in terms of a Boolean called isPurchased.
My thinking was to left outer join table User with UserProduct and get all users and whether they have a product something like this.
var result = from a in Users
join b in UserProduct(Not available through EF) on a.Id equals b.prodId into group1
from g1 in group1.DefaultIfEmpty()
select new
{
id = g1.Id,
isPurchased = g1.prodId != null
}.ToList();
However in EF mapping, the object UserProduct is not created and so I cannot use it directly in my Linq query? So how do I go about this? Is there a way I can use linq to join tables with the actual table name(UserProduct) instead of joining entities?
Assuming Users contains a property List<Products> products to represent the junction information, and a variable boughtProductId to represent the particular product:
var result = from u in Users
let isPurchased = u.products.Any(p => p.Id == boughtProductId)
select new {
id = isPurchased ? boughtProductId : null,
isPurchased
}.ToList();

C# LINQ multiple one to many includes with where condition

I can't find the correct query to get all the data I need.
I've got three sample tables:
Employee (Id, Name, Telephone)
EmployeeDepartment (Id, EmployeeId, DepartmentId)
DepartmentAddress (Id, DepartmentId, City,
Street, IsActive...)
Each of this tables has one-to-many relation with next one. An Employee can be assigned to many departments and one department can have many addresses assigned.
I'm trying to write an Entity Framework query which will return, for example, all employees which are assigned to department in City of New York. My problem is that I don't want just employees. I need it to also include all properties of EmployeeDepartment and DepartmentAddress in Employee class.
What I'm trying to do is something like:
var matchingEmployees = ctx.Employee
.Include("EmployeeDepartment")
.Include("EmployeeDepartment.DepartmentAddress")
.Where(p=> p.EmployeeDepartment
.Where(x=>x.DepartmentAddress.Where(y=>y.City == "New York)))
.ToList();
As a result I get all Departments and all EmployeeDepartments, when I need only ones matching Where condition. What query will return the data I need?
You don't need to use the where like that just get to the condition directly. I think its something like that, if you post your entities it would be a little more easier.
var matchingEmployees = ctx.Employee
.Include("EmployeeDepartment")
.Include("EmployeeDepartment.DepartmentAddress")
.Where(p=> p.DepartmentAddress.City == "New York)))
.ToList();
Maybe this:
ctx.Employee
.Include( e => e.EmployeeDepartment)
.Include( e => e.EmployeeDepartment.DepartmentAddress)
.Where(e => && e.EmployeeDepartment.DepartmentAddress.All(y => y.City == "New York")).ToList();
"What I'm trying to do is write Entity Framework query, which will return, for example, all employees which are assigned to department in City of New York."
If you're not interested in actually retrieving the departments, and only want to filter by them, then a join would be more appropriate. It's easier for other developers to see that your intention is to narrow down the list of employees, rather than simply narrowing down the list of nested department addresses. This can be accomplished in extension syntax(which I prefer), but joins are much more readable in expression syntax:
var employeesInNewYorkDepartments =
(from employee in ctx.Employee
let department = employee.EmployeeDepartment
let departmentAddress = department.DepartmentAddress
where departmentAddress.City == "New York"
select employee).ToList();
Hopefully I don't have syntax errors. This assumes the relationship from employee to department is many-to-one. I.e. many employees are in a department.
If it's the opposite for both relationships:
var employeesInNewYorkDepartments =
(from employee in ctx.Employee
join department in employee.EmployeeDepartments
join departmentAddress in department.DepartmentAddresses
where departmentAddress.City == "New York"
select employee).Distinct().ToList();
Note your navigation property name should be plural when navigating in the many direction. They represent access to a collection and therefore a singular navigation property name is extremely misleading. (The entity class name would still be singular though as it declares the structure of a single item.)

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