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.
Related
In relation 1 - 1, I have two foreign keys in each of tabels that should tell me to what object I', referring. In table "WebAppUser" foreign key works correctly but in table "UserPermissions" I always get 0 as id of foreign key (column: "WebAppUserId" which is reference to a specific "WebAppUser").
My Code:
public class UserPermissions
{
[Key]
public int UserPermissionsId { get; set; }
//public int? WebAppUserId { get; set; }
public int WebAppUserId { get; set; }
public virtual WebAppUser WebAppUser { get; set; }
/*public virtual IEnumerable<WebAppUserClaims> Claims { get; set; }
public virtual IEnumerable<WebAppUserLogin> Logins { get; set; }
public virtual IEnumerable<WebAppUserToken> Tokens { get; set; }*/
public virtual IEnumerable<WebAppUserRole> WebAppUserRoles { get; set; }
}
public class WebAppUser /*: IdentityUser<int>*/
{
[Key]
public int Id { get; set; }
//1:1
public int UserProfileId { get; set; }
public virtual UserProfile UserProfile { get; set; }
//1:1
public int UserCredentialsId { get; set; }
public virtual UserCredential Credentials { get; set; }
//1:1
public int PermissionsId { get; set; }
public virtual UserPermissions UserPermissions { get; set; }
}
//WebAppUser - UserPermissions 1-1
modelBuilder.Entity<WebAppUser>()
.HasOne(x => x.UserPermissions)
.WithOne(y => y.WebAppUser)
.HasForeignKey<WebAppUser>(x => x.PermissionsId);
I tried entity configuration like this, but it also doesn't work:
modelBuilder.Entity<WebAppUser>()
.HasOne(x => x.UserPermissions)
.WithOne(y => y.WebAppUser)
.HasForeignKey<WebAppUser>(x => x.PermissionsId);
modelBuilder.Entity<UserPermissions>()
.HasOne(x => x.WebAppUser)
.WithOne(y => y.UserPermissions)
.HasForeignKey<UserPermissions>(x => x.WebAppUserId);```
Try this change in fluentApi
modelBuilder.Entity<WebAppUser>()
.HasOne<UserPermissions>(x => x.UserPermissions)
.WithOne(y => y.WebAppUser)
.HasForeignKey<UserPermissions>(x => x.WebAppUserId);
And don't forget when you make a request to this table to use Include(x => x.UserPermissions)
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.
The problem is that when the method runs, it creates rows for the CariHareket table, no matter how many rows of the tables linked to the Kontrat.
I can't group by because EF Core doesn't support it (EF Core v3.1.9)
hareketService.GetAll(Predicate())//table name is CariHareket
.Include(s => s.Cari)
.Include(s => s.HedefHareketCariVirman)
.Include(s => s.HareketCari)
.Include(s => s.Kontrat).ThenInclude(s => s.KontratKalemler)
.Include(s => s.Kontrat).ThenInclude(s => s.KontratTarihBaglantilar)
.Include(s => s.Kontrat).ThenInclude(s => s.FaturaTalimativeTurkceFatura)
.Include(s => s.Kontrat).ThenInclude(s => s.AraciCari)
CariHareket and Kontrat model classes:
public class CariHareket
{
[Key]
public int ID { get; set; }
[ForeignKey(nameof(CariID))]
public virtual Cariler Cari { get; set; }
public int? CariID { get; set; }
[ForeignKey(nameof(KontratID))]
public virtual KontratUst Kontrat { get; set; }
public int? KontratID { get; set; }
[ForeignKey(nameof(HareketCariID))]
public virtual Cariler HareketCari { get; set; }
public int? HareketCariID { get; set; }
[ForeignKey(nameof(HedefHareketCariVirmanID))]
public virtual Cariler HedefHareketCariVirman { get; set; }
public int? HedefHareketCariVirmanID { get; set; }
...
}
public class Kontrat
{
...
[InverseProperty("Kontrat")]
public virtual BindingList<KontratMasraflarNotlar> KontratMasraflarNotlar { get; set; }
[InverseProperty("Kontrat")]
public virtual BindingList<KontratKalem> KontratKalemler { get; set; }
[InverseProperty("Kontrat")]
public virtual BindingList<KontratTarihBaglantilar> KontratTarihBaglantilar { get; set; }
[InverseProperty("Kontrat")]
public virtual BindingList<KontratKasa> KontratKasa { get; set; }
[InverseProperty("Kontrat")]
public virtual BindingList<KontratKonteyner> KontratKonteyner { get; set; }
[InverseProperty("Kontrat")]
public virtual BindingList<KontratTurkceFaturaKalem> FaturaTalimativeTurkceFatura { get; set; }
}
Worked a lot with EF 6.x (via designer) and now started on a new project using EF Core.
I'm getting an error that says value cannot be null, not sure exactly what I'm doing wrong. I've got rid of a lot of fields for brevity as there are hundreds.
All these tables are views via synonyms that connect to a different database. I can get it to work fine, if I do each individual call to a database, but as soon as I do include. I get an error on that line. The error I'm getting is
ArgumentNullException: Value cannot be null.
Parameter name: key
System.Collections.Generic.Dictionary.FindEntry(TKey key)
OnGetAsync
var equipment = _context.EMEMs.Include(x => x.EMEDs).Where(x => x.KeyID.ToString() == key);
EMEM = await equipment.Include(x => x.EMCM).ThenInclude(x=>x.EMCDs).FirstOrDefaultAsync();
EMEM
public class EMEM
{
public byte? EMCo { get; set; }
[Display(Name = "Equipment Code")]
public string Equipment { get; set; }
public string Type { get; set; }
public string Category { get; set; }
public Guid? UniqueAttchID { get; set; }
[Key]
public long KeyID { get; set; }
[NotMapped] public string EquipmentDetails => $"{Equipment.Trim()} - {Description} - {VINNumber}";
public virtual IEnumerable<EMWH> EMWHs { get; set; }
public virtual EMCM EMCM { get; set; }
public virtual IEnumerable<udEMED> EMEDs { get; set; }
}
EMCM
public class EMCM
{
[Key]
public long KeyID { get; set; }
public byte? EMCo { get; set; }
public string Category { get; set; }
public string Description { get; set; }
public string Notes { get; set; }
public virtual IEnumerable<EMEM> EMEMs { get; set; }
public virtual IEnumerable<udEMCD> EMCDs { get; set; }
}
udEMCD
public class udEMCD
{
[Key]
public long KeyID { get; set; }
public byte? Co { get; set; }
public string Category { get; set; }
public string DocumentCategory { get; set; }
public int Seq { get; set; }
public Guid? UniqueAttchID { get; set; }
public virtual udEMDC EMDC { get; set; }
public virtual EMCM EMCM { get; set; }
public virtual IEnumerable<HQAT> HQATs { get; set; }
}
Context
modelBuilder.Entity<EMEM>().ToTable("EMEM").HasOne(x => x.EMCM).WithMany(x => x.EMEMs).HasForeignKey(x => new { x.EMCo, x.Category }).HasPrincipalKey(x => new { x.EMCo, x.Category });
modelBuilder.Entity<EMEM>().ToTable("EMEM").HasMany(x => x.EMEDs).WithOne(x => x.EMEM).HasForeignKey(x => new { x.Co, x.Equipment }).HasPrincipalKey(x => new { x.EMCo, x.Equipment });
modelBuilder.Entity<EMCM>().ToTable("EMCM").HasMany(x => x.EMCDs).WithOne(x => x.EMCM)
.HasForeignKey(x => new { x.Co, x.Category }).HasPrincipalKey(x => new { x.EMCo, x.Category });
modelBuilder.Entity<udEMCD>().ToTable("udEMCD").HasOne(x => x.EMDC).WithMany(x => x.EMCDs)
.HasForeignKey(x => x.DocumentCategory).HasPrincipalKey(x => x.Category);
modelBuilder.Entity<udEMDC>().ToTable("udEMDC").HasMany(x => x.EMEDs).WithOne(x => x.EMDC).HasForeignKey(x => new{ x.DocumentCategory}).HasPrincipalKey(x => new{ x.Category});
modelBuilder.Entity<udEMED>().ToTable("udEMED");
modelBuilder.Entity<EMWH>().ToTable("EMWH");
modelBuilder.Entity<EMWI>().ToTable("EMWI");
modelBuilder.Entity<HQAT>().HasOne(x => x.EMWH).WithMany(x => x.HQATs).HasForeignKey(x => x.UniqueAttchID)
.HasPrincipalKey(x => x.UniqueAttchID);
modelBuilder.Entity<EMWH>().HasOne(x => x.EMEM).WithMany(x => x.EMWHs)
.HasForeignKey(x => new {x.EMCo, x.Equipment}).HasPrincipalKey(x => new {x.EMCo, x.Equipment});
EDIT: I added nullable KeyID's just to test prior to uploading and still didn't work.
I think the error is that you're declaring the Key as nullable, which it should never happen.
[Key]
public long? KeyID { get; set; }
change your code to this...
[Key]
public long KeyID { get; set; }
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.