Inheritance in DbSet<T> EntityFramework for similar tables - c#

I want to do Inheritance for similar tables.
For example suppose we have two tables: Teachers and Students both of them can be derived from a base class Human.
Is it possible to write a common task and avoid repeat code using EntityFramework? a function like this which works for DbSet<Student> and DbSet<Teacher>:
void IncreaseAge(DbSet<Human> humans, int id)
{
//
}
and more important, a generic add function to db, pseudo code:
void AddHuman({...}, string name, int age)
{
// add a human to related table
}
any help would be greatly appreciated.

Use Extension with a Generic parameter:
void IncreaseAge<T>(DbSet<T> entities, int id) where T: Human
{
var current = entities.Find(id);
current.Age++;
// SaveChanges() in the context
}
If your Student is inheriting Age property from Human class. This code should work perfectly.
Edited
Also, you can apply the same technique for the add
void Insert<T>(DbSet<T> entities, string name, int age) where T: new(), Human
{
entities.Add(new T{ Name = name, Age = age });
// SaveChanges() in the context
}
Hope this help!

A DbSet represents all the entities. You'd normally not pass it to a function. Instead consider a method like this in your DbContext:
public void IncreaseAge(IQueryable<Human> humans)
{
foreach( var h in humans)
{
h.Age++;
}
SaveChanges();
}
Then you can pass in an query that specifies a set of teachers or students you'd like to operate on. EG:
db.IncreaseAge(db.Teachers.Where(t => t.Age == 47));

I guess you have read about the various strategies when using inheritance in Entity Framework. The nice thing about entity framework is that it hides the used inheritance strategy.
As the three inheritance strategies are described in the link, there is no need to describe the fluent API needed for this. I'll only write the classes you'll end up with and how to do queries on Students, Teachers and the common base class Persons.
Furthermore I'll describe some considerations needed to properly select the correct inheritance strategy.
So you have Student and Teachers, both derived from Person.
abstract class Person
{
public string Name {get; set}
public Gender Gender {get; set;}
public DateTime Birthday {get; set;}
}
public class Teacher : Person
{
public int Id {get; set;}
...
}
public class Student : Person
{
public int Id {get; set;}
...
}
And the DbContext:
public class MyDbContext : DbContext
{
public DbSet<Teacher> Teachers {get; set;}
public DbSet<Student> Students {get; set;}
}
Now whenever you have an IQueryable of Teachers or Students you can also use all properties of Person, without doing something special.
var maleTeachers = myDbContext.Teachers
.Where(teacher => teacher.Gender == Gender.Male);
var youngStudents = myDbcontext.Students
.Where(student => student.Birthday > new Datetime(2000, 1, 1);
If you have to query all Persons, you'll have to Concatenate the Students and the Teachers. In baby steps:
IQueryable<Person> teachers = myDbcontext.Teachers.Cast<Person>();
IQueryable<Person> students = myDbContext.Students.Cast<Person>();
IQueryable<Person> allPersons = teachers.Concat(students);
var result = allPersons.Where(person => ...)
.Select(person => ...)
... etc
Of course this can be done in one statement.
Decide which inheritance strategy to use
When deciding on the inheritance strategy, keep in mind what kind of queries you'll do most:
Query Teachers who ... or Query Students who ...
Query Persons that ...
If you do the first most often, then consider Table per concrete class (TPC). You'll have two tables, one for students and one for teachers. The Person properties are in the same table. So table Students will have Person columns; table Teachers will also have Person columns.
The advantage is that if you ask for "Students who ...", only one table is involved. a join won't be necessary.
The disadvantage is that if you ask for "Persons that ...", the Teachers and Students tables need to be concatenated.
If you will be querying Persons more often, consider creating Table per Type (TPT). The result will be three tables with foreign keys: Teachers, Persons, Students. When asking for Persons, only one table is involved. However, when asking for Teachers we always need to join two tables.
Even if you opt for the best inheritance strategy because those are the kind of queries you perform most often you might sometimes have to do the other type of queries.
TPC: Table per concrete class
You choose this if you mostly ask for Teachers that ... or Students who ... You'll end up with two tables: one for Students, and one for Teachers. There will be no Persons table.
If you have to do an occasional Person query you'll have to concatenate the two sequences. This is in baby steps:
IQueryable<Person> teachers = myDbcontext.Teachers.Cast<Person>();
IQueryable<Person> students = myDbContext.Students.Cast<Person>();
IQueryable<Person> allPersons = teachers.Concat(students);
var result = allPersons.Where(person => ...)
.Select(person => ...)
... etc
Of course this can be done in one statement.
If you have to do this more often consider adding a prperty to your DbContext class that does this for you:
class MyDbcontext : Dbcontext
{
public DbSet<Teacher> Teachers {get; set;}
public DbSet<Student> Students {get; set;}
public IQueryable<Person> Persons
{
get
{
return this.Teachers.Cast<Person>()
.Concat(this.Students.Cast<Person>());
}
}
}
Usage will be:
using (var myDbContext = new MyDbContext(...))
{
IQueryable<Person> females = myDbcontext.Persons
.Where(person => person.Gender == Gender.Female);
}
If you don't want to pollute your Dbcontext, consider creating an extension function that does the same. See extension functions demystified
static class MyDbContextExtensions
{
IQueryable<Person> Persons(this MyDbContext dbContext)
{
return dbContext.Teachers.Cast<Person>()
.Concat(dbContext.Students.Cast<Person>());
}
}
TPT: table per type
You'll end up with three tables, Students, Teachers, Persons. Students and Teachers will have a foreign key to the Persons they are.
You can query Persons directly using the DbSet. On the other hand, if you want only Teachers who..., you'll just access the DbSet. As soon as you use one of the inherited Person properties, Entity framework will automatically do the join for you. You won't see this in the code. So even though you don't do a join, internally two tables are involved, which might be less efficient.
So be careful which inheritance strategy you choose.

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();

Generic table loading EntityFramework

There are two tables: Teachers and Students both of them can be derived from a base class Human.
May load a DbSet<Human> from Teacher and Student class? and do a generic function, e.g. generic inserting
pseudo code:
class Student : Human
{
//
//
//
DbSet<Human> GenericLoading(...)
{
//
}
}
usage of generic DbSet:
void Insert<T>(DbSet<T> entities, string name, int age) where T: new(),Human
{
entities.Add(new T{ Name = name, Age = age });
}
any help would be greatly appreciated.
Sounds like you're looking for Table-Per-Concrete-Type (TPC) inheritance.
Derive the two entities from the same base class Human and configure each entity class use its own table, then declare a DbSet<Human> in your DbContext
You could use IQueryable.Concat to create a union Query, if it is about loading the data:
var teacherQuery = db.Teacher.Select(e => new Person {...})
var studentQuery = db.Student.Select(e => new Person {...})
var combinedQuery = teacherQuery.Concat(studentQuery);
I am not sure how you could do an insert, if you don`t know the exact properties.

How to use LINQ to find a duplicate object

I have an Employee class that has the following:
public class Employee
{
//EmployeeNumber cannot be the same as the Id
public int EmployeeNumber {get; set; }
public string EmployeeName {get; set }
}
Ultimately I am going to be updating the database with new employees. I have a list of the new employees, and I have a list of the current employees that exist in the database. Employee name can be the same, but EmployeeNumber has to be unique. I want to ultimately have a list of duplicate employees that has been created from comparing the list I will be adding to the database, with the list of employees that represents what is inside the database.
What is the best way to get a list of the duplicate employees using LINQ?
The correct way to do it would be to declare the EmployeeNumber as the table key, then there is not need to check for duplicates.
public class Employee
{
[Key]
public int EmployeeNumber {get; set; }
public string EmployeeName {get; set }
}
Also in your database you would declare the EmployeeNumber as the primary key.
Assuming you are using SQL Server, you can add Identity(1,1) to make it auto-increment.
Here is a sample of how your table definition might look:
CREATE TABLE Persons
(
EmployeeNumber int IDENTITY(1,1) PRIMARY KEY,
EmployeeName varchar(255) NOT NULL,
)
I'm not sure if it's the most efficient (That award goes to #Fahad's comment).
Assuming you really mean "How do I get a list of records that appear in two collections," I like to use the Join or GroupJoin methods, as you can select a new collection, or an anonymous type that contains the record from both collections.
The Syntax is
Join (this collection1, collection2, FuncCollection1Key, Funccollection2Key, FuncOutputSelection).
So if your "new" collection is IEnumerable NewEmployees and your existing collection is IEnumerable<Employee> DbEmployees your collection of duplicate employees is derived by:
var DupeEmployees = NewEmployees.Join(DbEmployees, n=>n.EmployeeNumber, d=>d.EmployeeNumber, (nEmp,dbEmp)=>nEmp);
The two "middle" lambda expressions must be functions that result in the same type of value (that implements IEquatable), but there's no other restriction. You have have two collections of different types, and you can output anything you like.
Now, the best way to do this is Farhad's suggestion, using Join in this case is a bit like shooting a bee with an Elephant gun, but understanding Join will return you many benefits down the road.
You can implement IEqualityComparer and use the LinQ method "Except"
public class MyComparer : IEqualityComparer<Employee>
{
public bool Equals(Employee x, Employee y)
{
return x.EmployeeNumber.Equals(y.EmployeeNumber);
}
public int GetHashCode(Employee x)
{
return x.EmployeeNumber.GetHashCode()
}
}
You can just check whether EmployeeNumber of current employee is available in the list of new employees.
List<Employee> currentEmployees = ...
List<Employee> newEmployees = ...
List<Employee> duplicateEmployees = currentEmployees.Where(currentEmployee => (newEmployees.Select(f => f.EmployeeNumber)).Contains(currentEmployee.EmployeeNumber)).ToList();

NHibernate load the nested complex object entity in automapper queryable extension

I am having class employee which contain complex property Department.
public Class Employee
{
public int Id { get; set;}
public Department {get; set;}
}
public Class Department
{
public int Id {get;set;}
public string Name { get;set;}
}
i create the map for both
(consider the above both class are available in two namespace 'source', 'destination')
Mapper.CreateMap<source.Employee,destination.Employee>()
Mapper.CreateMap<source.Department,destination.Department>()
when i project it.
empQueryable.Project().To<destination.Employee>();
If i saw the NHProfiler
I found that it loads the entity Department
and create the query
select ... from employee left outer join Department .....
i don't know why it loads entity Department, it should make just the projection.
I'm going to go out on a limb here and assume that "destination.Employee" contains references to the "destination.Department". When AutoMapper builds a projection, it does the same as it would as "Mapper.Map". It crawls the destination type, its properties, and its member's properties all the way down. In short, it will build a Select expression something like:
.Select(e => new destination.Employee {
Id = e.Id,
Department = new destination.Department {
Id = e.Department.Id,
Name = e.Department.Name
}
});
You have a few choices here:
Ignore members you don't want mapped in your employee configuration, namely the "Department" property
Create targeted destination types based on use case, and don't share destination types that require different hydrated data based on different needs.
Use explicit expansion (ForMember(d => d.Department, opt => opt.ExplicitExpansion()), then explicitly expand that member in your projection as needed, with the overload for "Project.To" that takes a list of members to expand.

Categories

Resources