I have the following entities:
Applicant : Person
PhoneNumber
PhoneType
The applicant is derived from Person. The Applicant has many PhoneNumbers (up to three). Each PhoneNumber has only 1 PhoneType.
public class Applicant : Person
{
public virtual ICollection<PhoneNumber> PhoneNumbers { get; set; }
}
public class PhoneNumber
{
public int PersonId { get; set; }
[Required]
[MaxLength(17)]
public string Number { get; set; }
public int PhoneTypeId { get; set; }
public virtual PhoneType PhoneType { get; set; }
}
public class PhoneType
{
[Required]
[MaxLength(24)]
public string Name { get; set; }
public virtual ICollection<PhoneNumber> PhoneNumbers { get; set; }
}
It seems to me that the two queries I am using could be merged and the foreach loop eliminated, but I already have a large number of joins in the first query and I am not sure how to merge them. The question is a two part question:
How could I merge the two queries and
If I don't merge the queries, how could I improve upon the PhoneNumber / PhoneType query.
The goal is to return an Applicant with a list of PhoneNumbers, and each PhoneNumber with a PhoneType. For clarity the types are : Home, Office and Mobile.
/*------------------------------------------*/
/* Obtain the Applicant */
/*------------------------------------------*/
IQueryable<Applicant> applicantQuery =
DbContext.Applicants.Where(a => a.CreatedBy == userId)
.Include(applicant => applicant.Address)
.Include(applicant => applicant.Address.StateProvince)
.Include(applicant => applicant.PhoneNumbers)
.Include(applicant => applicant.HomeChurch)
.Include(applicant => applicant.HomeChurch.Address)
.Include(applicant => applicant.HomeChurch.Address.StateProvince)
.Include(applicant => applicant.TripApplication);
Applicant applicant = applicantQuery.FirstOrDefault<Applicant>();
if (applicant != null && applicant.PhoneNumbers != null)
{
IQueryable<PhoneType> phoneTypeQuery = DbContext.Set<PhoneType>();
List<PhoneType> phoneTypes = phoneTypeQuery.ToList<PhoneType>();
foreach (PhoneNumber ph in applicant.PhoneNumbers)
{
ph.PhoneType = (phoneTypes.Where(pt => pt.Id == ph.PhoneTypeId)).First();
}
}
Thank you in advance for any help you might give.
EF Core supports loading multiple levels by combining Include() with ThenInclude(), more info here
To load the Phone along with PhoneTypes your query should look like this (I removed the rest of the relations for clarity):
var applicant = DbContext.Applicants.Where(a => a.CreatedBy == userId)
.Include(applicant => applicant.PhoneNumbers)
.ThenInclude(phone => phone.PhoneType)
.FirstOrDefault();
Related
I have these tables with these relations :
https://i.stack.imgur.com/xUbeu.png
And I wrote these codes :
public class TestData
{
public int EmployeeId { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string FullName { get; set; }
public string Avatar { get; set; }
public bool IsActive { get; set; }
public List<int> Roles { get; set; }
}
public TestData GetData(string email)
{
var employee = _CarRentalContext.Employees.SingleOrDefault(w => w.Email == email);
List<int> Roles = _CarRentalContext.EmployeesRoles
.Where(w => w.EmployeeId == employee.EmployeeId)
.Select(s => s.RoleId).ToList();
return new TestData()
{
EmployeeId = employee.EmployeeId,
FullName=employee.FullName,
Email=employee.Email,
Password=employee.Password,
IsActive=employee.IsActive,
Avatar=employee.Avatar,
Roles = Roles,
};
}
Now what is the best way to write this function?
And if I want to get a list of RoleName instead of RoleId, what should this function look like?
EF defines entities for tables. Your schema has a many-to-many EmployeeRoles table for the association between Employees and Roles, so the entities should look something like this:
public class Employee
{
public int EmployeeId { get; set; }
// ...
public virtual ICollection<Role> Roles { get; set; } = new List<Role>();
// or
// public virtual ICollection<EmployeeRole> EmployeeRoles { get; set; } = new List<EmployeeRole>();
}
If Employee doesn't expose a collection/list of either Role or EmployeeRole then your team needs to read up on using Navigation Properties for relationships. For nearly all relationships like this there is no need to have DbSets in the DbContext for the joining EmployeeRole entity. To populate a TestData DTO you just would need:
var testdata = _CarRentalContext.Employees
.Where(w => w.Email == email)
.Select(w => new TestData
{
EmployeeId = w.EmployeeId,
FullName=w.FullName,
Email=w.Email,
Password=w.Password,
IsActive=w.IsActive,
Avatar=w.Avatar,
Roles = w.Roles.Select(r => new RoleData
{
RoleId = r.RoleId,
Name = r.Name
}).ToList()
}).SingleOrDefault();
If instead the Employee has a collection of EmployeeRoles then it's a little
uglier, replacing the inner Roles= with :
Roles = w.EmployeeRoles.Select(er => new RoleData
{
RoleId = er.Role.RoleId,
Name = er.Role.Name
}).ToList()
... to dive through the EmployeeRole to the Role.
Using Select like that is known as Projection and will let EF build a query to retrieve just the fields about the Employee and associated roles that you need to populate the details. Assuming you want both the ID and Name for each associated role, you would create a simple DTO (RoleData) and use Select within the Employee.Roles to populate from the Role entity/table.
And if I want to get a list of RoleName instead of RoleId, what should this function look like?
.Select(s => s.Role.RoleName)
This question already has answers here:
Create code first, many to many, with additional fields in association table
(7 answers)
Closed 2 years ago.
i m working on EF 6 (mapping with many to many relationship) ,see
https://www.codeproject.com/Articles/234606/Creating-a-Many-To-Many-Mapping-Using-Code-First
where it created "PersonCourses" as middle table ,now i have two problem with that In a many-to-many relationship EF manages the join table internally and hidden. It's a table without an Entity class in your model.
so what if i need to access "PersonCourses" in my code (project) and what if i need to add certain columns with it ??
EF can auto-manage the joining table so long as it just contains the two FKs as a composite PK. If you want to add to that then you need to declare the joining table as an entity with a one-to-many from each side.
So instead of:
[Table("Persons")]
public class Person
{
// ...
public virtual ICollection<Course> Courses { get; set; } = new List<Course>();
}
[Table("Courses")]
public class Course
{
// ...
public virtual ICollection<Person> People { get; set; } = new List<Person>();
}
modelBuilder.Entity<Person>()
.HasMany(x => x.Courses)
.WithMany(x => x.People);
you would have:
[Table("Persons")]
public class Person
{
// ...
public virtual ICollection<PersonCourse> PersonCourses { get; set; } = new List<PersonCourse>();
}
[Table("Courses")]
public class Course
{
// ...
public virtual ICollection<PersonCourse> PersonCourses { get; set; } = new List<PersonCourse>();
}
[Table("PersonCourses")]
public class PersonCourse
{
[Key, Column(Order=0), ForeignKey("Person")]
public int PersonId { get; set; }
[Key, Column(Order=1), ForeignKey("Course")]
public int CourseId { get; set; }
// ... any additional properties for the entity.
public virtual Person Person { get; set; }
public virtual Course Course { get; set; }
}
modelBuilder.Entity<Person>()
.HasMany(x => x.PersonCourses)
.WithRequired(x => x.Person);
modelBuilder.Entity<Course>()
.HasMany(x => x.PersonCourses)
.WithRequired(x => x.Course);
The main disadvantage of this is that Person no longer has a collection of Courses, but of PersonCourses so you have to dive the extra level in your projections every time you want to get details about course names. It might be tempting to leave the PersonCourses collection on Person named as "Courses" but I found that this can get misleading as you can end up with collections on some objects called Persons being Person vs. PersonCourse or Courses being Course vs. PersonCourse. It's generally less confusing when the collection name reflects the type.
So instead of:
var courses = context.Persons
.Where(x => x.PersonId == personId)
.SelectMany(x => x.Courses)
.ToList();
You need to change that to:
var courses = context.Persons
.Where(x => x.PersonId == personId)
.SelectMany(x => x.PersonCourses.Select(pc => pc.Course))
.ToList();
Update: To have an Id column on PersonCourse:
[Table("PersonCourses")]
public class PersonCourse
{
[Key]
public int Id { get; set; }
[ForeignKey("Person")]
public int PersonId { get; set; }
[ForeignKey("Course")]
public int CourseId { get; set; }
// ... any additional properties for the entity.
public virtual Person Person { get; set; }
public virtual Course Course { get; set; }
}
... or better, do away with the FK fields in the entity and map them via configuration:
[Table("PersonCourses")]
public class PersonCourse
{
[Key]
public int Id { get; set; }
// ... any additional properties for the entity.
public virtual Person Person { get; set; }
public virtual Course Course { get; set; }
}
EF6 may map these automatically by convention, but explicitly you can use:
modelBuilder.Entity<Person>()
.HasMany(x => x.PersonCourses)
.WithRequired(x => x.Person)
.Map(x => x.Mpakey("PersonId");
modelBuilder.Entity<Course>()
.HasMany(x => x.PersonCourses)
.WithRequired(x => x.Course)
.Map(x => x.Mpakey("CourseId");
I recommend this approach, like Shadow Properties with EF Core to avoid having 2 sources of truth for the Person ID or Course ID.
I.e. PersonCourse.PersonId vs. PersonCourse.Person.Id
When updating entities with navigation properties, you should update references via the navigation property, (personCourse.Course = newCourse) not via a FK property. (personCourse.CourseId = newCourseId) Doing so, or intermixing the source of truth for the FK can lead to weird results depending on what the DbContext is tracking at the time.
I have the following entities when I generate migration it creates two columns with name RestrictedCategoryId and RestrictedCategoryId1(FK). How to solve this issue to generate only one column with FK?
Note: I need OrderId in each entity.
`C#
public class Order
{
public Guid Id { get; set; }
public DateTime OrderDate { get; set; }
private List<Category> _categories;
public List<Category> Categories => _categories;
}
public class Category
{
public Guid Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public Guid OrderId { get; set; }
public Order Order { get; set; }
private List<RestrictionCategory> _restrictedCategories;
public List<RestrictionCategory> RestrictedCategories => _restrictedCategories;
}
public class RestrictionCategory
{
public Guid Id { get; set; }
public string Name { get; set; }
public Guid OrderId { get; set; }
public Order Order { get; set; }
public Guid CategoryId { get; set; }
public Category Category { get; set; }
public Guid RestrictedCategoryId { get; set; }
public Category RestrictedCategory { get; set; }
}
public class OrderConfiguration : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> builder)
{
builder.HasKey(o => o.Id);
builder.Property(o => o.Id).IsRequired();
}
}
public class CategoryConfiguration : IEntityTypeConfiguration<Category>
{
public void Configure(EntityTypeBuilder<Category> builder)
{
builder.HasKey(c => new { c.Id, c.OrderId });
builder.Property(o => o.Id).IsRequired();
builder.Property(o => o.OrderId).IsRequired();
builder.HasMany(c => c.RestrictedCategories).WithOne(cr => cr.Category)
.HasForeignKey(cr => new { cr.CategoryId, cr.OrderId
}).OnDelete(DeleteBehavior.NoAction);
}
}
public class RestrictionCategoryConfiguration : IEntityTypeConfiguration<RestrictionCategory>
{
public void Configure(EntityTypeBuilder<RestrictionCategory> builder)
{
builder.HasKey(c => new { c.Id, c.OrderId });
builder.Property(o => o.Id).IsRequired();
builder.Property(o => o.OrderId).IsRequired();
builder.HasIndex(cr => new { cr.RestrictedCategoryId, cr.OrderId });
}
}
`
The entities resembles to actual ones.
Actually you get two additional columns:
RestrictedCategoryId = table.Column<Guid>(nullable: false),
RestrictedCategoryId1 = table.Column<Guid>(nullable: true), // <--
RestrictedCategoryOrderId = table.Column<Guid>(nullable: true) // <--
Apparently EF Core Foreign Key Conventions doesn't play well with composite keys, so you have to explicitly configure the relationship - similar to what you did for the other relationship, just since your model has no corresponding collection navigation property you have to use HasMany with generic type argument and no parameters, e.g. inside CategoryConfiguration:
builder.HasMany<RestrictionCategory>()
.WithOne(cr => cr.RestrictedCategory)
.HasForeignKey(cr => new { cr.RestrictedCategoryId, cr.OrderId})
.OnDelete(DeleteBehavior.NoAction);
I have the following tables:
public class Parent
{
[Key]
public long Id { get; set; }
[ForeignKey("ParentId")]
public List<Person> Persons { get; set; }
}
public class Person
{
[Key]
public long Id { get; set; }
public long ParentId { get; set; }
[ForeignKey("PersonId")]
public List<Friend> Friends { get; set; }
}
public class Friend
{
public long PersonId { get; set; }
public long FriendId { get; set; }
[ForeignKey("FriendId")]
public Person Friend { get; set; }
}
The Friend table is a many-to-many relationship between two rows of the Person table. It has a PK composed from the PersonId and the FriendId, declared like this:
modelBuilder.Entity<Friend>(entity =>
{
entity.HasKey(e => new { e.PersonId, e.FriendId });
});
modelBuilder.Entity<Friend>()
.HasOne(e => e.Friend)
.WithMany()
.OnDelete(DeleteBehavior.Restrict);
I want to get all parents with all persons and all their friends, which would look like this:
var entities = context.Parents
.AsNoTracking()
.Include(c => c.Persons)
.ThenInclude(i => i.Friends)
.ToList();
However this does not get the data from the Friends table.
If I inlcude the friends in a person query without the parent it works:
var entities = context.Persons
.AsNoTracking()
.Include(i => i.Friends)
.ToList();
I am using EF Core version 2.2 .
How can I make the first query to retrieve the firends of a person as well and what is causing this behavior?
Ok, so I have a table Building which includes all Persons in that building. However each Person has a profession which is a entity in itself that is added to the Person entity depending what is available.
var data = _dbcontext.Building
.Where(m => m.BuildingId == buildingId)
.Include(x => x.Person).ThenInclude(x => x.Doctor)
.Include(x => x.Person).ThenInclude(x => x.Teacher)
.Include(x => x.Person).ThenInclude(x => x.Farmer)
.Include(x => x.Person).ThenInclude(x => x.Prostitute);
This is how I found to do it online, but it seems to literally be including the Person entity 4 times, so I have tons of duplicates. I really only want it once, but I want to left join the profession entities as available.
The Foreign keys are all setup properly and I used EF to create my models. Here is what the Person entity looks like:
public partial class Person {
public int PersonId { get; set; }
public int? DoctorId { get; set; }
public int? TeacherId { get; set; }
public int? FarmerId { get; set; }
public int? ProstituteId { get; set; }
public int BuildingId { get; set; }
public Doctor Doctor { get; set; }
public Teacher Teacher { get; set; }
public Farmer Farmer { get; set; }
public Prostitute Prostitute { get; set; }
}
var data = _dbcontext.Person
.Where(p => p.BuildingId == buildingId)
.Include(p => p.Building)
.Include(p => p.Doctor)
.Include(p => p.Teacher)
.Include(p => p.Farmer)
.Include(p => p.Prostitute)
.OrderBy(p => p.Building;