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)
Related
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 have the following two classes:
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Child> Children { get; set; }
public Parent()
{
Children = new List<Child>();
}
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public virtual Parent Parent { get; set; }
}
And then the Fluent setting in the data context:
modelBuilder.Entity<Child>()
.HasKey(c => new { c.Id, c.ParentId })
.HasOptional(c => c.Parent)
.WithMany(p => p.Children)
.WillCascadeOnDelete(true);
I'm doing this so that I actually delete the child object when I say parent.Children.Remove(aChild);, not just set its ParentId null.
The problem is, I'm getting the error "Violation of PRIMARY KEY constraint 'PK_dbo.Child'. Cannot insert duplicate key in object 'dbo.Child'. The duplicate key value is (0, 2)." when I create a fresh parent with children, and then db.SaveChanges():
Parent p = new Parent { Name = "Quarterbacks" };
p.Children.Add(new Child { Name = "Brady" });
p.Children.Add(new Child { Name = "P. Manning" });
p.Children.Add(new Child { Name = "Kaepernick" });
p.Children.Add(new Child { Name = "Wilson" });
p.Children.Add(new Child { Name = "Rodgers" });
db.Parents.Add(p);
db.SaveChanges();
I thought integer primary keys are auto-generated on insertion. What should I do? Should I change the keys to strings and create GUID keys in C# for this to work?
I believe that for composite keys no part of the key is marked as an identity by default, even not for integer keys (which would be the case for a simple, non-composite key). You probably must add the Identity option explicitly to the Child entity's Id property:
modelBuilder.Entity<Child>()
.Property(c => c.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
I'm a bit worried that your second part of the Child's key is nullable. Perhaps EF does allow that, but the database not necessarily: I think, with SQL Server for example nullable key parts are forbidden. Maybe, other databases can deal with that. You're apparently aiming for an identifying relationship (the one that deletes the child from the database when it is removed from the parent) which however needs a required (not an optional) relationship between parent and child, as far as I know.
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)
I am using Entity Framework 4.3.1 against a SQL Server 2012 database and I am using the POCO approach. I am getting the following error and I am wondering if anyone can explain how to fix it:
ModelValidationException
One or more validation errors were detected during model generation:
\tSystem.Data.Entity.Edm.EdmAssociationConstraint: : The number of properties in the Dependent and Principal Roles in a relationship constraint must be identical.
There is no InnerException available for any further information.
I cannot change the database schema and it is a little odd, but here it is...
** are the primary key (notice I have composite primary keys)
(FK) Denotes a foreign key
Here are the tables (if it helps I can post the SQL to generate them but I do not think the tables are actually the problem as the exception is in the validation of the model):
One
-
**OneId int not null
**TwoId int not null (FK)
**ThreeId int not null (FK)
Name nvarchar(50) not null
Two
-
**TwoId int not null
**ThreeId int not null (FK)
Name nvarchar(50) not null
Three
-
**ThreeId not null
Name nvarchar(50) not null
Here are the entities (notice that I am including the foreign keys in the model but other than that pretty standard):
public class Three
{
public int ThreeId { get; set; }
public string Name { get; set; }
public virtual ICollection<Two> Twos { get; private set; }
public virtual ICollection<One> Ones { get; private set; }
public void AddOne(One one)
{
if (one == null)
throw new ArgumentNullException("two");
if (Ones == null)
Ones = new List<One>();
if (!Ones.Contains(one))
Ones.Add(one);
one.Three = this;
}
public void AddTwo(Two two)
{
if (two == null)
throw new ArgumentNullException("two");
if (Twos == null)
Twos = new List<Two>();
if (!Twos.Contains(two))
Twos.Add(two);
two.Three = this;
}
}
public class Two
{
public int TwoId { get; set; }
public int ThreeId { get; set; }
public string Name { get; set; }
public virtual Three Three { get; set; }
public virtual ICollection<One> Ones { get; private set; }
public void AddOne(One one)
{
if (one == null)
throw new ArgumentNullException("two");
if (Ones == null)
Ones = new List<One>();
if (!Ones.Contains(one))
Ones.Add(one);
one.Two = this;
}
}
public class One
{
public int OneId { get; set; }
public int TwoId { get; set; }
public int ThreeId { get; set; }
public virtual Two Two { get; set; }
public virtual Three Three { get; set; }
}
And here is the data context:
public class DbCtx : DbContext
{
public DbCtx(string connectionString)
: base(connectionString)
{
Ones = Set<One>();
Twos = Set<Two>();
Threes = Set<Three>();
}
public DbSet<One> Ones { get; private set; }
public DbSet<Two> Twos { get; private set; }
public DbSet<Three> Threes { get; private set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
var one = modelBuilder.Entity<One>();
one.ToTable("One");
one.HasKey(d => new
{
d.OneId,
d.TwoId,
d.ThreeId
});
one.Property(d => d.OneId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
one.HasRequired(t => t.Two)
.WithMany(s => s.Ones)
.HasForeignKey(t => t.TwoId);
one.HasRequired(t => t.Three)
.WithMany(s => s.Ones)
.HasForeignKey(t => t.ThreeId);
var two = modelBuilder.Entity<Two>();
two.ToTable("Two");
two.HasKey(d => new
{
d.TwoId,
d.ThreeId
});
two.Property(p => p.TwoId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
two.HasRequired(t => t.Three)
.WithMany(s => s.Twos)
.HasForeignKey(t => t.ThreeId);
var three = modelBuilder.Entity<Three>();
three.ToTable("Three");
three.HasKey(s => s.ThreeId);
three.Property(p => p.ThreeId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
base.OnModelCreating(modelBuilder);
}
}
Finally, this is a snippet of code to cause the exception:
using (var ctx = new DbCtx(#"....."))
{
Console.WriteLine(ctx.Twos.Count());
}
The reason for the error are incorrectly configured relations in your model. This is not correct:
one.HasRequired(t => t.Two)
.WithMany(s => s.Ones)
.HasForeignKey(t => t.TwoId);
one.HasRequired(t => t.Three)
.WithMany(s => s.Ones)
.HasForeignKey(t => t.ThreeId);
It should be:
one.HasRequired(t => t.Two)
.WithMany(s => s.Ones)
.HasForeignKey(t => new { t.TwoId, t.ThreeId });
Because dependent's FK must contain all columns of principal PK. You must also remove navigation property from Three to One.
This can also be caused by Code first from Database.
I had several views that I brought in that did not have an obvious key field according to Entity Framework conventions. The code generated put the [Key] attribute on the wrong field. In fact, it could not detect any uniqueness, so it put the [Key] attribute on all the fields.
I was able to remove all of the extra Key attributes to make the error go away.
Note for EF5+:
.HasForeignKey has been deprecated from EF 5: List of available methods (https://msdn.microsoft.com/en-us/library/system.data.entity.modelconfiguration.configuration.manytomanyassociationmappingconfiguration_methods(v=vs.103).aspx)
- MapLeftKey
- MapRightKey
- ToTable
If one were to need Many to Many where one 'Many' is to an Entity with a CompositeKey is:
one.HasKey(t => new { t.TwoId, t.ThreeId });
one.HasRequired(t => t.Two)
.WithMany(s => s.Ones)
.Map(m=>m.MapLeftKey("OneId").MapRIghtKey(new string[]{"TwoId", "ThreeId"}))
I have two tables in my database. One is called Users, and the other is called Widgets. The Widgets table represents 3 entities in my code model. One of the entities, Widget, is a parent class for the other two entities, WidgetTypeA and WidgetTypeB. Both WidgetTypeA and WidgetTypeB have navigation properties to the User entity, which is persisted to the Users table in the database. I'm having trouble getting Code First to use the same foreign key for both the WidgetTypeA and WidgetTypeB entities (UserId). Does anyone know how to do this? It seems like it should be a common problem with Table Per Hierarchy mapping.
My entity classes are as follows:
public class Widget
{
public int Id { get; set; }
public string Name { get; set; }
}
class WidgetMap : EntityTypeConfiguration<Widget>
{
public WidgetMap()
{
ToTable("Widgets");
HasKey(w => w.Id);
Property(w => w.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(w => w.Name)
.IsRequired()
.HasMaxLength(75)
.IsUnicode(true);
}
}
public class WidgetTypeA : Widget
{
public int UserId { get; set; }
public virtual User User { get; set; }
public string Color { get; set; }
public int DepthLevel { get; set; }
}
class WidgetTypeAMap : EntityTypeConfiguration<WidgetTypeA>
{
public WidgetTypeAMap()
{
Map(w => w.Requires("WidgetTypeId").HasValue(1));
HasRequired(w => w.User)
.WithMany(u => u.WidgetTypeAs)
.HasForeignKey(w => w.UserId)
.WillCascadeOnDelete(false);
Property(w => w.Color)
.IsOptional()
.IsUnicode(true)
.HasMaxLength(75);
Property(w => w.DepthLevel)
.IsOptional();
}
}
public class WidgetTypeB : Widget
{
public int UserId { get; set; }
public virtual User User { get; set; }
}
class WidgetTypeBMap : EntityTypeConfiguration<WidgetTypeB>
{
public WidgetTypeBMap()
{
Map(w => w.Requires("WidgetTypeId").HasValue(2));
HasRequired(w => w.User)
.WithMany(u => u.WidgetTypeBs)
.HasForeignKey(w => w.UserId)
.WillCascadeOnDelete(false);
}
}
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public int Age { get; set; }
public virtual ICollection<WidgetTypeA> WidgetTypeAs { get; set; }
public virtual ICollection<WidgetTypeB> WidgetTypeBs { get; set; }
}
class UserMap : EntityTypeConfiguration<User>
{
public UserMap()
{
ToTable("Users");
HasKey(u => u.Id);
Property(u => u.Username)
.IsRequired()
.HasMaxLength(75)
.IsUnicode(true);
Property(u => u.Age)
.IsRequired();
}
}
At any rate, I keep getting the error
Invalid column name 'UserId1'
when I try to perform the following operations:
using (var entities = new MyEntities())
{
User u = new User
{
Username = "Frank",
Age = 14
};
entities.Users.Add(u);
entities.SaveChanges();
WidgetTypeA wa1 = new WidgetTypeA
{
Name = "0SDF81",
UserId = u.Id,
DepthLevel = 6
};
entities.WidgetTypeAs.Add(wa1);
entities.SaveChanges();
}
Not sure if this can be fixed or not. I can always specify a second UserId foreign key for the Widgets table, but that seems pointless. Perhaps there's a way to do this using Fluent API?
You cannot map properties defined in different derived entities to the same column. That is limitation in EF. If your WidgetTypeA has UserId property and your WidgetTypeB has UserId property they must be different columns in the database. It should work if you move both UserId and User properties from derived types to the parent Widget type.
I know its a long way late, but hopefully may help other readers.
Although Ladislav was correct that using a mapped Foreign Key is not supported in EF6, I did find a useful workaround.
It is possible to define a computed column specification whose expression simply refers to the original column. Userid in the description above. This can be used as the discriminator for the TPH mapping. With this approach, the column need not be persisted, but can be used for TPH, with the original column being available for use as a foreign key.