Automapper Many to Many - c#

I've been trying to get this working for too long. I've looked at every automapper question I could find, but still no luck.
How do I configure Automapper so that it will properly map my many to many properties between my business entity and my database model?
I'm using a repository pattern using a DB first data model.
I have these entity objects:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public DateTime CreateDate { get { return DateTime.Now; } private set { } }
public List<Department> Departments { get; set; }
public List<Company> Companies { get; set; }
}
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime CreateDate { get { return DateTime.Now; } private set { } }
}
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime CreateDate { get { return DateTime.Now; } private set { } }
}
I need to be able to update these. I need to map them to the db object so I can update the user.
public partial class User{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public Nullable<System.DateTime> CreateDate { get; set; }
public virtual ICollection<UsersCompany> UsersCompanies { get; set; }
public virtual ICollection<UsersDepartment> UsersDepartments { get; set; }
}
public partial class UsersCompany
{
public int Id { get; set; }
public int UserId { get; set; }
public int CompanyId { get; set; }
public virtual Company Company { get; set; }
public virtual User User { get; set; }
}
public partial class UsersDepartment
{
public int Id { get; set; }
public int UserId { get; set; }
public int DepartmentId { get; set; }
public virtual Department Department { get; set; }
public virtual User User { get; set; }
}
I have a method in which I pass the entity and attempt to map it to the db model. This is what I have right now after about 100 different attempts to get automapper to politely map my join tables...with no luck.
private DBUser ToDataModel(User user)
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<User, DBUser>()
.ForMember(dest => dest.UsersDepartments, opt => opt.MapFrom(x => x.Departments));
cfg.CreateMap<User, DBUsersDepartment>()
.ForMember(x => x.User, y => y.MapFrom(z => z));
cfg.CreateMap<Department, DBUsersDepartment>();
});
IMapper mapper = config.CreateMapper();
return mapper.Map<DBUser>(user);
}
This is what I see after it 'maps' (notice no user information has been mapped):

If you still want to know a possible solution to your problem for the future, here are the necessary changes to your code, to get it to work.
While your DTO class User uses the direct related DTOs Department and Company, your DB class USerDb uses the many-many mappings UsersDepartment and UsersCompany.
public class User
{
...
public List<Department> Departments { get; set; }
public List<Company> Companies { get; set; }
}
public class DbUser
{
...
public List<UsersDepartment> UsersDepartments { get; set; }
public List<UsersCompany> UsersCompanies { get; set; }
}
cfg.CreateMap<UserDb, User>()
.ForMember(dest => dest.Departments, opt => opt.MapFrom(user => user.UsersDepartments.Select(userDepartment => userDepartment.Department)))
.ForMember(dest => dest.Companies, opt => opt.MapFrom(user => user.UsersCompanies.Select(userCompany => userCompany.Company)));
cfg.CreateMap<User,DbUser>()
.ForMember(dest => dest.UsersDepartments, opt => opt.MapFrom( user=>user.Departments.Select( department=> new UsersDepartment{UserId=user.Id, DepartmentId=department.Id}))))
.ForMember(dest => dest.UsersCompanies, opt => opt.MapFrom( user=>user.Companies.Select( company=> new UsersCompany{UserId=user.Id, CompanyId=company.Id}))));
I would also remove the additional Id from UsersDepartment and UsersCompany and use a combined key UsersId, DepartmentId or UseId,CompanyId instead.
Hope, that helps.

You are mapping from
public class Department{
public int Id { get; set; }
public string Name { get; set; }
public DateTime CreateDate { get { return DateTime.Now; } private set { }
}
to
public partial class UsersDepartment{
public int Id { get; set; }
public int UserId { get; set; }
public int DepartmentId { get; set; }
public virtual Department Department { get; set; }//No matching property in Department
public virtual User User { get; set; }//No matching property in Department
}
so it is normal that when maping Department→UsersDepartment
public virtual User User { get; set; }
and
public virtual Department Department { get; set; } will be null.
Moreover, you are creating a mapper each time you call private DBUser ToDataModel(User user), which is highly inefficient. Please consider, creating your mappers at the beginning of your application, for example if it is web application in global.asax etc

For anyone interested. I ended up going a different route, where I update each collection separately, then update the base user class.
...now off to the next issue.

Related

EF7 all related entities in ICollection loaded twice

I have two entities, Function and Department with a Many to Many table in between.
public class Department
{
public Department()
{
DepartmentFunctions = new HashSet<DepartmentFunction>();
PersonFunctions = new HashSet<PersonFunction>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<DepartmentFunction> DepartmentFunctions { get; set; }
public DateTime? DeletedAt { get; set; }
}
public class Function
{
public Function()
{
DepartmentFunctions = new HashSet<DepartmentFunction>();
FunctionTaskProfiles = new HashSet<FunctionTaskProfile>();
PersonFunctions = new HashSet<PersonFunction>();
}
public int Id { get; set; }
public string Name { get; set; }
public float? ItemOrder { get; set; }
public virtual Organisation Organisation { get; set; }
public virtual ICollection<DepartmentFunction> DepartmentFunctions { get; set; }
public virtual ICollection<FunctionTaskProfile> FunctionTaskProfiles { get; set; }
public virtual ICollection<PersonFunction> PersonFunctions { get; set; }
public DateTime? DeletedAt { get; set; }
}
public class DepartmentFunction
{
public int FunctionId { set; get; }
public int DepartmentId { get; set; }
public virtual Department Department { get; set; }
public virtual Function Function { get; set; }
}
When the departmentfunction on either entity is loaded, either with eager loading or lazy loading, every unique DepartmentFunction is listed twice. This problem occured after updating from EF core 5 to EF core 7. I have not changed anything with that update.
I have tried explicitly configuring the relation like this:
modelBuilder.Entity<DepartmentFunction>(entity =>
{
entity.HasKey(e => new {FunctieId = e.FunctionId, AfdelingId = e.DepartmentId})
.HasName("PK__Afdeling__591131EC594F97A1");
entity.HasOne(d => d.Department)
.WithMany(p => p.DepartmentFunctions)
.HasForeignKey(d => d.DepartmentId)
.OnDelete(DeleteBehavior.ClientSetNull);
entity.HasOne(d => d.Function)
.WithMany(p => p.DepartmentFunctions)
.HasForeignKey(d => d.FunctionId)
.OnDelete(DeleteBehavior.ClientSetNull);
I have also seen this solution, however, implementing this one would involve changing hundreds of references.

Nested fields in Entity Framework Core: can I fetch everything in one query?

I'm trying to efficiently manipulate complex records with multiple levels of nested fields in C# / ASP.NET Core / Entity Framework Core.
I created a small test app with EF models "Departments > Courses > CourseAuditors".
Here's my query:
public void OnGet()
{
Departments = ctx.Departments.ToList();
foreach (var department in Departments)
{
department.Courses = ctx.Courses
.Where(c => c.DepartmentID == department.ID)
.ToList();
foreach (var course in department.Courses)
{
course.CourseAuditors = ctx.CourseAuditors
.Where(c => c.CourseID == course.ID)
.ToList();
}
}
}
Q: Is there any way I can get rid of the loops, and read everything in one query?
Here are the models:
Department.cs
public class Department
{
public int ID { get; set; }
public string Name { get; set; }
public string DepartmentHead { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
Course.cs
public class Course
{
public int ID { get; set; }
public string Name { get; set; }
public string Instructor { get; set; }
public string Location { get; set; }
public int DepartmentID { get; set; }
public virtual ICollection<CourseAuditor> CourseAuditors { get; set; }
}
CourseAuditor.cs
public class CourseAuditor
{
public int ID { get; set; }
public string StudentName { get; set; }
public int CourseID { get; set; }
}
Our current platform is
TargetFramework=.net5.0;
EntityFrameworkCore=5.0.6 (we'd like to migrate to .NET 6.x soon).
My primary concern is SQL-level efficiency (the fewer SQL-level queries/round trips the better!).
Any advice/suggestions would be welcome!
First of all you should extend your models with proper relations:
public class CourseAuditor
{
public int ID { get; set; }
public string StudentName { get; set; }
public int CourseID { get; set; }
public Course Course { get; set; } // this property is missing
}
public class Course
{
public int ID { get; set; }
public string Name { get; set; }
public string Instructor { get; set; }
public string Location { get; set; }
public int DepartmentID { get; set; }
public Department Department { get; set; } // this property is missing
public virtual ICollection<CourseAuditor> CourseAuditors { get; set; }
}
Then in your DbContext method OnModelCreating(ModelBuilder modelBuilder) you should create relations like so:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Course>()
.HasMany(c => c.CourseAuditors)
.WithOne(ca => ca.Course)
.HasForeignKey(ca => ca.CourseID);
modelBuilder.Entity<Department>()
.HasMany(d => d.Courses)
.WithOne(c => c.Department)
.HasForeignKey(d => d.DepartmentID);
}
After this setup you can now use SQL Join feature - .Include(...) in Entity Framework.
Changed method OnGet():
public void OnGet()
{
Departments = ctx.Departments
.Include(d => d.Courses)
.ThenInclude(c => c.CourseAuditors)
.ToList();
}
Lastly I would suggest changing naming of properties.
DepartmentID -> DepartmentId
ID -> Id
etc.
There's the convention in C# to not capitalize full texts.

Define relationship in EF Core

How do I define the relationships here with EF Core?
I have an Employee table which has multiple Jobs
public class Employee
{
public int EmployeeId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<HourlyRate> DefaultRate { get; set; }
public string Note { get; set; }
public bool Active { get; set; }
public DateTime DateHired { get; set; }
public List<PhoneNumber> PhoneNumbers { get; set; }
public List<Address> Addresses { get; set; }
public List<Job> Jobs { get; set; }
public bool Deleted { get; set; }
}
And the Job class has an Employee object to navigate back to the employee and the Job has multiple Directors which are also Employees
public class Job
{
public int JobId { get; set; }
public Employee Employee { get; set; }
public JobType Type { get; set; }
public Department Department { get; set; }
public List<Employee> Directors { get; set; }
public bool Active { get; set; }
public decimal HourlyRate { get; set; }
public string Note { get; set; }
public bool Deduction { get; set; }
public int? DeductionPercent { get; set; }
}
This is my DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Employee>()
.HasMany(employee => employee.Jobs)
.WithOne(i => i.Employee);
}
Initially the Job only had a single Director and everything was good but the requirement has changed to have multiple directors and ef removed the Director column from the Job table and added a JobId column to the Employee table but the problem is that if i add that director to a second job by job.Directors.Add(director) EF overrides the job id of the of the director and the director is being removed from the previous job
I am using EF Core 2.2
if a Job has only 1 Employee but multiple Directors (also Employee)
add public int EmployeeId {get; set;} to your Job class and add this
modelBuilder
.Entity<Job>()
.HasMany(p => p.Directors)
.WithMany(p => p.Jobs));
also, change List<> to ICollection<>
You should tell EF through fluent API that there's a 1-to-many relationship from Employee to Job. Otherwise, EF may get confused.
The many-to-many relationship needs a junction table and matching entity in the model which you'll also need to configure through fluent API. You'll define two 1-to-many relationships from Employee and Job to that new entity. EF core does not directly support such relationships before 5.0.
If you are targeting SQL, then you need to mark at least one of the relationships as OnDelete(CascadeBehavior.NoAction). Otherwise, your model will generate invalid table defintions which will raise errors at creation time.
Update:
The junction table would be defined something like this.
public class Employee
{
// ... other stuff
public List<EmployeeJob> EmployeeJobs { get; set; }
}
public class Job
{
// ... other stuff
public List<EmployeeJob> EmployeeJobs { get; set; }
}
public class EmployeeJob
{
public int EmployeeId { get; set; }
public int JobId { get; set; }
public Employee Employee { get; set; }
public Job Job { get; set; }
}
// goes in DbContext
modelBuilder.Entity<EmployeeJob>.HasKey(x => new { x.EmployeeId, x.JobId });
Try to use this code. Since your employee can have one or many jobs I added the table EmployeeJob and many-to-many relations. I think you just need to add IsDirector flag to Employee or maybe better something like an EmployeeType:
public class Employee
{
public Employee()
{
EmployeeJobs = new HashSet<EmployeeJob>();
}
[Key]
public int EmployeeId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Note { get; set; }
public bool Active { get; set; }
public DateTime DateHired { get; set; }
public bool Deleted { get; set; }
[InverseProperty(nameof(EmployeeJob.Employee))]
public virtual ICollection<EmployeeJob> EmployeeJobs { get; set; }
}
public class Job
{
public Job()
{
EmployeeJobs = new HashSet<EmployeeJob>();
}
[Required]
public int JobId { get; set; }
public bool Active { get; set; }
public decimal HourlyRate { get; set; }
public string Note { get; set; }
public bool Deduction { get; set; }
public int? DeductionPercent { get; set; }
[InverseProperty(nameof(EmployeeJob.Job))]
public virtual ICollection<EmployeeJob> EmployeeJobs { get; set; }
}
public class EmployeeJob
{
[Key]
public int Id { get; set; }
public int EmployeeId { get; set; }
[ForeignKey(nameof(EmployeeId))]
[InverseProperty(nameof(EmployeeJob.Employee.EmployeeJobs))]
public virtual Employee Employee { get; set; }
public int JobId { get; set; }
[ForeignKey(nameof(JobId))]
[InverseProperty(nameof(EmployeeJob.Employee.EmployeeJobs))]
public virtual Job Job { get; set; }
}
public class EmployeeDbContext : DbContext
{
public EmployeeDbContext()
{
}
public EmployeeDbContext(DbContextOptions<EmployeeDbContext> options)
: base(options)
{
}
public DbSet<Employee> Employees { get; set; }
public DbSet<Job> Jobs { get; set; }
public DbSet<EmployeeJob> EmployeeJobs { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Server=localhost;Database=Employee;Trusted_Connection=True;");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<EmployeeJob>(entity =>
{
entity.HasOne(d => d.Employee)
.WithMany(p => p.EmployeeJobs)
.HasForeignKey(d => d.EmployeeId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_EmployeeJob_Employee");
entity.HasOne(d => d.Job)
.WithMany(p => p.EmployeeJobs)
.HasForeignKey(d => d.JobId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_EmployeeJob_Job");
});
}

Entity Framework duplicated column issue

I have a basic model like that :
public class Account
{
public int Id { get; set; }
public string Name { get; set; }
public List<Contact> Contacts { get; set; }
public Contact PrincipalContact { get; set; }
public int? PrincipalContactId { get; set; }
}
public class Contact
{
public int Id { get; set; }
public string Name { get; set; }
public Account Account { get; set; }
public int? AccountId { get; set; }
}
Entity Framework creates two columns on the table Contacts: Account_Id and AccountId.
The column AccountId is always null. I don't know why I have this behavior
What is the proper way to achieve this with Entity Framework ? I tried to add the [ForeignKey] attribute but it doesn't change anything.
Thank you.
In the case of one-to-one relationships, you will need to provide some additional
information so that Code First knows which entity is the principal and which is
the dependent.
public class Account
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public List<Contact> Contacts { get; set; }
[ForeignKey("PrincipalContact")]
public int? PrincipalContactId { get; set; }
public virtual Contact PrincipalContact { get; set; }
}
public class Contact
{
[Key]
[ForeignKey("AccountOf")]
public int Id { get; set; }
public string Name { get; set; }
public virtual Account AccountOf { get; set; }
}
The Account_Id column is created automatically by EF based in the one-to-many relationship. If you don't specify nothing, by convention, EF will recognize the fact that your Navigation property is called Account and your ForeignKey will be called AccountId, but due to you have a property with the same name, EF changes it to Account_Id.
To create both relationships that you need I recommend you modify the model as I show below:
public class Account
{
public int Id { get; set; }
public string Name { get; set; }
public List<Contact> Contacts { get; set; }
public Contact PrincipalContact { get; set; }
}
public class Contact
{
public int Id { get; set; }
public string Name { get; set; }
public Account Account { get; set; }
public int? AccountId { get; set; }
}
Then, In your Context you can configure the relationships explicitly using Fluent Api.
public class YourContext : DbContext
{
public IDbSet<Account> Accounts { get; set; }
public IDbSet<Contact> Contacts { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Contact>()
.HasOptional(c => c.Account)
.WithMany(e => e.Contacts)
.HasForeignKey(a => a.AccountId);
modelBuilder.Entity<Account>()
.HasOptional(c => c.PrincipalContact)
.WithMany()
.Map(c => c.MapKey("PrincipalContactId"));
}
}
Update
If you want to keep the PrincipalContactId property on the Account class, you should map the relationship this way:
modelBuilder.Entity<Account>()
.HasOptional(c => c.PrincipalContact)
.WithMany().HasForeignKey(a => a.PrincipalContactId);

Managing two relationships between 2 entities

I have two entities: UserProfile and Inbox
This is my code for UserProfile:
public partial class UserProfile {
public int Id { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
//--------------------------
public virtual ICollection<Messages.Inbox> Inboxes { get; set; }
}
public partial class UserProfile {
public static void Configure(DbModelBuilder mb) {
mb.Entity<UserProfile>().HasKey(up => up.Id);
}
}
And this is my code for Inbox:
public partial class Inbox {
public int Id { get; set; }
public int UserId { get; set; }//FK to userProfile
public Accounts.UserProfile User { get; set; }
public DateTime MessageDateTime { get; set; }
public string Context { get; set; }
public int SenderId { get; set; }//FK to userProfile
public Accounts.UserProfile Sender { get; set; }
}
public partial class Inbox {
public static void Configure(DbModelBuilder mb) {
mb.Entity<Inbox>().HasKey(up => up.Id);
mb.Entity<Accounts.UserProfile>().HasMany(up => up.Inboxes)
.WithRequired(p => p.User)
.HasForeignKey(p => p.UserId);
mb.Entity<Accounts.UserProfile>().HasMany(up => up.Inboxes)
.WithRequired(p => p.Sender)
.HasForeignKey(p => p.SenderId);
}
}
How can I manage these two relations between Inbox and UserProfile?
If you have a DTO that you expose to the user/ui to add inbox, that dto should not include User/Sender properties, just the Ids SenderId/UserId, but keep them in the Inbox entity, as you need to load some details about the User/Sender from the inbox entity, so instead of joining with UserProfile table, you can include the it using Entity-framework. hope that helps.

Categories

Resources