Multiple Include/ThenInclude causing duplicates in EF Core - sql-server

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;

Related

ArgumentNullException: Value cannot be null. (Parameter 'source')

I have parent and childs nested tables.
Here is my model:
public class Categories
{
[Key]
public int CategoriesId { get; set; }
public int Order { get; set; }
public string CategoryName { get; set; }
public List<News> News { get; set; }
}
public class News
{
[Key]
public int NewsId { get; set; }
public int CategoriesId { get; set; }
public string Content { get; set; }
public DateTime Date { get; set; }
...
public List<Comments> Comments { get; set; }
public Categories Categories { get; set; }
}
public class Comments
{
[Key]
public int CommentsId { get; set; }
public int NewsId { get; set; }
public string Comment { get; set; }
...
public News News { get; set; }
}
public class NewsImages
{
[Key]
public int ImageId { get; set; }
public int NewsId { get; set; }
public string ImageUrl { get; set; }
public bool Cover { get; set;}
...
public News News { get; set; }
}
I'm trying to send it from ViewComponent to View;
public async Task<IViewComponentResult> InvokeAsync()
{
var group = _dbContext.Categories.Where(k => k.Order != 0).OrderBy(h => h.Order)
.Select(c => new
{
C = c,
N = c.News.OrderByDescending(n => n.Date).Take(5)
.Select(r => new
{
Y = r.Comments,
R = r.NewsImages.Where(rs => rs.Cover == true).FirstOrDefault()
})
});
var model = group
.Select(m => m.C);
return View(await model.ToListAsync()) ;
}
I am sure there are enough News records for every Category, But I get error :
ArgumentNullException: Value cannot be null. (Parameter 'source')
AspNetCore.Views_Shared_Components_IndexKategori_Default.ExecuteAsync() in Default.cshtml
var bp = k.News.FirstOrDefault();
if I use that code works fine :
var model = _dbContext.Categories
.Include(h => h.News).ThenInclude(h => h.Comments)
.Include(h => h.News).ThenInclude(h => h.NewsImages)
.Where(h => h.Order != 0)
.OrderBy(h => h.Order)
But when I use the code above, a few records appear for some categories, and some categories react as if there are no records.
Where am I making mistakes?
Thank you in advance for those who helped ..
Whenever you have a big LINQ statement that throws an exception, and you can't find where the exception comes form, translate the LINQ into smaller steps, and ToList() every step.
public async Task<IViewComponentResult> InvokeAsync()
{
// Temp code: small steps, ToList after every step
var a = dbContext.Categories.Where(category => category.Order != 0).ToList();
var b = a.OrderBy(category => category.Order).ToList();
var c = b.Select(category => new
{
Category = category,
News = category.News.OrderByDescending(news => news.Date)
.Take(5)
.ToList();
})
.ToList();
var d = c.Select(item => new
{
Category = item.Category,
NewsItems = item.News.Select(news => new
{
Comments = news.Comments,
Images = news.NewsImages.Where(newsImage => newsImage.Cover).ToList(),
})
.ToList(),
})
.ToList();
var e = d.Select(item => new
{
Category = item.Category,
NewsItems = item.NewsItems.Select(newsItem => new
{
Comments = newsItem.Comments,
Images = images.FirstOrDefault();
})
.ToList(),
})
.ToList();
// original code:
var group = _dbContext.Categories.Where...
}
I'm sure that your debugger will tell you which step is incorrect.

How to Improve on this Entity Framework Query

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

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

ThenInclude with many to many in EF Core

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?

How do I model a 'has a' database relationship with multiple principles?

In code I have an object graph that looks like this:
public class Author
{
public int AuthorId { get; set; }
public string Name { get; set; }
public Address Address { get; set; }
}
public class Publisher
{
public int PublisherId { get; set; }
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address
{
public int AddressId { get; set; }
public string Postcode { get; set; }
}
How do I model this in a relational database schema?
This needs to be a 0-1..1 relation, i.e. Authors and Publishers may either have or not have an Address.
Ideally Addresses would have to be referenced by either an Author or a Publisher but not both.
Massive bonus respect if you can model it in Entity Framework Core using navigation properties and have cascade delete remove the Address when an Author or Publisher is removed. (But I'm betting no one will be able to).
I'd list all the things I've tried, but the post would be so long no one would read it. It'll be quicker just to say I've tried everything I can think of.
There are many ways you can achieve the goal in EF Core. The key point is that Address will be the dependent end of the relationships and it will contain optional FKs to the principal entities Author and Publisher.
Here are the possible Address models and configurations:
(1) Address with explicit FK and navigation properties
Model:
public class Address
{
public int AddressId { get; set; }
public string Postcode { get; set; }
public int? AuthorId { get; set; }
public Author Author { get; set; }
public int? PublisherId { get; set; }
public Publisher Publisher { get; set; }
}
Configuration:
modelBuilder.Entity<Author>()
.HasOne(e => e.Address)
.WithOne(e => e.Author)
.HasForeignKey<Address>(e => e.AuthorId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Publisher>()
.HasOne(e => e.Address)
.WithOne(e => e.Publisher)
.HasForeignKey<Address>(e => e.PublisherId)
.OnDelete(DeleteBehavior.Cascade);
(2) Address with navigation properties only
Model:
public class Address
{
public int AddressId { get; set; }
public string Postcode { get; set; }
public Author Author { get; set; }
public Publisher Publisher { get; set; }
}
Configuration:
modelBuilder.Entity<Author>()
.HasOne(e => e.Address)
.WithOne(e => e.Author)
.HasForeignKey<Address>("AuthorId")
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Publisher>()
.HasOne(e => e.Address)
.WithOne(e => e.Publisher)
.HasForeignKey<Address>("PublisherId")
.OnDelete(DeleteBehavior.Cascade);
(3) Address with explicit FK properties only
Model:
public class Address
{
public int AddressId { get; set; }
public string Postcode { get; set; }
public int? AuthorId { get; set; }
public int? PublisherId { get; set; }
}
Configuration:
modelBuilder.Entity<Author>()
.HasOne(e => e.Address)
.WithOne()
.HasForeignKey<Address>(e => e.AuthorId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Publisher>()
.HasOne(e => e.Address)
.WithOne()
.HasForeignKey<Address>(e => e.PublisherId)
.OnDelete(DeleteBehavior.Cascade);
(4) Address without explicit FK and navigation properties
Model:
public class Address
{
public int AddressId { get; set; }
public string Postcode { get; set; }
}
Configuration:
modelBuilder.Entity<Author>()
.HasOne(e => e.Address)
.WithOne()
.HasForeignKey<Address>("AuthorId")
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Publisher>()
.HasOne(e => e.Address)
.WithOne()
.HasForeignKey<Address>("PublisherId")
.OnDelete(DeleteBehavior.Cascade);
Reference: Relationships

Resources