Many-to-many with TPH inheritance in EF Core - c#

I am having trouble with setting up a many-to-many join table with EF core if one side of the join table is a derived class in table-per-hierarchy set up.
Here's the set up:
class Chore
{
Guid Id;
}
class LaundryChore : Chore
{
// PROBLEMATIC
List<Clothing> ManyClothing;
}
class FoldingChore : Chore
{
Clothing SingleClothing;
}
class Clothing
{
Guid Id;
// PROBLEMATIC
List<Chore> Chores;
}
I have the TPH set up with discriminator and that all works fine. IF the ManyClothing field was on the Chore class then I can just do:
builder.Entity<Clothing>().HasMany(clothing => clothing.Chores)
.WithMany(chore => chore.ManyClothing);
and this works as expected.
But since ManyClothing field was on the LaundryChore class, I would get DNE error with above.
I tried switching the direction:
builder.Entity<LaundryChore>().HasMany(chore => clothing.ManyClothing)
.WithMany(clothing => clothing.Chores);
and I get a casting error:
Cannot implicitly convert type 'System.Collections.Generic.List' to 'System.Collections.Generic.IEnumerable'
If I change to:
class Clothing
{
Guid Id;
List<LaundryChore> Chores;
}
Then the error I get is:
The filter expression ... cannot be specified for entity type 'LaundryChore'. A filter may only be applied to the root entity type 'Chore'
Any guidance would be appreciated - thanks!

A navigation properties can only participate in a single relationship.
It isn't a limitation type, but a limitation by relation. For example, if you have only the Chore and Clothing classes :
public class Chore
{
public int Id { get; set; }
public List<Clothing> ManyClothingToLaundry { get; set; }
public Clothing SingleClothingToFolding { get; set; }
}
public class Clothing
{
public int Id { get; set; }
public List<Chore> Chores { get; set; }
}
Now, you want add a Chore to a Clothing :
clothing.Chores.Add(chore);
Do this add a chore to laundry or to folding? EF Core can't know in this case.
In your specific case, EF Core could detect the relationship from the type, but this functionality is not implemented.
If Clothing has two distinct relationship, then Clothing need two navigation properties :
public class Clothing
{
public int Id { get; set; }
public List<Chore> FoldingChores { get; set; }
public List<Chore> LaundryChores { get; set; }
public IEnumerable<Chore> Chores => FoldingChores.Union(LaundryChores);
}
...
protected override void OnModelCreating(ModelBuilder builder)
{
...
builder.Entity<Clothing>().Ignore(c => c.Chores);
builder.Entity<Clothing>()
.HasMany<LaundryChore>(nameof(Clothing.LaundryChores))
.WithMany(chore => chore.ManyClothing);
builder.Entity<Clothing>()
.HasMany<FoldingChore>(nameof(Clothing.FoldingChores))
.WithOne(chore => chore.SingleClothing);
}
Clothing.FoldingChores is a collection of the base entity Chore for the example, but it can be directly a collection of the finale type FoldingChore. Idem with Clothing.LaundryChores :
public class Clothing
{
public int Id { get; set; }
public List<FoldingChore> FoldingChores { get; set; }
public List<LaundryChore> LaundryChores { get; set; }
public IEnumerable<Chore> Chores => FoldingChores.Cast<Chore>().Union(LaundryChores.Cast<Chore>());
}

Related

How can I make Ef Core totally ignore an object who wraps other properties inside an Entity

I'm trying to make EfCore ignore a wrapping object similarly to the owned type concept.
How can I turn this object:
public class Entity
{
public Guid Id { get; set; }
public object SomeProperty { get; set; }
public ICollection<Item> Items { get; set; }
public ICollection<OtherItem> OtherItems { get; set; }
}
public class Item
{
public Entity Entity { get; set; }
public Guid EntityId { get; set; }
}
public class OtherItem
{
public Entity Entity { get; set; }
public Guid EntityId { get; set; }
}
Into this object
public class Entity
{
public Guid Id { get; set; }
public Aggregate Aggregate { get; set; } // This should not be mapped to the Database but only the properties
}
[Owned] // I think this is what i'm looking for
public class Aggregate
{
public object SomeProperty { get; set; }
public ICollection<Item> Items { get; set; }
public ICollection<OtherItem> OtherItems { get; set; }
public void SomeUsefulFunction()
{
// do Something Useful on the Aggregate
}
}
I would like EfCore to completely ignore the Aggregate object and threat his properties as if they were from the entity object. I thought the concept of owned entities was exactly this but I get the following error:
Unable to determine the relationship represented by navigation 'Aggregate.OtherItems' of type 'ICollection<OtherItem>'. Either manually configure the relationship, or ignore thi
s property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
Like this?
public class Entity
{
public Guid Id { get; set; }
public Aggregate Aggregate => new Aggregate(this);
protected object SomeProperty { get; set; }
protected ICollection<Item> Items { get; set; }
protected ICollection<OtherItem> OtherItems { get; set; }
}
public class Aggregate
{
public object SomeProperty => _entity.SomeProperty;
public ICollection<Item> Items => _entity.Items;
public ICollection<OtherItem> OtherItems => _entity.OtherItems;
public Aggregate(Entity entity)
{
_entity = entity;
}
public void SomeUsefulFunction()
{
// do Something Useful on the Aggregate
}
}
public class SampleContext : DbContext
{
public DbSet<Entity> Entities { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Entity>().Ignore(c => c.Aggregate);
}
}
PS Just showing how to get, you can easly adapt it to use set too.
I suggest you to check out dotnet reference architecture https://github.com/dotnet-architecture/eShopOnContainers. There Ordering service is build using DDD approach - https://github.com/dotnet-architecture/eShopOnContainers/tree/dev/src/Services/Ordering.
It has good examples how to model Aggregates, Domain Events (Ordering.Domain folder) and also repository implementation along with configuration of EF entities (Ordering.Infrastructure folder).
Also there're couple of solution templates available to avoid typing boilerplate code :
https://github.com/jasontaylordev/CleanArchitecture
https://github.com/ardalis/CleanArchitecture
From microsoft docs :
Applications that follow the Dependency Inversion Principle as well as the Domain-Driven Design (DDD) principles tend to arrive at a similar architecture. This architecture has gone by many names over the years. One of the first names was Hexagonal Architecture, followed by Ports-and-Adapters. More recently, it's been cited as the Onion Architecture or Clean Architecture

Relationship - EF Core

I'm having some trouble to get into EF Core relationship.
I didn't know how to search it properly, so I've not found what I need, but I got somewhere.
I have these two classes:
Expense:
public class Expense : Entity
{
public string Title { get; set; }
public string Description { get; set; }
public decimal Amount { get; set; }
public List<ExpenseType> Types { get; set; }
public ValidationResult ValidationResult { get; private set; }
public bool IsValid
{
get
{
var fiscal = new ExpenseIsValidValidation();
ValidationResult = fiscal.Valid(this);
return ValidationResult.IsValid;
}
}}
ExepenseType:
public class ExpenseType : Entity
{
#region properties
public string Name { get; private set; }
public string Description { get; private set; }
public ValidationResult ValidationResult { get; private set; }
public bool IsValid
{
get
{
var fiscal = new ExpenseTypeIsValidValidation();
ValidationResult = fiscal.Valid(this);
return ValidationResult.IsValid;
}
}}
During the ToListAsync in ExpenseType, the EF adds the column "expenseId" to the query, but this column does not exist.
My database has three tables, one for each class, and one for the relationship.
(Expense, ExpenseType and Expense_ExpenseType)
By looking for the solution here on StackOverflow I found that I should have a class for the third table.
Here it is:
public class Expense_ExpenseType
{
public int ExpenseId { get; set; }
public Expense Expense { get; set; }
public int ExpenseTypeId { get; set; }
public ExpenseType ExpenseType { get; set; }
}
My idea is that I can have an ExpenseType without having an Expense, and I can have an Expense without ExpeseType or with as many as I want of them.
So ExpenseType hasn't any Expense.
I'm not sure what I should do now.
Should I Map using optionsBuilder? How?
Should I ReWrite the database?
If you want to create Many-to-Many relationship, you have several options how to do it:
Create additional class how you described. In this case EF will create table and you can get access to get values only from this table.
public class Expense_ExpenseType
{
public int ExpenseId { get; set; }
public Expense Expense { get; set; }
public int ExpenseTypeId { get; set; }
public ExpenseType ExpenseType { get; set; }
}
You may don't create class and just describe in the context relationship. Where you will describe everything and EF will create by yourself this table. But from the app you will not see this table. You have to use this variant if you don't want to extend table with additional fields.
modelBuilder
.Entity<Student>()
.HasMany<Course>(s => s.Courses)
.WithMany(c => c.Students)
.Map(cs =>
{
cs.MapLeftKey("StudentRefId");
cs.MapRightKey("CourseRefId");
cs.ToTable("StudentCourse");
});
For this relationship you can read more here
But in your case you don't need to use Many-to-Many. That's why if you don't want to add propertie ExpanseTypeId or ExpenseId in your model you can describe it like this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Expense>()
.HasMany<ExpenseType>(o => o.Types) //It is your list of expense types.
.WithOne() //Here you can add model for expense. To have an option go back from expense type to expense
.HasForeignKey("ForeignKey");//This key EF will create for you in DB but not in you app model
}
What do you want to use you have to decide. If you have an idea that expense has a lot of expensetypes and each expense type has a lot of expenses. You have to use Many-To-Many how I described.
I think that your main question is "My idea is that I can have an ExpenseType without having an Expense, and I can have an Expense without ExpeseType or with as many as I want of them."
so you can do that by creating a nullable foreign key ExpenseTypeId in Expanse class and HashSet of Expanse in ExpeseType class.
Like this:
public class ExpenseType : Entity
{
public ICollection<Expanse> Expanses {get; set;} = new HashSet<Expanse>()
}
public class Expense : Entity
{
public int? ExpanseTypeId {get; set;}
public ExpanseType ExpanseType {get; set;}
}

Foreign Key with multiple Inherited Classes EF Core 2.0 Code First

I have 2 classes (A & B), both inherit from my Base class.
My Side Class has a BaseId and a Base field.
I'm working with Code First and Table per Concrete Class (TPC)
I'm trying to create Foreign keys to connect the Side table with my A & B table. When i create only one relation with fluent api it works fine. But when i add the second one, the new migration drop the FKs from A and create a new one for B. Why i can't create 2 relations?
public class Base
{
public int Id { get; set; }
public string Name { get; set; }
}
public class A : Base
{
public string StringA { get; set; }
}
public class B : Base
{
public string StringA { get; set; }
}
public class Side
{
public int Id { get; set; }
public int BaseId { get; set; }
public Base Base { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Side>().HasOne(s => (A) s.Base);
modelBuilder.Entity<Side>().HasOne(s => (B) s.Base);
}
Hopefully you already got this figured out, but as previously mentioned table TPC isn't supported! You can use TPH.
To do TPH just do the following:
modelBuilder.Entity<Side>.ToTable("Sides")
.HasDiscriminator<SideType>("SideType")
.HasValue<A>(SideType.BASE_A)
.HasValue<B>(SideType.BASE_A);
public enum SideType {
BASE_A = 100,
BASE_B = 500
}

Relationship on owned type

I want to define a one to many relationship on an owned type.
Here in my example,
InboundRequest: principal entity
RequestHistory: owned type
RequestHistoryEntry : Dependency entity
public class InboundRequest : IAggregateRoot
{
public int Id { get; private set; }
public RequestHistory History { get; private set; }
}
public class RequestHistory
{
public IList<RequestHistoryEntry> HistoryEntries { get; set; }
}
public class RequestHistoryEntry
{
public RequestState State { get; private set; }
public DateTime Timestamp { get; private set; }
public int Id { get; set; }
}
builder.Entity<InboundRequest>().OwnsOne(x => x.History);
Unfortunately EF Core gives me the following error:
The relationship from 'RequestHistoryEntry' to
'InboundRequest.History#RequestHistory.HistoryEntries' is not
supported because the owned entity type
'InboundRequest.History#RequestHistory' cannot be on the principal
side.
Is there a way to have a one to many relationship between an owned type and a list of dependencies?
The only way to have a one to many relationship with owned types is if the owned entity is on the many side of the relationship, in which case ef core will map it to a separate table by convention. What you are attempting is per se not possible by design. However, in your particular case, why don't you get rid of RequestHistory altogether and make the list of RequestHistoryEntry as a collection of owned type to your InboundRequest as follows:
public class InboundRequest : IAggregateRoot
{
public int Id { get; private set; }
public IList<RequestHistoryEntry> HistoryEntries { get; set; }
}
public class RequestHistoryEntry
{
public RequestState State { get; private set; }
public DateTime Timestamp { get; private set; }
public int Id { get; set; }
}
builder.Entity<InboundRequest>().OwnsMany(x => x.HistoryEntries);
That should get rid of the error as well as achieve your desired relationship to your dependent entity.
You cannot set a list of objects as a owned type, because the columns of your History table are added to the InboundRequest table. The column count of the table must be fixed and not dynamic.
If you write the following:
public RequestHistoryEntry HistoryEntries { get; set; }
Then your InboundRequest table looks like that (or similar):
| Id | RequestHistory_State | RequestHistory_Timestamp | RequestHistory_Id |
If you are using a List of RequestHistoryEntry, then the column count is not clear.
Therfore you have to use a separate table for it.

Entity Framework Many to many through containing object

I was curious if it is possible to map an intermediate table through a containing object.
public class Subscriber : IEntity
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
private ChannelList _subscribedList { get; set; }
public int NumSubscribedChannels { get { return _subscribedList.Count(); } }
}
public class HelpChannel : IEntity
{
[Key]
public int Id { get; set; }
public string name { get; set; }
public string category { get; set; }
public int group { get; set; }
}
I need to have a subscriber table, channel table and an intermediate table to link a subscriber to his/her channels.
Is it possible to map the list that is within the ChannelList object to the Subscriber Model?
I figured that's probably not possible and that I would need to just have a private List for EF to map. But I wasn't sure if EF will do that for private variables. Will it?
I'm hoping that is does because if it has to be public to maintain the encapsulation.
You can map private properties in EF code-first. Here is a nice description how to do it. In your case it is about the mapping of Subscriber._subscribedList. What you can't do is this (in the context's override of OnModelCreating):
modelBuilder.Entity<Subscriber>().HasMany(x => x._subscribedList);
It won't compile, because _subscribedList is private.
What you can do is create a nested mapping class in Subscriber:
public class Subscriber : IEntity
{
...
private ICollection<HelpChannel> _subscribedList { get; set; } // ICollection!
public class SubscriberMapper : EntityTypeConfiguration<Subscriber>
{
public SubscriberMapper()
{
HasMany(s => s._subscribedList);
}
}
}
and in OnModelCreating:
modelBuilder.Configurations.Add(new Subscriber.SubscriberMapping());
You may want to make _subscribedList protected virtual, to allow lazy loading. But it is even possible to do eager loading with Include:
context.Subscribers.Include("_subscribedList");

Categories