How to query many to many relation using Entity Framework - c#

I have the following models in my application:
public class Employee
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int Benefits { get; set; }
}
public class Department
{
public int DeptID { get; set; }
public string DeptName { get; set; }
}
public class DeptEmp
{
public int PersonID { get; set; }
public int DeptID { get; set; }
}
I want to create a query, using Entity Framework, to select all columns from employee with a condition that it retrieves only those employees that PersonId has a relation with DeptId in the DeptEmp class and DepartId from Department has a relation with DeptId in the DeptEmp.
I have written the following LINQ statement:
var selectEmployees = from e in Employee
join d in DeptEmp on e.PersonId equals d.PersonId
join dd in Depatment on d.DeptId equals dd.DeptId
select new
{
e.FirstName,
e.LastName,
e.Benefits
};
but it is not working. Am I missing anything?

Entity framework works on a "use standards or else" basis. It is fairly easy if you use standards, if not you have to provide lots of information about your deviations.
For instance, entity framework expects a primary key of Employee as Id or EmployeeId. If you decide to use a different primary key (PersonId), you'll have to tell entity framework that this is your primary key.
The same is with your many-to-many relationship. If you use the defaults it is fairly easy, otherwise you'll need attributes or fluent API to inform about the deviations from the defaults.
Default many-to-many in your Employee / Department model would be:
See also Entity Framework Tutorial Configure many-to-many
public class Employee
{
public int EmployeeId{ get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int Benefits { get; set; }
// an employee has many departments:
public virtual ICollection<Department> Departments { get; set; }
}
public class Department
{
public int DeptartmentId { get; set; }
public string DeptName { get; set; }
// an department has many employees
public virtual ICollection<Employee> Employees{ get; set; }
}
public MyDbContext : DbContext
{
public DbSet<Employee> Employees {get; set;}
public DbSet<Department> Departments {get; set;}
}
If you make a simple console application with these classes you'll see that it creates also a many-to-many table. You'll seldom need this table, but if you really need it, you could add it to the DbContext.
I want ... to select all columns from employee with the condition that it retrieves only those employees that PersonId has a relation with DeptId
I assume that this means that given a DeptId you want all properties from all employees working in this DeptId:
using (var dbContext = new MyDbContext(...))
{
var myDepartment = dbContext.Departments
.Where(department => department.DepartmentId == DeptId)
.SingleOrDefault();
// I know there is at utmost one, because it is a primary key
if (myDepartment == null) ShowDepartmentMissing(...);
var employeesOfDepartment = myDepartment.Employees
.Select(employee => new
{
FirstName = employee.FirstName,
LastName = employee.LastName,
Benefits = employee.Benefits,
});

Related

Insert record in many-to-many when key is DatabaseGeneratedOption.None [duplicate]

When inserting data into a many-to-many relationship, should you insert to the join-table or to both original tables?
My table models:
public DbSet<User> Users { get; set; }
public DbSet<Group> Groups { get; set; }
public DbSet<GroupMember> GroupMembers { get; set; }
The relationship between them is configured with Fluent API:
builder.Entity<GroupMembers>().HasKey(gm => new { gm.UserId, gm.GroupId });
builder.Entity<GroupMembers>().HasOne(gm => gm.Group).WithMany(group => group.GroupMembers).HasForeignKey(gm => gm.GroupId);
builder.Entity<GroupMembers>().HasOne(gm => gm.User).WithMany(user => user.GroupMembers).HasForeignKey(gm => gm.UserId);
public class Group
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
public List<GroupMember> GroupMembers { get; set; } = new List<GroupMembers>();
}
public class User
{
[Key]
public Guid Id { get; set; }
public string Username { get; set; }
public List<GroupMembers> GroupMembers { get; set; } = new List<GroupMembers>();
}
public class GroupMembers
{
[Key]
public Guid UserId { get; set; }
public User User { get; set; }
[Key]
public Guid GroupId { get; set; }
public Group Group { get; set; }
}
Now, the question is; in which tables/classes should I insert the data about the group members?
Is it like this:
GroupMembers groupMember = new GroupMembers
{
Group = group,
GroupId = group.Id,
User = user,
UserId = user.Id
};
user.GroupMembers.Add(groupMember);
group.GroupMembers.Add(groupMember)
_databaseContext.Users.Update(user);
_databaseContext.SaveChanges();;
_databaseContext.Groups.Update(group);
_databaseContext.SaveChanges();
Or like this, leaving the User and Group untouched, with the information about their relationship ONLY in the join-table:
GroupMembers groupMember = new GroupMembers
{
Group = group,
GroupId = group.Id,
User = user,
UserId = user.Id
};
_databaseContext.GroupMembers.Add(groupMember);
_databaseContext.SaveChanges();
As far as Entity Framework is concerned, this is not a many-to-many relationship
What you have here is three entity types with two one-to-many relationships defined between them. You might know that this is done to represent a many-to-many, but EF doesn't know that.
If I arbitrarily change the names of your entities while maintaining the structure, you wouldn't be able to tell if this was a many-to-many relationship or not.
Simple example:
public class Country {}
public class Company {}
public class Person
{
public int CountryOfBirthId { get; set; }
public virtual Country CountryOfBirth { get; set; }
public int EmployerId { get; set; }
public virtual Company Employer { get; set; }
}
You wouldn't initially think of Person as the represenation of a many-to-many relationship between Country and Company, would you? And yet, this is structurally the same as your example.
Essentially, your handling of your code shouldn't be any different from how you handle any of your one-to-many relationships. GroupMembers is a table (db set) like any else, and EF will expect you to treat it like a normal entity table.
The only thing that's different here is that because GroupMember has two one-to-many relationships in which it is the "many", you therefore have to supply two FKs (one to each related entity). But the handling is exactly the same as if you had only one one-to-many relationship here.
In other words, add your groupMember to the table itself:
GroupMembers groupMember = new GroupMembers
{
// You don't have to fill in the nav props if you don't need them
GroupId = group.Id,
UserId = user.Id
};
_databaseContext.GroupMembers.Add(groupMember);
_databaseContext.SaveChanges();
Note: The following only applies to non-Core Entity Framework, as EF Core does not yet support it.
An example of what would be a "real" many-to-many relationship in (non-Core) EF would be if the intermediary table was not managed by you, i.e.:
public class MyContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Group> Groups { get; set; }
}
public class Group
{
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public class User
{
[Key]
public Guid Id { get; set; }
public string Username { get; set; }
public virtual ICollection<Group> Groups { get; set; }
}
In this scenario, EF will still generate the cross table in the database, but EF will hide this table from you. Here, you are expected to work via the nav props:
var user = myContext.Users.First();
var group = myContext.Groups.First();
user.Groups.Add(group);
myContext.SaveChanges();
Whether you use a "real" many-to-many relationship or manage the cross table yourself is up to you. I tend to only manage the cross table myself when I can't avoid it, e.g. when I want additional data on the cross table.
Make sure the data id is correct and Exists
GroupMembers groupMember = new GroupMembers
{
GroupId = group.Id,
UserId = user.Id
};
_databaseContext.GroupMembers.Add(groupMember);
_databaseContext.SaveChanges();
There is less line of code and you have to assume that the object is completely independent when inserted

EF6(not EF core) using Navigation property instead of Linq joins

I have two tables that I need to join and filter. Orders and Customers. I have generated these classes using EF Code First from DB.
Generated classes for the tables -
Orders
[Table("Orders")]
public partial class Orders
{
[Key]
[StringLength(17)]
public string OrderID { get; set; }
public int ShipToCustomerID { get; set; }
//Navigation Property
public Customer Customer { get; set; }
}
Customers
[Table("Customer")]
public partial class Customer
{
public int CustomerID { get; set; }
public string AccountNumber { get; set; }
//Navigation prop
public int ShipToCustomerID { get; set; } (not a part of the table, just attempting to get the navigation work)
public Orders Order { get; set; }
}
Method 1:
LINQ Joins
using (var context = new OrderDetailsGeneral1())
{
var data = (from p in context.Orders
join q in context.Customers
on p.ShipToCustomerID equals q.CustomerID
where p.OrderID == "7150615"
select new
{
OrderID = p.OrderID,
CustomerID = q.AccountNumber
}
);
var orders = data.ToList();
return Json(orders);
}
This works well and I get the following output -
[
{
"OrderID": "7150615",
"CustomerID": "23320347 "
}
]
Method 2:
I read that it's better to use navigation properties than using joins and that's why I was trying to do so, as per that I added the navigation properties to the classes above.
I tried a bunch of ways to link them together. One of them is the way mentioned here and I came across a bunch of errors.
It would try to map Customers.CustomerID to Orders.OrderID instead of Orders.ShipToCustomerID.
What's the best way to achieve this? I am having a hard time figuring out linking this foreign key (Customers.CustomerID) to a non primary/alternate key (Orders.ShipToCustomerID)
you have to fix your classes
[Table("Orders")]
public partial class Order
{
[Key]
[StringLength(17)]
public string OrderID { get; set; }
public int ShipToCustomerID { get; set; }
[ForeignKey(nameof(ShipToCustomerID))]
[InverseProperty("Orders")]
public virtual Customer Customer { get; set; }
}
[Table("Customer")]
public partial class Customer
{
[Key]
public int CustomerID { get; set; }
public string AccountNumber { get; set; }
[InverseProperty(nameof(Order.Customer))]
public virtual ICollection<Order> Orders { get; set; }
}

Query joined tables c# code first

I have these tables i have made in c# using code first approach.
Employee class:
public int id { get; set; }
public string name { get; set; }
Department class:
public int id { get; set; }
public string deptName { get; set; }
public IQueryable<Employee> { get; set; }
This generates a DepartmentID in my Employee table in my sql database. I cannot however access this field in c# as DepartmentID is not a field in the employee class/model.
My question is how do i access this variable. I wish to do some various joins etc but am struggling with this.
You can certainly expose the foreign key, but it is not necessarily needed. The beauty of EF is you don't need joins.
First I would clean up your classes:
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
// Exposed FK. By convention, EF know this is a FK.
// EF will add one if you omit it.
public int DepartmentID { get; set; }
// Navigation properties are how you access the related (joined) data
public virtual Department Department { get; set; }
}
public class Department
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
}
Now you can query your data easily:
var employeeWithDepartment = context.Employee
.Include(e => e.Department)
.FirstOrDefault(e => e.ID = 123);
var employeeName = employeeWithDepartment.Name;
var departmentName = employeeWithDepartment.Department.Name;
... etc.
var departmentWithListOfEmployees = context.Departments
.Include(d => d.Employees)
.Where(d => d.Name == "Accounting")
.ToList();
... build table or something
foreach (var employee in departmentWithListOfEmployees.Employees)
{
<tr><td>#employee.ID</td><td>#employee.Name</td>
}
... close table

InverseProperty Entity Framework 7

I am facing a problem with EF7 inverse property. There are two entities that are connected like this.
public class Employee
{
public int Id { get; set; }
}
public class Review
{
public int Id { get; set; }
[Required]
public virtual Employee Employee { get; set; }
[Required]
public Employee Manager { get; set; }
}
I want to access a list of the reviews when I start to query my employees, so I tried to do this:
public class Employee
{
public Employee()
{
Reviews = new List<Review>();
}
public int Id { get; set; }
[InverseProperty("Employee")]
public virtual ICollection<Review> Reviews { get; set; }
}
With this, the query is not well made and return this error:
Invalid column name 'EmployeeId1'.
This is the part of the query where is the error:
SELECT [ua].[Id], [r].[EmployeeId], [r].[EmployeeId1], [r1].[EmployeeId], [r1].[EmployeeId1]
FROM [UserAssessment] AS [ua]
LEFT JOIN [Review] AS [r] ON [ua].[ReviewId] = [r].[Id]
LEFT JOIN [Review] AS [r1] ON [ua].[ReviewId] = [r1].[Id]
Anyone know what can I do?
UPDATE
This statement is generating the query:
return this.DbSet
.Include(ua => ua.Employee).ThenInclude(t => t.Role)
.Include(ua => ua.Review).ThenInclude(rt => rt.ReviewType)
.Include(ua => ua.Review).ThenInclude(rt => rt.Manager).ThenInclude(r => r.Role)
I have to access with those same includes because lazy loading is not available on EF7 yet.
You need the InverseProperty on both the Employee and Review
public class Review
{
public int Id { get; set; }
[Required]
[InverseProperty("Reviews")]
public Employee Employee { get; set; }
[Required]
public Employee Manager { get; set; }
}
public class Employee
{
public int Id { get; set; }
[InverseProperty("Employee")]
public ICollection<Review> Reviews { get; set; }
}
Should work. I have a similar setup where it creates the navigation without creating any new fields. If this doesn't work let me know and I'll spin up a test project.
Also note, that EF7 currently ignores virtual and this does not have meaning like it did in EF6.

How to remove rows for deleted fields from relationship table in entity framework

I have the Student, Course and a relationship between them as StudentCourse. The fields in these classes are as follows:
public class Student
{
public int StudentId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int CourseId { get; set; }
[ForeignKey("CourseId")]
public Course Course { get; set; }
}
public class Course
{
public int CourseId { get; set; }
public string CourseName { get; set; }
}
public class StudentCourse
{
public int ID { get; set; }
public virtual Student Student { get; set; }
public virtual Course Course {get;set;}
}
When I delete the students in student table , then I want to remove the corresponding rows from the relationship StudentClass. How can I do it?
I believe that you actually want a many-to-many relationship between Student and Course: A student can participate in many courses and a course can have many students.
In this case you can simplify your model:
public class Student
{
public int StudentId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<Course> Courses { get; set; }
}
public class Course
{
public int CourseId { get; set; }
public string CourseName { get; set; }
public ICollection<Student> Students { get; set; }
}
The "join entity" StudentCourse is not needed. EF will create three tables from this model: A Students table, a Courses table and StudentCourses (or CourseStudents) table that will have a composite primary key (StudentId, CourseId) (or named similar). Both parts are foreign keys to their respective tables.
For the two FK relationships in the database cascading delete will be turned on by default. So, if a Student gets deleted the link records in the join table will be deleted automatically. The same when a Course gets deleted.
You can also define the detailed names for join table and join table columns explicity and you can also work with only a single collection, for example only the Courses collection in Student but without the Students collection in Course. You must use Fluent API for this:
modelBuilder.Entity<Student>()
.HasMany(s => s.Courses)
.WithMany() // no parameter if there is no collection in Course
.Map(m =>
{
m.MapLeftKey("StudentId");
m.MapRightKey("CourseId");
m.ToTable("StudentCourses");
});

Categories