EF Include and ThenInclude - entity-framework

I have a few models I am trying to bind with Include which is not returning back all expected related data. The full chain is:
User (one) > Role (one) > Permissions (Many) > Entity (One) > EntityArea (One)
These are my models: (CompanyBase is a base class with a companyId in it)
public class User : _CompanyBase
{
public int UserID { get; set; }
public string FullName { get; set; }
public int RoleID { get; set; }
public Role Role { get; set; }
}
public class Role : _CompanyBase
{
[Key, Column(Order = 1), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int RoleID { get; set; }
[Required]
[StringLength(100, MinimumLength = 3)]
public string Name { get; set; }
public ICollection<RolePermission> RolePermissions { get; set; }
public ICollection<User> Users { get; set; }
}
public class RolePermission : _CompanyBase
{
[Key, Column(Order = 1), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int RolePermissionID { get; set; }
public Guid Id { get; set; }
[Required]
[StringLength(100, MinimumLength = 3)]
public string PermissionCode { get; set; }
public int RoleID { get; set; }
public Role Role { get; set; }
public int EntityID { get; set; }
public Entity Entity { get; set; }
}
public class Entity : _CompanyBase
{
[Key, Column(Order = 1), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int EntityID { get; set; }
[Required]
[StringLength(100, MinimumLength = 3)]
public string DisplayName { get; set; }
public int EntityAreaID { get; set; }
public EntityArea EntityArea { get; set; }
}
public class EntityArea :_CompanyBase
{
[Key, Column(Order = 1), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int EntityAreaID { get; set; }
[Required]
[StringLength(50, MinimumLength = 3)]
public string Name { get; set; }
public ICollection<Entity> Entities { get; set; }
}
And I am trying to bind them with:
dbUser = db.Users
.AsNoTracking()
.Where(x => x.UserId == UserID)
.Include(m => m.Role)
.ThenInclude(m => m.RolePermissions)
.ThenInclude(m => m.Entity)
.ThenInclude(m => m.EntityArea)
.FirstOrDefault();
However, I do get the Role, I'm not getting anything further (Rolepermissions collection, Entity and Area). Is there something fundamentally I am doing wrong? This is a readonly query so hence notracking being used.
Thanks!

I don't think there is anything fundamentally wrong with your attempt. Have you checked if the specific user actually has any data in the connected tables?
Include() can sometimes generate a left join when you don't really need it, so I'd advise you to stay away from it when you can. I'd generally use a projection to specify the data I want to recieve. For your example this can be done with:
dbUser = db.Users
.AsNoTracking()
.Where(x => x.UserId == UserID)
.Select(x => new
{
Role = x.Role,
RolePermissions = x.RolePermissions,
Entity = x.Entity,
EntityArea = x.EntityArea
})
.FirstOrDefault();

Related

.NET Core: How to merge nested one-to-many relations in dto

How could merge nested child entity in parent?
I have these three entities:
public class Faculty
{
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<Group> Groups { get; set; }
}
public class Group
{
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<User> Users { get; set; }
}
public class User
{
public Guid Id { get; set; }
public string Name { get; set; }
}
Expected results in ResultDto is:
public class ResultDto
{
public Guid FacultyId { get; set; }
public string FacultyName { get; set; }
public ICollection<User> Users { get; set; }
}
You're looking for SelectMany:
var results = context.Faculties.Select(f => new ResultDto
{
FacultyId = f.Id,
FacultyName = f.Name,
Users = f.Groups.SelectMany(g => g.Users).ToList()
}
This will run in EF-core versions like 5 and 6, also in 3, but slightly less efficiently.

How can I order by many-to-many extra column in Entity Framework Core?

I am using Entity Framework Core 3. Member and Request entities are related many-to-many. But I have an extra column named Order. The member request order is saved in the MemberRequest table.
public class Member
{
public int MemberID { get; set; }
public string Name { get; set; }
public virtual ICollection<MemberRequest> MemberRequests { get; set; }
}
public class Request
{
public int RequestID { get; set; }
public string Message { get; set; }
public virtual ICollection<MemberRequest> MemberRequests { get; set; }
}
public class MemberRequest
{
[Key, Column(Order = 0)]
public int MemberID { get; set; }
[Key, Column(Order = 1)]
public int RequestID { get; set; }
public virtual Member Member { get; set; }
public virtual Request Request { get; set; }
public int Order { get; set; }
}
I want to get two select using Entity Framework Core:
public IEnumerable<Member> Get(int id)
{
var members = await _context.Request
.Include(e => e.MemberRequests)
.Where(e => e.MemberRequests.Any(m => m.RequestID == id))
.ToListAsync();
// How can I order by MemberRequest.Order ???
return member;
}
But I could not order result by MemberRequest.Order in MemberRequest entity. How can I do it?

Complex subquery in Entity Framework 6

I have an entity called Insurance like this:
public class Insurance : BaseEntity, IExpirationDocument
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public override int Id { get; set; }
[Column(TypeName = "NVARCHAR")]
[StringLength(256)]
public string PathToCertificate { get; set; }
[Column(TypeName = "NVARCHAR")]
[StringLength(50)]
public string Filename { get; set; }
public int Value { get; set; }
public string Name => InsuranceType.Name;
public DateTime ExpiryDate { get; set; }
public DateTime IssueDate { get; set; }
public bool Active { get; set; }
public int InsuranceTypeId { get; set; }
public virtual InsuranceType InsuranceType { get; set; }
public int InsurerId { get; set; }
public virtual Insurer Insurer { get; set; }
public int ApplicantId { get; set; }
public virtual Applicant Applicant { get; set; }
public int? DocumentEmailHistoryId { get; set; }
public virtual DocumentEmailHistory DocumentEmailHistory { get; set; }
public Insurance()
{
Active = true;
}
}
Would it be possible to do this type of query with Entity Framework:
SELECT *
FROM Insurances i1
INNER JOIN
(SELECT
insuranceTypeId, applicantid, MAX(IssueDate) as 'maxissuedate'
FROM
Insurances
GROUP BY
insuranceTypeId, applicantid) AS i2 ON i1.applicantid = i2.applicantid
AND i1.insuranceTypeId = i2.insuranceTypeId
WHERE
i1.issueDate = i2.maxissuedate
If you are trying to get latest issued Insurance according to InsuranceTypeId and ApplicantId you can group data according to needed properties, order by IssueDate descendingly and take only one Insurance info. Of course it will not give you the same query but it will give you the same result:
var result = context.Insurances
.GroupBy(m => new { m.InsuranceTypeId , m.ApplicantId })
.Select( g => new
{
MaxInsurance = g.OrderByDescending(m => m.IssueDate)
.Take(1)
})
.SelectMany(m => m.MaxInsurance);

Entity Framework Many to Many Relationship 3 Classes

Hi I have the three classes below. I'm trying achieve a many to many mapping with three classes. I have achieved many to many relationship between two classes but I'm trying to get another class in the mix. Below is the classes I have and a class representation of the relationship I'm trying to achieve.
public class Person
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmployeeId { get; set; }
public ICollection<Department> Departments { get; set; }
}
public class Department
{
public int ID { get; set; }
public string Name { get; set; }
public string DepartmentCode { get; set; }
public string Description { get; set; }
public ICollection<Person> Members { get; set; }
}
public class Role
{
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
Any hints on how to achieve the below would be helpful.
public class PersonRoleDepartment
{
public int PersonID { get; set; }
public int DepartmentID{ get; set; }
public int RoleID { get; set; }
}
public class Person
{
public int Id { get; set; }
public ICollection<PersonDepartmentRole> DepartmentRoles { get; set; }
}
public class Department
{
public int Id { get; set; }
public ICollection<PersonDepartmentRole> PersonRoles { get; set; }
}
public class Role
{
public int Id { get; set; }
public ICollection<PersonDepartmentRole> PersonDepartments { get; set; }
}
public class PersonDepartmentRole
{
[Key, Column( Order = 0 )]
public int PersonId { get; set; }
[ForeignKey( "PersonId" )]
[Required]
public virtual Person Person { get; set; }
[Key, Column( Order = 1 )]
public int DepartmentId { get; set; }
[ForeignKey( "DepartmentId" )]
[Required]
public virtual Department Department { get; set; }
[Key, Column( Order = 2 )]
public int RoleId { get; set; }
[ForeignKey( "RoleId" )]
[Required]
public virtual Role Role { get; set; }
}
Fluent API config:
var pdrConfig = modelBuilder.Entity<PersonDepartmentRole>()
.HasKey( pdr => new
{
pdr.PersonId,
pdr.DepartmentId,
pdr.RoleId
} );
pdrConfig.HasRequired( pdr => pdr.Department )
.WithMany( d => d.PersonRoles )
.HasForeignKey( pdr => pdr.DepartmentId );
pdrConfig.HasRequired( pdr => pdr.Person )
.WithMany( p => p.DepartmentRoles )
.HasForeignKey( pdr => pdr.PersonId );
pdrConfig.HasRequired( pdr => pdr.Role )
.WithMany( r => r.PersonDepartments )
.HasForeignKey( pdr => pdr.RoleId );
Note: all references from any of the primary entities to the others should route through your junction entity PersonDepartmentRole - if not, data inconsistencies can arise (e.g. I add a Department to a Person as well as a corresponding PersonDepartmentRole record to the DB, but then I remove the Department from the Person entity - the PersonDepartmentRole entity still remains)
Instead, if you want all departments for a person:
db.Person.DepartmentRoles.Select( dr => dr.Department ).Distinct()
In this construct, can a person be in a department but have no role? Or, can a department play a role but has no people? If not, how could this be accomplished

Entity Framework Code First Many to Many Setup For Existing Tables

I have the following tables Essence, EssenseSet, and Essense2EssenceSet
Essense2EssenceSet is the linking table that creates the M:M relationship.
I've been unable to get the M:M relationship working though in EF code first though.
Here's my code:
[Table("Essence", Schema = "Com")]
public class Essence
{
public int EssenceID { get; set; }
public string Name { get; set; }
public int EssenceTypeID { get; set; }
public string DescLong { get; set; }
public string DescShort { get; set; }
public virtual ICollection<EssenceSet> EssenceSets { get; set; }
public virtual EssenceType EssenceType { get; set; }
}
[Table("EssenceSet", Schema = "Com")]
public class EssenceSet
{
public int EssenceSetID { get; set; }
public int EssenceMakerID { get; set; }
public string Name { get; set; }
public string DescLong { get; set; }
public string DescShort { get; set; }
public virtual ICollection<Essence> Essences { get; set; }
}
[Table("Essence2EssenceSet", Schema = "Com")]
public class Essence2EssenceSet
{
//(PK / FK)
[Key] [Column(Order = 0)] [ForeignKey("Essence")] public int EssenceID { get; set; }
[Key] [Column(Order = 1)] [ForeignKey("EssenceSet")] public int EssenceSetID { get; set; }
//Navigation
public virtual Essence Essence { get; set; }
public virtual EssenceSet EssenceSet { get; set; }
}
public class EssenceContext : DbContext
{
public DbSet<Essence> Essences { get; set; }
public DbSet<EssenceSet> EssenceSets { get; set; }
public DbSet<Essence2EssenceSet> Essence2EssenceSets { get; set; }
protected override void OnModelCreating(DbModelBuilder mb)
{
mb.Entity<Essence>()
.HasMany(e => e.EssenceSets)
.WithMany(set => set.Essences)
.Map(mc =>
{
mc.ToTable("Essence2EssenceSet");
mc.MapLeftKey("EssenceID");
mc.MapRightKey("EssenceSetID");
});
}
}
This is the code I'm trying to run:
Essence e = new Essence();
e.EssenceTypeID = (int)(double)dr[1];
e.Name = dr[2].ToString();
e.DescLong = dr[3].ToString();
//Get Essence Set
int setID = (int)(double)dr[0];
var set = ctx.EssenceSets.Find(setID);
e.EssenceSets = new HashSet<EssenceSet>();
e.EssenceSets.Add(set);
ctx.Essences.Add(e);
ctx.SaveChanges();
And here's the error:
An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception.
I'm not able to find the problem. I'd greatly appreciate help setting this up right.
Thanks!
Remove your Essence2EssenceSet model class. If junction table contains only keys of related entities participating in many-to-many relations it is not needed to map it as entity. Also make sure that your fluent mapping of many-to-many relations specifies schema for table:
mb.Entity<Essence>()
.HasMany(e => e.EssenceSets)
.WithMany(set => set.Essences)
.Map(mc =>
{
mc.ToTable("Essence2EssenceSet", "Com");
mc.MapLeftKey("EssenceID");
mc.MapRightKey("EssenceSetID");
});