ThenInclude with many to many in EF Core - database

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?

Related

React js : get foreign key data from .net core entity framework

I am using visual studio 2022 react js web app with .net core template. I've sales table in database which uses productId primary key. in react.js I have select menu in which i want to show the product name instead of product id. how could i achieve this?
in component:
// 1 create useStae
const [sales, setSales] = useState([])
//2 call Api
useEffect(() => {
fetch("api/sale/GetSales")
.then(response => { return response.json() })
.then(responseJson => {
setSales(responseJson)
})
}, [])
and in form i have select menu which shows procutId(foreign keys)
{
sales.map((item) => (
<option>{ item.productId}</option>
))
}
</select>
in controller
[HttpGet]
[Route("GetSales")]
public IActionResult GetSales()
{
NavbaseContext db = new NavbaseContext();
List<Sale> saleList = db.Sales.ToList();
return StatusCode(StatusCodes.Status200OK, saleList);
}
inside sales Model:
using System;
using System.Collections.Generic;
namespace Project1.Models;
public partial class Sale
{
public int Id { get; set; }
public int? ProductId { get; set; }
public int? CustomerId { get; set; }
public int? StoreId { get; set; }
public DateTime? DateSold { get; set; }
public virtual Customer? Customer { get; set; }
public virtual Product? Product { get; set; }
public virtual Store? Store { get; set; }
}
To show product name instead of id, there are some minor modifications you need to do in each part of the code. Try to follow and understand.
Include Product object in your Sale class.
Update your API to include the product name in the response. You can use LINQ to join the Sale and Product tables, and select the product name along with other sale properties.
In your component, display product name instead of product id.
Class
public partial class Sale
{
public int Id { get; set; }
public int? ProductId { get; set; }
public int? CustomerId { get; set; }
public int? StoreId { get; set; }
public DateTime? DateSold { get; set; }
public virtual Customer? Customer { get; set; }
public virtual Product? Product { get; set; }
public virtual Store? Store { get; set; }
}
API
[HttpGet]
[Route("GetSales")]
public IActionResult GetSales()
{
NavbaseContext db = new NavbaseContext();
var saleList = db.Sales
.Include(s => s.Product) // Include the product object to be able to access its properties
.Select(s => new { s.Id, s.CustomerId, s.StoreId, s.DateSold, ProductName = s.Product.Name })
.ToList();
return StatusCode(StatusCodes.Status200OK, saleList);
}
Component
<select>
{sales.map((item) => (
<option key={item.Id} value={item.Id}>{item.ProductName}</option>
))}
</select>

EF Core Add-Migration generating extra column with ColumnName1

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);

EF Core query full join between many-to-many relation

I'm working with EF core and I have a many-to-many relation between STUDENTS and SUBJECTS, like this:
public class StudentDetail
{
[Key]
[JsonPropertyName("Id")]
public int Id { get; set; }
[Required]
[Column(TypeName ="nvarchar(50)")]
[JsonPropertyName("Name")]
public string Name { get; set; }
[JsonPropertyName("StudentSubjects")]
public virtual IEnumerable<StudentSubject> StudentSubjects {get; set;}
}
public class SubjectDetail
{
[Key]
[JsonPropertyName("Id")]
public int Id { get; set; }
[Required]
[Column(TypeName = "nvarchar(20)")]
[JsonPropertyName("SubjectName")]
public string SubjectName { get; set; }
[Required]
[JsonPropertyName("Teacher")]
public virtual TeacherDetail Teacher { get; set; }
[JsonPropertyName("StudentSubjects")]
public IEnumerable<StudentSubject> StudentSubjects { get; set; }
}
public class StudentSubject
{
[JsonPropertyName("StudentId")]
public int StudentId { get; set; }
[JsonPropertyName("Student")]
public StudentDetail Student { get; set; }
[JsonPropertyName("SubjectId")]
public int SubjectId { get; set; }
[JsonPropertyName("Subject")]
public SubjectDetail Subject { get; set; }
[Required]
[Column(TypeName = "nvarchar(3)")]
[JsonPropertyName("Grade")]
public string Grade { get; set; }
}
I create my Databse using migrations, so after I did the migration, the database was created like this:
I need a query that bring me all the Subjects with their teacher and grade of an specific Student. I was tryng doing it like this:
var subjects = await _context.StudentSubject
.Include(s => s.Subject)
.Where(sid => sid.StudentId == student.Id)
.Select(st => st.Subject)
.Include(t => t.Teacher)
.ToListAsync();
But I'm getting an ERROR saying that I'm tryng to use Include(); on a non Queryable Entity. Anyone know what am I doing wrong?
Include will only work if you are having a link between two table and that is defined by keyword Virtual in C# classes. Please use public virtual ICollection instead of public IEnumerable and follow the link for more details. If you still wants to continue with same class then try using join in Linq queries.

How to get all data from ef core many to many

On EF core have Two tables(Page, Group) both have many to many relations with junction table GroupPage. Want to get all pages data with junction table related data based on groupId as like bellow.
If you construct your EF relation correctly you should not have a GroupPage entity.
See Entity Framework Database First many-to-many on how to construct your EF EDM correctly.
Once you have your EDM correctly mapped, you should have the classes
public class Page
{
public int Id { get; set; }
public ICollection<Group> Groups { get; set; }
...
}
public class Group
{
public int Id { get; set; }
public ICollection<Page> Pages { get; set; }
...
}
Then you just need to do the following
public IQueryable<Page> GetPages(int groupId)
{
return from group in _context.Groups
where group.Id == groupId
from page in group.Pages
select page;
}
The following syntax is self-descriptive. Here are the entities structure and Page Dto.
public class Page
{
public int Id { get; set; }
public ICollection<Group> Groups { get; set; }
...
}
public class Group
{
public int Id { get; set; }
public ICollection<Page> Pages { get; set; }
...
}
public class PageGroup
{
public int PageId { get; set; }
public Page Page { get; set; }
public int GroupId { get; set; }
public Group Group { get; set; }
}
public class PagesDto
{
public string Name { get; set; }
public int GroupId { get; set; }
public int PageId { get; set; }
public string Description { get; set; }
public string Tab { get; set; }
public string Module { get; set; }
public bool? IsActive { get; set; }
public bool? IsDefault { get; set; }
public PagesDto()
{
IsActive = false;
IsDefault = false;
}
}
Following function help us to get group related pages information.
public async Task<List<PagesDto>> GetAllPagesByGroupId(int selectedGroupId)
{
//get all pages
var pages = await _pagesRepository.GetAll().Select(p => new PagesDto {
PageId = p.Id,
Name = p.Name,
GroupId = 0
}).ToListAsync();
//get group ralated pages
var selectedGroupPageIds = _groupPagesRepository
.GetAll()
.Where(p => p.GroupId == selectedGroupId)
.Select(p => p.PageId);
//update page information base on group related pages info.
foreach (var item in pages.Where(p=>selectedGroupPageIds.Contains(p.PageId)))
{
item.GroupId = selectedGroupId;
}
return pages;
}

Multiple Include/ThenInclude causing duplicates in EF Core

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;

Resources