I have an issue I am unsure how to solve. I have three models, similar to as follows.
public class Parent : BaseEntity {
[Key]
public string Guid { get; set; }
[NotMapped]
public List<Child> Childs { get; set; }
}
public class Child : BaseEntity {
[Key]
public string Guid { get; set; }
public string ParentGuid { get; set; }
public List<Detail> Details { get; set; }
}
public class Detail : BaseEntity {
[Key]
public string Guid { get; set; }
[ForeignKey(nameof(Child))]
public string ChildGuid { get; set; }
public Child Child { get; set }
}
I'm attempting to include both the children and the details. However, I am not guaranteed the Guid on the child exists in the parent table. That's why I went with NotMapped, but am willing and able to change that if need be. Right now I have this:
query.GroupJoin(context.Parents,
parent => parent.ChildGuid,
child => child.Guid,
(parent, childs) => new
{
Parent = parent,
Childs = childs
}
)
.AsEnumerable()
.Select(combos =>
{
combos.Parent.Childs = combos.Childs.ToList();
return combos.Parent;
})
.AsQueryable();
But that, of course, does not include the Details. Not sure if I'm heading the right direction here or not, but could use some direction if anyone has run into this before.
A typical parent-child relationship has the ParentID on the child table/entity. Your entity definitions seem to reflect that, but then your Linq expression refers to some parent.ChildGuid that isn't mentioned in your entity.
First let's correct your entity definition relationships. Childs shouldn't be excluded. A parent can have 0 or multiple children. Collections should be declared as virtual ICollection<T> rather than concrete List<T>. virtual enables lazy loading and helps ensure that EF proxies are fully functional for tracking changes of items in the collections. We should also initialize those collections with a new concrete list. This helps ensure that any new entity we create is ready to go accepting children.
public class Parent : BaseEntity
{
[Key]
public string Guid { get; set; }
public virtual ICollection<Child> Childs { get; set; } = new List<Child>();
}
public class Child : BaseEntity
{
[Key]
public string Guid { get; set; }
public string ParentGuid { get; set; }
public virtual ICollection<Detail> Details { get; set; } = new List<Detail>();
}
public class Detail : BaseEntity
{
[Key]
public string Guid { get; set; }
[ForeignKey(nameof(Child))]
public string ChildGuid { get; set; }
public virtual Child Child { get; set }
}
EF can automatically map relationships provided you follow supported conventions in your naming. Chances are with the "Guid" syntax for your ID columns, EF won't work these out automatically so you will probably need to give it some help through configuration. For example in the OnModelCreating of the DbContext:
// EF Core
modelBuilder.Entity<Parent>()
.HasMany(x => x.Childs)
.WithOne()
.IsRequired() // If a Child must have a ParentGuid
.HasForeignKey(x => x.ParentGuid);
Since Child has a ParentGuid but no Parent reference declared, we use the WithOne() with no mapping, then use the HasForeignKey to set up the relationship. Similarly we set up the relationship between Child and Details:
// EF Core
modelBuilder.Entity<Child>()
.HasMany(x => x.Details)
.WithOne(x => x.Child)
.IsRequired() // If a Detail must have a Child
.HasForeignKey(x => x.ChildGuid);
In this case since we have a Child reference on Detail so we map to that.
Now regarding this statement:
However, I am not guaranteed the Guid on the child exists in the parent table.
From this it somewhat implies that a ParentGuid on the Child table might not exist in the Parents table. This would be a fairly serious concern from a database normalization and ensuring a valid data state. I would highly recommend to avoid trying to use a non-normalized schema such as attempting to "share" a Child table between multiple Parent tables. A classic example where this is tried is having an Address table (Child) referencing a ParentId that could point at a Customer (Parent) or Site (Parent) and/or other parent-like tables. Instead, you should consider a more normalized approach which would use a CustomerAddress and SiteAddress etc. linking table to ensure these relationships can be mapped out.
If a Child can exist without a Parent then you just need to remove the .IsRequired().
Now when querying, you don't need to worry specifically about joining and grouping, just query through the navigation properties and either eager-load the related data you want when working with the Entity graph, or project the data using Select when querying for details:
var query = context.Parents
.Include(x => x.Childs)
.ThenInclude(x => x.Details)
To get the children with their associated Parent:
var query = context.Parents
.Include(x => x.Childs)
.ThenInclude(x => x.Details)
.SelectMany(x => x.Childs.Select(c => new {Parent = x, Child = c})
.ToList();
If you want to include children that have no parent:
var query = context.Parents
.Include(x => x.Childs)
.ThenInclude(x => x.Details)
.SelectMany(x => x.Childs.Select(c => new {Parent = x, Child = c})
.Union(context.Childs.Where(x => x.ParentGuid == null))
.ToList();
These are very rough guesses as the type of query you want to perform. Again, if the ParentGuid could be referring to a non-existent row in the DB or a different table I would really look at correcting that to ensure the data maintains referential integrity. I don't recommend trying to "break" EF behavior to work with an effectively broken schema. You may get it to have a semblance of working, but it could very easily lead to exceptions or unexpected behaviour.
Related
Imagine a model of User that can have Parents and also can have Children.
How would you model such a case in EF Core?
I tried with something like that (pseudo-code)
public class User
{
public ICollection<Relation> Relations {get;set;}
public ICollection<User> Parents => Relation.Where(r => r.Relation == 'Parents')
public ICollection<User> Children => Relation.Where(r => r.Relation == 'Children')
}
public class Relaction
{
public User User1 {get;set;}
public Guid User1Id {get;set;}
public User User2 {get;set;}
public Guid User2Id {get;set;}
public Relation Relation {get;set;} //some enum or sth to indicate relation type
}
But in such modeling, I'm not able to force EF DbContext to fetch into User.Relations data where UserId is in User1Id and in User2Id.
Any idea?
What you are asking for is a classic many-to-many self relationship - (1) user as parent can have many users as children, and (2) user as child can have many users as parents.
Thus it is modelled with one main entity and one join (linking) entity similar to what you have shown. The linking entity does not need special indicator because the two FKs determine the role. i.e. lets change your example with more descriptive names:
public class User
{
public Guid Id { get; set; }
}
public class UserRelation
{
public User Parent { get; set; }
public User Child { get; set; }
public Guid ParentId { get; set; }
public Guid ChildId { get; set; }
}
Now, in pseudo code, given User user, then
user.Parents = db.Users.Where(u => user == u.Child)
user.Children = db.Users.Where(u => user == u.Parent)
EF Core 5.0+ allows you to hide the join entity (it still exists, but is maintained implicitly) and model the relationship with the so called skip navigations, which are the natural OO way of representing such relationship, e.g. the model becomes simply
public class User
{
public Guid Id { get; }
public ICollection<User> Parents { get; set; }
public ICollection<User> Children { get; set; }
}
This is all needed to create such relationship.
However the name of the join table and its columns by convention won't be what normally you would do - in this case, they would be "UserUser" table with "ParentsId" and "ChildrenId" columns.
If you use migrations and don't care about the names, then you are done and can safely skip the rest.
If you do care though, luckily EF Core allows you to change the defaults with fluent configuration (even though in a not so intuitive way):
modelBuilder.Entity<User>()
.HasMany(e => e.Parents)
.WithMany(e => e.Children)
.UsingEntity<Dictionary<string, object>>("UserRelation",
je => je.HasOne<User>().WithMany()
.HasForeignKey("ParentId").IsRequired().OnDelete(DeleteBehavior.Restrict),
je => je.HasOne<User>().WithMany()
.HasForeignKey("ChildId").IsRequired().OnDelete(DeleteBehavior.Restrict),
je => je.ToTable("UserRelations")
.HasKey("ParentId", "ChildId")
);
Here Dictionary<string, object> is the shared type EF Core will use to maintain the join entity in memory (change tracker). And is the most annoying thing in the above configuration since in a future they might change their minds and use different type (there are actually plans to do that in EF Core 6.0), so you'll have to update your mappings. Note that this does not affect the database design, just the memory storage type in EF Core change tracker.
So, because of that and the fact that in some scenarios it is better to work directly with the join entity, you could actually combine both approaches (explicit join entity and skip navigations) and get the best of both worlds.
To do than, you add the explicit entity and (optionally) navigations from/to it. The next is w/o collection navigations from User to UserRelation (with fully defined navigation you would need two ICollection<UserRelation> properties there):
public class User
{
public Guid Id { get; }
public ICollection<User> Parents { get; set; }
public ICollection<User> Children { get; set; }
}
public class UserRelation
{
public User Parent { get; set; }
public User Child { get; set; }
public Guid ParentId { get; set; }
public Guid ChildId { get; set; }
}
and required minimal fluent configuration
modelBuilder.Entity<User>()
.HasMany(e => e.Parents)
.WithMany(e => e.Children)
.UsingEntity<UserRelation>(
je => je.HasOne(e => e.Parent).WithMany(), // <-- here you would specify the corresponding collection nav property when exists
je => je.HasOne(e => e.Child).WithMany(), // <-- here you would specify the corresponding collection nav property when exists
je => je.ToTable("UserRelations")
);
The result is the same database model, but with different in-memory representation of the join entity and ability to query/manipulate it directly. Actually you can do the same with implicit entity, but in type unsafe way using sting names and object values which need to be cast to the appropriate type. This probably will improve in the future if they replace Dictionary<string, object> with some generic type, but for now explicit entity combined with skip navigations looks the best.
You can find (I guess better than mine) explanation of all this in the official EF Core documentation - Many-to-many and the whole Relationships section in general.
I have a Program entity like this
public class Program : IEntityBase
{
public int Id { get; set; }
public string ProgramCode { get; set; }
public string Name { get; set; }
public int DegreeTypeID { get; set; }
public DegreeType DegreeType { get; set; }
}
with programCode created as a unique key with this implementation
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Program>().HasAlternateKey(d => d.ProgramCode).HasName("AK_ProgramCode");
}
I have another entity ApplicantProgram with this definition
public class ApplicantProgram : IEntityBase
{
public int Id { get; set; }
public int ApplicantID { get; set; }
public Applicant Applicant { get; set; }
[Required]
public string FirstChoiceID { get; set; }
[Required]
public string SecondChoiceID { get; set; }
[Required]
public string ThirdChoiceID { get; set; }
public string SessionID { get; set; }
}
Which has FirstChoiceID, SecondChoiceID & ThirdChoiceID having ProgramCode in the program table.
Now these are my questions,
How do i get Program.Name property from ApplicantProgram knowing the FirstChoiceID that is to link to Program.ProgramCode?
Is it possible to create a Navigation property to program from ApplicantProgram?
How do i create a foreign key from ApplicantProgram to Program based off the ChoiceIDs that should link to Program.ProgramCode without using Program.Id?
Thank you for pausing to read this.
(1) How do I get Program.Name property from ApplicantProgram knowing the FirstChoiceID that is to link to Program.ProgramCode?
There is nothing specific to EF here, you could use the typical data correlation operator - join. Just because you have 3 related properties, you would need 3 joins as well:
var query =
from applicantProgram in db.ApplicantPrograms
join firstChoice in db.Programs on applicantProgram.FirstChoiceID equals firstChoice.ProgramCode
join secondChoice in db.Programs on applicantProgram.SecondChoiceID equals secondChoice.ProgramCode
join thirdChoice in db.Programs on applicantProgram.ThirdChoiceID equals thirdChoice.ProgramCode
select new
{
ApplicantProgram = applicantProgram,
FirstChoice = firstChoice,
SecondChoice = secondChoice,
ThirdChoice = thirdChoice,
};
Inside the select, you could get the whole related objects as above, or specific properties like firstChoice.Name, secondChoice.Name etc.
But you won't need all that in EF once you define the navigation properties, which leads us to:
(2) Is it possible to create a Navigation property to program from ApplicantProgram?
(3) How do I create a foreign key from ApplicantProgram to Program based off the ChoiceIDs that should link to Program.ProgramCode without using Program.Id?
These two are interrelated. While it's possible to define a FK without navigation property, the navigation property would allow you simple access to related entity properties inside the LINQ queries as well as simple eager loading the related entity as part of the entity which is using it.
Start by adding the 3 navigation properties (one for each FK property) in ApplicantProgram class:
public Program FirstChoice { get; set; }
public Program SecondChoice { get; set; }
public Program ThirdChoice { get; set; }
and the following fluent configuration:
builder.Entity<ApplicantProgram>()
.HasOne(e => e.FirstChoice)
.WithMany()
.HasForeignKey(e => e.FirstChoiceID)
.HasPrincipalKey(e => e.ProgramCode)
.OnDelete(DeleteBehavior.Restrict);
builder.Entity<ApplicantProgram>()
.HasOne(e => e.SecondChoice)
.WithMany()
.HasForeignKey(e => e.SecondChoiceID)
.HasPrincipalKey(e => e.ProgramCode)
.OnDelete(DeleteBehavior.Restrict);
builder.Entity<ApplicantProgram>()
.HasOne(e => e.ThirdChoice)
.WithMany()
.HasForeignKey(e => e.ThirdChoiceID)
.HasPrincipalKey(e => e.ProgramCode)
.OnDelete(DeleteBehavior.Restrict);
What we have here is the standard many-to-one relationship configuration - with HasOne(...) specifying the reference navigation property, WithMany() specifying no corresponding collection navigation property, HasForeighKey(...) specifying the corresponding FK property, and also the typical for multiple relationships to one and the same table turning off the cascade delete in order to avoid the multiple cascade paths problem.
What is specific thought (and is the EF Core improvement over EF6) is the HasPrincipalKey(...) method which allows you to specify other unique key property instead of the PK (by default) to be used by the FK relationship. Which in the combination with HasAlternateKey(...) on the other end allows to achieve the desired FK relationship setup.
And basically that's all. Now the query from (1) could be simply
var query =
from applicantProgram in db.ApplicantPrograms
select new
{
applicantProgram,
firstChoice = applicantProgram.FirstChoice,
secondChoice = applicantProgram.SecondChoice,
thirdChoice = applicantProgram.ThirdChoice,
};
Similar to (1), you could project the whole related objects or just properties you need.
Or you could get the ApplicantProgram instances with populated related Program properties by adding Include operators to the ApplicantProgram query (the so called eager loading):
var query = db.ApplicantPrograms
.Include(applicantProgram => applicantProgram.FirstChoice)
.Include(applicantProgram => applicantProgram.SecondChoice)
.Include(applicantProgram => applicantProgram.ThirdChoice);
When creating a new parent object, I need to attach a child to it in two places. I receive the error:
Unable to determine valid ordering list and single reference to child
My model looks like:
public class Child()
{
public int Id { get; set; }
public int ParentId { get; set; }
public string Name { get; set; }
}
public class Parent
{
public int Id { get; set; }
public virtual ICollection<Child> Children { get; set; }
public int FavouriteChildId { get; set; }
public virtual Child FavouriteChild { get; set; }
public void AddChild(string name)
{
var child = new Child { Name = name };
Children.Add(child);
if (Children.Count() == 1)
{
FavouriteChild = child;
}
}
}
I use fluent mapping for the Entity Framework configuration:
public class ParentMap : EntityTypeConfiguration<Parent>
{
public ParentMap()
{
this.HasRequired(t => t.FavouriteChild)
.WithMany()
.HasForeignKey(d => d.FavouriteChildId);
}
}
public class ChildMap : EntityTypeConfiguration<Child>
{
public ChildMap()
{
this.HasRequired(t => t.Parent)
.WithMany(t => t.Children)
.HasForeignKey(d => d.ParentId );
}
}
When inserting into the database, I use:
// Snipped out a standard EF Database context, repositories, generics, etc.
var newParent = new Parent();
newParent.AddChild("Felix");
newParent.AddChild("Naomi");
var parentSet = context.Set<Parent>();
parentSet.Add(newParent);
context.SaveChanges();
SaveChanges() throws the error given above.
I imagine that there is something wrong with the way I have my mapping setup and that Entity Framework cannot work out which way around to insert the Child.
I cannot do this in two steps because I need there to be at least one Favourite Child (thus the FavouriteChildId integer is not nullable). I want to also avoid moving the concept of "Favourite" to the Child entity (by adding a boolean property) because the child should never know that it's the favourite. Favouritism is a facet of the parent.
Thanks in advance.
I feel that you need to re-design it.
We seem to be forcing Entity into the Chicken or the Egg dilemma here.
The Parent needs a Child to be inserted before
it because FavouriteChildId is required for Parent to be inserted.
The Child needs a Parent to be inserted before it because ParentId is needed
for Child to be inserted.
Which one should be inserted first, the Parent or the Child?
I wondered if anyone can advise me on how to resolve a problem with regards to using FluentAPI to map a couple of tables.
I have Parent table that has our key called ID
Then a Child table with two fields idA & idB.
The primary key in the parent table links to either idA or idB, not both.
public Parent()
{
this.ChildA = new HashSet<Child>();
this.ChildA = new HashSet<Child>();
}
public virtual ICollection<Child> ChildA { get; set; }
public virtual ICollection<Child> ChildB{ get; set; }
}
public Child()
public virtual Parent parent { get; set; }
}
There is much I can do about the relationship/table design because it is legacy and cannot be changed. Just need to understand the correct FluentAPI to use to account for this issue. Above example it what I envisaged would be needed along with something like...
modelBuilder.Entity<Child>().HasRequired<Parent>(p => p.parent).WithMany(q => q.childs).HasForeignKey(r => r.idA);
modelBuilder.Entity<Child>().HasRequired<Parent>(p => p.parent).WithMany(q => q.childs).HasForeignKey(r => r.idB);
I believe I was able to get the correct mapping you are looking for. I added naviation properties to the POCO which allows Entity Framework to know how to use the foreign keys in code.
public class Child
{
public int Id { get; set; }
public virtual Parent ParentA { get; set; }
public virtual Parent ParentB { get; set; }
public Child() { }
}
To map these navigation properties to you already existing foreign key columns, I used the FluentAPI Map method.
modelBuilder.Entity<Child>().HasRequired<Parent>(p => p.ParentA).WithMany(q => q.ChildA).Map(m => m.MapKey("idA")).WillCascadeOnDelete(false);
modelBuilder.Entity<Child>().HasRequired<Parent>(p => p.ParentB).WithMany(q => q.ChildB).Map(m => m.MapKey("idB")).WillCascadeOnDelete(false);
With this, I have indicated ParentA populates the ChildAcollection, and ParentB populates the ChildB collection. The Map method is what allows me to map to your already existing FKs, and I don't have to include them with the POCO as a property.
Note that each POCO that maps to a table must have a primary key. Does your already existing child table have a PK? If not, you may have some further trouble. I recommend reading this SO post about it. Entity Framework: table without primary key
I'm using EF6 code-first to try to map to a desired schema
The requirement is to have an organisation table. Each organisation can have zero to many parents and zero to many children
And to have a single relationship table which holds the parent and child relationships
So I'm hoping to have a POCO like this:
public class Organisation
{
public Organisation()
{
Children = new Collection<Organisation>();
Parents = new Collection<Organisation>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Organisation> Parents { get; set; }
public virtual ICollection<Organisation> Children { get; set; }
}
and mapping like this:
public class OrganisationMap : EntityTypeConfiguration<Organisation>
{
public OrganisationMap()
{
HasMany(n => n.Children)
.WithMany()
.Map(m => m.ToTable("OrganisationRelationship").MapLeftKey("ParentId").MapRightKey("ChildId"));
HasMany(n => n.Parents)
.WithMany()
.Map(m => m.ToTable("OrganisationRelationship").MapLeftKey("ChildId").MapRightKey("ParentId"));
}
}
but if I try to add a migration after setting that up I receive error message:
OrganisationOrganisation1: Name: The EntitySet
'OrganisationOrganisation1' with schema 'dbo' and table
'OrganisationRelationship' was already defined. Each EntitySet must
refer to a unique schema and table.
Is there a way to achieve this goal?
For additional info. The reason I am holding the parent relationship is that when loading the data I need to find all nodes that have no parent and treat them as a collection of roots.
If there's a better way to do that than holding a parents collection I'd be totally happy!
If I add a migration with just the children collections then all works
Thanks for the input. I'm still trying to find out if it's possible to have one or more known roots to simplify building the graph but am interested if this mapping is possible...
The data sort of looks like this:
You're defining the mappings twice - try only once, something similar to the below
public OrganisationMap()
{
HasMany(n => n.Children)
.WithMany( n => n.Parents )
.Map(m =>
m.ToTable("OrganisationRelationship")
.MapLeftKey("ParentId")
.MapRightKey("ChildId"));
}
It's kind of hard to visualize an item having multiple parents at the same level.
Since you mentioned the word root in your question I think the ideal model would have a single parent and multiple children, now the parent of the item can have a parent, this way creating a hierarchy. You can implement the idea of an optional parent for root level elements.
If you want to implement the idea of multiple parents for a single item follow #Moho answer.
If you want hierarchy try the following:
public class Organisation
{
public Organisation()
{
Children = new Collection<Organisation>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual Organisation Parent { get; set; }
public virtual ICollection<Organisation> Children { get; set; }
public IEnumerable<Organisation> Ancestors
{
get
{
var item = this;
while (item.Parent != null)
{
yield return item.Parent;
item = item.Parent;
}
}
}
}
and your map:
public class OrganisationMap : EntityTypeConfiguration<Organisation>
{
public OrganisationMap()
{
HasOptional(n => n.Parent)
.WithMany(n => n.Children)
.Map(m => m.MapKey("ParentId"));
Ignore(n => n.Ancestors);
}
}
UPDATE:
you can make all the parents be loaded from the DB in a single pull by calling Include. This loads the parent for every item that is pulled.
db.Organisations.Include(x => x.Parent).Include(x => x.Children).Where(...your condition)