C# Include, ThenInclude Select specific fields - c#

I have the following classes:
public class Teacher
{
Guid ID;
string Name;
string Address;
string Tel;
ICollection<Student> Students;
}
public class Student
{
Guid id;
string Name;
string Address;
string Tel;
ICollection<Teacher> Teachers;
}
As this is a Many-to-Many relationship, EF would create a Junction Table TeacherStudent. I want to find all teachers and the list of the teacher's students (name only), eg:
Teacher1 name:
Student1 name,
Student3 name,
Student5 name,
Student9 name
Teacher2 name:
Student1 name,
Student2 name,
Student3 name,
Student9 name
I've tried:
_context.Teacher.Include(x=>x.TeacherStudent).ThenInclude(y=>y.Student);
This works and Student info is a list under each Teacher. However, I don't want all the info, I only want the name. I've tried SelectMany but it flattened out the data and I get the Teacher's name repeated for each Student. How can get a list of Student names under the Teacher's name and not retrieve the fields I don't need ?
Thanks.

Include is used to eager load related entities. Select is used to project an entity query into a desired data structure, or otherwise retrieve pieces of information about an entity.
So you can get a list of teachers and students' names:
var teachers = _context.Teacher
.Select(t => new
{
t.Id,
t.Name,
Students = t.Students.Select(s => new
{
s.Id,
s.Name
}).ToList()
}).ToList();
What this would return is an anonymous type containing a list of teachers, pulling back just their ID and name, and a collection of Student anonymous types with the student ID and Name.
Typically when reading data with projections it is a good idea to read IDs since you will often want to use this information to either bring up details, remove, or otherwise alter entries.
If I were to use projection, does it mean that EF still would
retrieve all the fields before projecting
Projection builds an SQL query to pull back just the fields needed to populate the data structure you request. It doesn't load entities so this is somewhat mutually exclusive to Include. You do not need to "include" related entities to use projection.
Eager Loading with Include is typically something you will use when performing an update. for instance if you are going to be adding or removing student associations to a Teacher, you will want to load the Teacher and associated Students to ensure that the change tracking understands what you are doing when you go to add or remove students from a Teacher.

Related

Entity Framework 6.2 copy many to many from one DbContext to another DbContext

When working with a network database such as MySQL, the DbContext should be short lived, but according to https://www.entityframeworktutorial.net/EntityFramework4.3/persistence-in-entity-framework.aspx the DbContext can be long lived when working with a local database, such as SQLite.
My app is using a long lived DbContext to work with SQLite on HDD and I want to copy many-to-many entities to another DbContext for the same type of SQLite database on USB.
I am using the Code-First approach.
public class Student
{
public Student()
{
this.Courses = new HashSet<Course>();
}
public int StudentId { get; set; }
[Required]
public string StudentName { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
public class Course
{
public Course()
{
this.Students = new HashSet<Student>();
}
public int CourseId { get; set; }
public string CourseName { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
DbContextHDD contains students StudentA, StudentB and StudentC and courses Course1, Course2 and Course3:
StudentA attends Course1 and Course3
StudentB attends Course2 and Course3
StudentC attends Course1 and Course2
DbContextUSB contains no students and no courses.
var courses = DbContextHDD.Courses.AsNoTracking();
List<Student> students = new List<Student>();
foreach(Course course in courses)
{
foreach(Student student in course.Students)
{
if(!students.Any(s => s.StudentId == student.StudentId))
{
students.Add(student);
}
}
}
Debug.WriteLine(students.Count); // output: 3
Debug.WriteLine(DbContextUSB.Students.Local.Count); // output: 0
DbContextUSB.Students.AddRange(students);
Debug.WriteLine(DbContextUSB.Students.Local.Count); // output: 4
DbContextUSB.SaveChanges(); // exception: UNIQUE constraint failed
DbContextUSB.Courses.AddRange(courses);
DbContextUSB.SaveChanges();
Why are there 4 students (3 unique and 1 duplicate) after I insert 3 unique students in to a DbSet with 0 students? What is the proper way to do this?
As I said, I am using a long lived DbContext because I am working with SQLite.
First, don't use AsNoTracking:
var courses = DbContextHDD.Courses. ...
Second, Include the required data:
var courses = DbContextHDD.Courses
.Include(c => c.Students)
.ToList();
Third, add the courses to the other context:
DbContextUSB.Courses.AddRange(courses);
DbContextUSB.SaveChanges();
You may not believe it, but in essence that's all!
One caveat is that you should disable proxy creation in the source context:
DbContextHDD.Configuration.ProxyCreationEnabled = false;
Otherwise EF creates proxy objects, which have a reference to the context they came from. They can't be attached to another context.
Another is that there may be students that don't attend courses. You'll miss them when querying courses. So you have to add them separately:
var lazyStudents = DbContextHDD.Students.Where(s => s.Courses.Count() == 0).ToList();
...
DbContextUSB.Students.AddRange(lazyStudents);
...
DbContextUSB.SaveChanges();
Why does this work?
Without tracking, Entity Framework can't detect that StudentA in
Course1 is the same student as in Course3. As a consequence, StudentA
in Course3 is a new Student instance. You'll end up having 6 students, 3 duplicates (if there's no unique index on StudentName preventing this). With tracking, EF does detect
that both courses have the same Student instance.
When adding an entity to a context, EF also marks nested
entities as Added when they're not yet attached to the context.
That's why it's enough to add courses only, and that's why EF doesn't
complain when courses contain the same student instances.
Since the added courses have their Students collections properly populated, EF also inserts the required junction records in the StudentCourse table. This didn't happen in your code (well maybe, or partly, see later).
Now why did you get 4 students?
Look at the courses:
Course1 StudentA*, StudentC*
Course2 StudentB*, StudentC
Course3 StudentA , StudentB
Because of AsNoTracking all student are different instances, but only the marked* students are in students because of how you add them. But here's the tricky part. Even with AsNoTracking(), Entity Framework executes relationship fixup with related entities that are materialized in one query. That means that the foreach(Course course in courses) loop produces courses with populated Students collections of which each student has one course in its Courses collection. It's almost impossible to keep track of what exactly happens, esp. because debugging also triggers lazy loading, but for sure, the line...
DbContextUSB.Students.AddRange(students);
also marks their nested courses and their students as Added as far as they ended up being different instances. The end result in this case is that one more student instance is added to the cache. Also, a number of junction records was created but not necessarily the correct ones.
The conclusion is that EF is a great tool for cloning object graphs, but the graph must be populated correctly, the right relationships and no duplicates, and should be added in one go.

Querying Many to Many relationships Entitty Framework (doing wrong?? )

I've been doing some research on this topic and figure out a way to achieve this queries in my project but I'm not sure if something here is wrong. please help.
in summary I've created the entities like this:
class Student
{
public int StudentId { get; set; }
public string Name { get; set; }
public ICollection<Courses> Courses {get;set;} //or public List <Courses> {get;set;}
}
class Course
{
public int CourseId { get; set; }
public string Name { get; set; }
public ICollection<Students> Students {get;set;} //or public List<Students> {get;set;}
}
// We can see here that the database creates the Join Table Correctly
What I want to do:
Display in a grid view each student and for each of the students display the courses in wich they are enrolled.
If I made a simple query like
dbContex.Students.ToList(); 
and we look at the list the Collection of courses value is null. What is happening here?, shoulden't EF map this and make a query to SQL to get the info?
After this y could not solve the problem because the info that I found was using other approach of the framework (Diagram First ,i think) and they set up things in the entities diagram.
 
How did I work out the problem :
Find out in a Wordpress Post a Query that I haven´t tried out and add some other lines of code to achieve what I wanted:
aux_S = contexto.Students.ToList();
foreach(var element in aux_S)
         
   {
                
element.Courses= contexto.Courses.Where(c => c.Students.Any(s => s.StudentId == element.StudentId)).ToList();
          
  }
// I know I can make a projection to dismiss all the fields that I do not need , this is just to try it out
Am I wrong  doing this ?
It worked, but how is it possible?
One of the slower parts of a database query is the transfer of the data to your machine. So it is good practice to transfer only the data you plan to use.
When you use LINQ in entity framework, using Queryable.Select is a good way to specify exactly what data you want to transfer. This is usually done just before your final ToList / ToDictionary / FirstOrDefault / Single / ...
You want all Students, each with all his Courses. If you look at your tables, you'll see that there is more data in the tables then you want. For instance, each Student has an Id, each of his Courses have the same value for StudentId. So if a Student attends 20 Courses, you would have transferred the same value for StudentId 21 times.
So to make your query efficient: Select only the Properties of Students you plan to use, with only the Properties of the Courses of these Students you are interested in.
This will automatically solve your problem:
var result = myDbcontext.Students
// if you don't want all Students, use a Where:
.Where(student => student.City = "Guadalajara")
// Select only the properties you plan to use:
.Select(student => new
{
Id = student.Id,
Name = student.Name,
Birthday = student.Birthday,
Address = new
{
Street = student.Street,
City = student.City,
...
}
Courses = student.Courses
// if you don't want all courses: use a where
.Where(course => course.Start.Year == 2018)
// again: select only the properties you plan to use
{
Name = course.Name,
Location = course.Location,
...
// One of the useless properties to transfer:
// StudentId = course.StudentId
})
.ToList();
});
If you perform this query:
var studentslist = dbContex.Students.ToList();
Each item on studentslist will have the 'Courses' collection null, because, although the connection/relation exists (between each table), you didn't specify that you wanted that collection populated. For that to happen you can change your query accordingly:
var studentslist = dbContex.Students.Include(p => p.Courses).ToList();
Now, after running the last query, if you get an empty list on one/any of the items, then it means those items (students), aren't linked to any courses.
You are not lazy loading, if you add virtual like: public virtual ICollection<Courses> Courses {get;set;} you should get the courses loaded.
However, I'd advise using lazy loading since it may cause performance issues down the road, what you want to do is eager loading.
So when you are querying your student you would simply do this:
dbContex.Students.Include(c => c.Courses).ToList();

Add foreign property to LINQ-to-SQL model

How can I add a custom property to my LINQ-to-SQL model that automatically joins a value from a foreign table, effectively de-normalizing an association?
My (simplified) database model:
Customer Group
------------ ----------
CustID [PK] GroupID [PK]
FirstName GroupName
LastName ...
GroupID [FK]
...
My first (simplified) approach:
public partial class Customer
{
public string GroupName
{
get { return this.Group.GroupName; }
}
}
In reality, GroupName is a few associated tables away, requiring me to join, filter, group, select, etc. to get to it. All doable with plain SQL.
Accessing GroupName triggers a separate SQL query. I know I could preload the Group model with loadOptions.LoadWith<Customer>(c => c.Group), but I would need to remember that and do it for every new DataContext, and I don't really need the Group model (and all models inbetween) at all. I just want to have the GroupName as readonly string on the Customer.
So I basically want to extend the model like I would do with a SQL query:
SELECT Customer.*, Group.GroupName
FROM Customer
INNER JOIN Group
ON Customer.GroupID = Group.GroupID
Can this be done with a LINQ-to-SQL model? Alternatives?

Entity framework set navigation property

We have database which does not have proper foreign keys set. We are now generating edmx using this database. What we want is to set navigation property so that we can get corresponding details from other table. Here is example what exactly we are looking for.
Lets say There is a table Employee and Department. Now in database there is no relation between these tables but Employee has DepartmentId which is taken from Department table.
When we fetch Employee we get only DepartmentID but we also want to get Department as property along with it so that we can get information like "DepartMentName", "Location" which is stored in Department table.
We tried adding Navigation property in EDMX file but it fails and keeps giving error related to relation.
Please help
You can go with something like this. Create a wrapper class for Employee and Department.
public class EmpDept
{
public Employee Employee {get; set;}
public Department Department {get; set;}
}
public IEnumberable<EmpDept> GetEmployeesWithDeptpartment()
{
var result = from e in context.Employee
where e.Id == somevalue
select new EmpDept()
{
Employee = e,
Department = context.Department.Where(d => d.Id == e.DepartmentId)
};
return result.ToList();
}
It means you have an extra class, but it's quick and easy to code, easily extensible, reusable and type-safe.
Hope this helps

Linq To Sql One To Many

Say I have 3 tables, Cars, Persons, CarOwners.
Cars is just a table of cars and Persons are different people that can own cars. CarOwners has a CarId and a PersonId relationship and a CarOwnerId PK.
If I just create the relationship and then drag them to the linq context, a property of the Person class will be generated called CarOwners. To get the cars that this person owns I would have to do the following query:
var cars = person.CarOwners.Select(c=> c.Car);
But I would like to be able to do:
var cars = person.Cars;
Is that at all possible? The extra step is quite annoying.
You can just create a new property on Person:
public partial class Person
{
public IEnumerable<Car> Cars
{
get { return this.CarOwners.Select(c => c.Car); }
}
}
Or upgrade to Entity Framework where you can separate logical model from physical model.
Try to remove the CarOwnerId from CarOwners in your sql database. Then select CarId and PersonId in designer mode in SSMS (using shift key), right click and select `Set Primary Key.
In EF 4, you'll get only navigation keys in your model this way (you can use: Person.Cars and Car.Persons). Not sure about L2S.

Categories

Resources