Mapping Parent and Children relationship collections to a single table in EF6 - c#

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&ltOrganisation>
{
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)

Related

EntityFramework Include Without Guarantee of Existence

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.

How to attach a new entity to a single property and a list at the same time

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?

Mapping unusual table relationships

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

EF Mapping One To Many

I am trying to figure out how to map the following relationship:
The "Relation" entity requires a "Node" and a "RelatedNode".
The Node entity has a collection of "Relations" (HasMany), where the Node is required to be the Relation.Node OR Relation.RelatedNode.
The current mapping is resulting in a table that looks like this:
[Id],[NodeId],[RelatedNodeId],[RelationType],[Node_Id]
[Node_Id] is getting automatically created, and this is what I am trying to avoid.
Relation Entity:
public class Relation
{
private Relation()
{
}
public Relation(int nodeId, int relatedNodeId)
{
NodeId = nodeId;
RelatedNodeId = relatedNodeId;
}
public Relation(Node node, Node relatedNode)
{
Node = node;
RelatedNode = relatedNode;
}
public int Id { get; set; }
public int NodeId { get; set; }
public Node Node { get; set; }
public int RelatedNodeId { get; set; }
public Node RelatedNode { get; set; }
public RelationType RelationType { get; set; }
}
Fluent-API:
// Relation
modelBuilder.Entity<Relation>().Map(m =>
{
m.ToTable("Relations");
});
modelBuilder.Entity<Relation>()
.HasKey(t => t.Id)
.Property(t => t.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<Relation>().HasRequired(t => t.Node).
WithMany().HasForeignKey(t => t.NodeId).WillCascadeOnDelete(false);
modelBuilder.Entity<Relation>().HasRequired(t => t.RelatedNode).
WithMany().HasForeignKey(t => t.RelatedNodeId).WillCascadeOnDelete(false);
// Node
modelBuilder.Entity<Node>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Nodes");
});
modelBuilder.Entity<Node>().HasMany(t => t.Relations);
Remove this line
modelBuilder.Entity<Node>().HasMany(t => t.Relations);
You've already specified this relationship above.
Entity Framework is adding that column to represent a relationship from Relation to Node. Since you specify the HasForeignKey for "Node" and "RelatedNode", it creates the "NodeId" and "RelatedNodeId" columns (as you expect and want).
The "Node_Id" column is what EF would generate when it needs a FK that hasn't been specified. So, somewhere EF is being told that there is a relationship from Node to Relation and the FK is not being specified (EG the line removed)

How to explicitly load relations for multiple models at once in Entity Framework?

I have a model similar to the following:
class Parent {
public int Id { get; set; }
public string Name { get;set; }
public ICollection<Child> Children { get; set; }
public GrandChildren SpecialGrandChild {
get {
return Children.SelectMany(c => c.Children).Where(...).Single();
}
}
}
class Child {
public int Id { get; set; }
public int ParentId { get; set; }
public Parent Parent { get; set; }
public ICollection<GrandChild> Children { get; set; }
}
class GrandChild {
public int Id { get; set; }
public string Name { get;set; }
public int ParentId { get; set; }
public Child Parent { get; set; }
}
I also have a fairly complex query involving all three tables. From that query I want to extract all the Parent objects, and I will be displaying a property of the SpecialGrandChild for each one.
The problem is that if I do:
query.Include(p => p.Children.Select(c => c.Children));
EF will generate an ungodly sql query, and take a ton of time to build the query (on some cases over 10 seconds!). The query is cached so further calls are much faster. If I drop the Include call, I do not get such a bad first-call performance, but of course I get a worse performance as I will be doing M*N+1 queries (for each Parent, fetch the Children, and for each Child fetch the GrandChildren).
So the question is: can I explicitly load all the Children and GrandChildren for all the loaded Parents in a single call? If so, how can I do so?
I tried querying all the Childs for the currently loaded Parents as follows:
var ids = parents.Select(p => p.Id);
(from c in Childs where ids.Contains(c.ParentId) select c).Include("Children").Load();
But that call does not tell EF that all the associated Childs are loaded so it still goes to the DB when I access the association properties.
Load your data in two steps:
var dbParent = ...; // query all Parent's
var dbChild = ...; // query all Child's
var parents = dbParent.Include(p => p.Children).ToList();
dbChild.Include(p => p.Children).toList();
That should make parents have a list of all parents, and because of tracking, each parent will have each of its children.
If you have to apply a filter condition on parent, you should make it on children too.
Since I don't know enough about your parent variable and a context your parent objects are probably attached on (or maybe not) I can't give you a precise answer.
But what you're looking for should look like this where ctx is an instance of your context and Parents a DbSet Property:
IQueryable<Parent> query = ctx.Parents.Include("Children.Children");
List<Parent> myTree = query.toList();

Categories