Using both many-to-many and one-to-many to same entity - c#

I have a many-to-many association in EF Code-First (as explained in this question), and I want to use a one-to-many to the same entity as well. The problem is EF does not produce the right database scheme. Code:
public class A
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<B> ObjectsOfB { get; set; }
}
public class B
{
public int Id { get; set; }
public virtual A ObjectA { get; set; }
public virtual ICollection<A> OtherObjectsOfA { get; set; }
}
When I remove the ObjectA property of class B the many-to-many association is generated correctly.
When generated incorrectly, entity B gets 2 foreign keys to A, and entity A gets 1 foreign key to B (like a many-to-one relation).

If you have more than one navigation property refering to the same entity EF does not know where the inverse navigation property on the other entity belongs to. In your example: Does A.ObjectsOfB refer to B.ObjectA or to B.OtherObjectsOfA? Both would be possible and a valid model.
Now, EF does not throw an exception like "cannot determine relationships unambiguously" or something. Instead it decides that B.ObjectA refers to a third endpoint in B which is not exposed as navigation property in the model. This creates the first foreign key in table B. The two navigation properties in B refer to two endpoints in A which are also not exposed in the model: B.ObjectA creats the second foreign key in table B and B.OtherObjectsOfA creates a foreign key in table A.
To fix this you must specify the relationships explicitely.
Option one (the easiest way) is to use the InverseProperty attribute:
public class A
{
public int Id { get; set; }
public string Name { get; set; }
[InverseProperty("OtherObjectsOfA")]
public virtual ICollection<B> ObjectsOfB { get; set; }
}
This defines that A.ObjectsOfB is part of a many-to-many relation to B.OtherObjectsOfA.
The other option is to define the relationships completely in Fluent API:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<A>()
.HasMany(a => a.ObjectsOfB)
.WithMany(b => b.OtherObjectsOfA)
.Map(x =>
{
x.MapLeftKey("AId");
x.MapRightKey("BId");
x.ToTable("ABs");
});
modelBuilder.Entity<B>()
.HasRequired(b => b.ObjectA) // or HasOptional
.WithMany()
.WillCascadeOnDelete(false); // not sure if necessary, you can try it
// without if you want cascading delete
}

If table B has foreign key to table A then class B has navigation property to A and A have navigation property to ICollection<A>.
If table B has many to many relation with table A then class A must have ICollection<B> and class B must have ICollection<A>.
Try that, maybe this will clarify your request from the EF.

Related

EF Core Foreign Key via Fluent API

I have a 2 classes:
public class Item
{
public int Id { get; set; }
public string ItemName { get; set; }
}
public class ItemStats //inhenrit from Item
{
public int Id { get; set; }
public int MaxEnhanceLevel { get; set; }
public Item Item { get; set; }
}
This is a TPT but since it's not supported out of the box I can't use inheritance. I know how to achieve this using data annotation
[ForeignKey(nameof(Item))]
public int Id { get; set; }
But how can I do this via FluentAPI? I don't want data annotation in my Entitie Classes.
What you have is a One-to-one relationship with single navigation property, principal entity Item and dependent entity ItemStats, using the so called shared primary key association, where the dependent entity PK is also a FK to the principal entity.
Fluent API for one-to-one relationships are HasOne, WithOne, HasForeignKey and HasPrincipalKey. Please note that the generic type arguments to HasForeignKey and HasPrincipalKey (which normally are omitted for one-to-many relationship) here are important because they indentify which entity is principal and which - dependent.
With that being said, the fluent configuration for your model is:
modelBuilder.Entity<ItemStats>()
.HasOne(e => e.Item)
.WithOne()
.HasForeignKey<ItemStats>(e => e.Id);

Can't define two relationships between the same two entities

I can't get EF 6 Code First to map the following two relationships the way I want them.
There are two entities: Template and TemplateVersion.
Every TemplateVersion has exactly one ParentTemplate.
A Template has a collection of TemplateVersions.
This was the first, simple, 1:many relationship, with navigation properties on both sides.
Now for the second:
From all TemplateVersions associated to a Template, only one (e.g. the "newest") is the CurrentTemplateVersion for that Template.
So: Template has a navigation property CurrentVersion, and an associated property CurrentVersionId.
There is no corresponding navigation property on the TemplateVersion side.
So, I would say, this second Template : TemplateVersion relation is 0..1 : 1.
Here are the models:
public class Template
{
public int Id { get; set; }
[...]
public virtual int CurrentVersionId { get; set; }
public virtual TemplateVersion CurrentVersion { get; set; }
public virtual ICollection<TemplateVersion> Versions { get; set; }
}
public class TemplateVersion
{
public int Id { get; set; }
[...]
public virtual int ParentTemplateId { get; set; }
public virtual Template ParentTemplate { get; set; }
}
I like to keep my model classes free from DB specifics, so I defined the relationships in the context:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Template>()
.HasMany(template => template.Versions)
.WithRequired(version => version.ParentTemplate)
;
modelBuilder.Entity<Template>()
.HasRequired(template => template.CurrentVersion)
.WithOptional()
;
}
The problem is, the 2nd relation doesn't work as expected.
Using EF Power Tools plugin, I reverse-engineer the model diagram. Here's what I get:
1st relation, 1:many (ok)
2nd relation, 0..1:1
Notice that CurrentVersionId property is not part of the relation, and Template.Id is !
The generated DB tables mirror exactly this: CurrentVersionId is not part of any foreign key, and Id on the Template table incorrectly is defined as a foreign key to Id on the TemplateVersion table.
What am I missing ?
PS. Even if I remove the 1st relationship completely, the 2nd one is the same.
In a one-to-one relationship, EF requires the PK of the dependent end also has to be the FK of the relationship:
public class Foo
{
public int Id{get;set;}
//...
}
public class Boo
{
[Key,ForeignKey("Foo")]
public int FooId{get;set;}
public virtual Foo Foo{get;set;}
//...
}
If you need that TemplateVersion has its own Id, then, to resolve your issue you could configure that relationship this way:
modelBuilder.Entity<Template>()
.HasRequired(template => template.CurrentVersion)
.WithMany().HasForeignKey(t=>t.CurrentVersionId);

The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations

"The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations."
I am getting this error in Entity Framework 4.4 when updating/migrating the database, but I am not trying to specify a 1:1 relationship. I want something like this:
public class EntityA
{
public int ID { get; set; }
public int EntityBID { get; set; }
[ForeignKey("EntityBID")]
public virtual EntityB EntityB { get; set; }
}
public class EntityB
{
public int ID { get; set; }
public Nullable<int> PreferredEntityAID { get; set; }
[ForeignKey("PreferredEntityAID")]
public virtual EntityA PreferredEntityA { get; set; }
}
where EntityA must have an EntityB parent, whereas EntityB can have a preferred EntityA child, but doesn't have to. The preferred child should be one of the children associated with the parent, but I don't know how to enforce this in the database. I'm planning on enforcing it programmatically.
How do I get around this error or what is a better way of accomplishing these relationships?
Entity Framework Code-First conventions are assuming that EntityA.EntityB and EntityB.PreferredEntityA belong to the same relationship and are the inverse navigation properties of each other. Because both navigation properties are references (not collections) EF infers a one-to-one relationship.
Since you actually want two one-to-many relationships you must override the conventions. With your model it's only possible with Fluent API:
modelBuilder.Entity<EntityA>()
.HasRequired(a => a.EntityB)
.WithMany()
.HasForeignKey(a => a.EntityBID);
modelBuilder.Entity<EntityB>()
.HasOptional(b => b.PreferredEntityA)
.WithMany()
.HasForeignKey(b => b.PreferredEntityAID);
(If you use this you can remove the [ForeignKey] attributes.)
You cannot specify a mapping that would ensure that the preferred child is always one of the associated childs.
If you don't want to use Fluent API but only data annotations you can add a collection property in EntityB and relate it to EntityA.EntityB using the [InverseProperty] attribute:
public class EntityB
{
public int ID { get; set; }
public Nullable<int> PreferredEntityAID { get; set; }
[ForeignKey("PreferredEntityAID")]
public virtual EntityA PreferredEntityA { get; set; }
[InverseProperty("EntityB")] // <- Navigation property name in EntityA
public virtual ICollection<EntityA> EntityAs { get; set; }
}

Using fluent API to set up composite foreign key in one-to-one relation in legacy database

I'm just a beginner in EF code first model. Given two POCO classes mapped to current legacy MS SQL database. They are associated with a composite foreign key setting up one to many relation. Since it's actually one-to-one relation I'd like to have corresponding navigation properties in my POCO objects and do mapping in fluent API. Here is my example:
public partial class Answer
{
//primary key
public int id { get; set; }
//foreign keys
public int question { get; set; }
public int assignedForm { get; set; }
//simple fields
public short state { get; set; }
public int author { get; set; }
//navigation property
public virtual AssignedQuestion AssignedQuestion { get; set; }
}
public partial class AssignedQuestion
{
// primary keys
public int id { get; set; }
public int assignedForm { get; set; }
//simple field
public string content { get; set; }
//navigation property
//public virtual ICollection<Answer> Answers { get; set; }
public virtual Answer Answer { get; set; }
}
If I wanted to do one-to-many relation I would simply uncomment "Answers" collection and have Fluent API mapping:
modelBuilder.Entity<AssignedQuestion>()
.HasKey(q => new { q.id, q.assignedForm });
modelBuilder.Entity<Answer>()
.HasRequired(a => a.AssignedQuestion)
.WithMany(aq=>aq.Answers)
.HasForeignKey(a => new { a.question,a.assignedForm});
My goal is to go with one-to-one relation and use "Answer" property in AssignedQuestion with such Fluent API as:
modelBuilder.Entity<AssignedQuestion>()
.HasKey(q => new { q.id, q.assignedForm });
modelBuilder.Entity<Answer>()
.HasRequired(a => a.AssignedQuestion)
.WithOptional(aq => aq.Answer);
//.HasForeignKey(a => new { a.question, a.assignedForm });
The problem is I can't specify exactly foreign key fields (as in previous example) and uncomment HasForeignKey call. In this case EF tries to join tables using conventional field names "AssignedQuestion_ID" and "AssignedQuestion_AssignedForm" instead of "question" and "assignedForm" in Answer table. Is there a walkaround in Fluent API other than changing field names?
It is not one-to-one relationship so your first mapping is correct. The reason why it is one-to-many is that EF understands one-to-one only when build on PKs on both sides. If AssignedQuestion has PK id and assignedForm your Answer will need to have FK and PK on its id and assignedForm otherwise EF doesn't see it as one-to-one relation. Even if you mark your question and assignedForm with unique constaint in database (to make it one-to-one in the database) EF will still not be able to handle it as one-to-one because it doesn't support unique constraints yet (except PK).

Entity has two properties which both reference the same entity type in one-to-many relationship

This seems like the most common relationship but for some reason I cannot get code-first EF working. When I run the code below I get the following error:
*{"Introducing FOREIGN KEY constraint 'Recording_RecordingLocation' on table 'Recordings' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.\r\nCould not create constraint. See previous errors."}*
I have researched SO and other places but have not been able to figure this out. I must be having a minor stroke so if this is duplicative I apologize. I don't think it is because all other reference questions I found were for many-to-many relationships... many-to-one.
My scenario is quite simple...
I have an entity (Recording) that has two required properties RecordingLocation and EditingLocation which are both of the same type WorkLocation. Each Recording has exactly one RecordingLocation and one EditingLocation (not many-to-many). I also have the requisite navigation properties.
Each WorkLocation is stand-alone and is not intrinsically linked to the Recording -- it's just a physical place where some work on that Recording took place. So when I delete a recording I do not want to delete the associated WorkLocations.
public class Recording
{
[Key]
public virtual int Id { get; set; }
//... other properties not shown here
public virtual int RecordingLocationId { get; set; }
public virtual WorkLocation RecordingLocation { get; set; }
public virtual int EditingLocationId { get; set; }
public virtual WorkLocation EditingLocation { get; set; }
{
public class WorkLocation
{
[Key]
public virtual int Id { get; set; }
public virtual WorkLocationType Type { get; set; }
public virtual string Description { get; set; }
public virtual LogicalStatus Status { get; set; }
}
// I'll use this on the front-end to filter a selection list
// but don't necessarily assume a Work Location is bound to only items of this type
public enum WorkLocationType
{
RecordingLocation,
EditingLocation,
MasteringLocation
}
What am I missing to get this working?
Your navigation properties RecordingLocation and EditingLocation are required because the corresponding foreign key properties are not nullable. By convention EF assumes that cascading delete is active for a required one-to-many relationship which causes a problem if you have more than one such relationship refering to the same table - hence the exception.
You must disable cascading delete (also your business logic seems to require it) which is only possible in Fluent API:
public class MyContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Recording>()
.HasRequired(r => r.RecordingLocation)
.WithMany()
.HasForeignKey(f => f.RecordingLocationId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Recording>()
.HasRequired(r => r.EditingLocation)
.WithMany()
.HasForeignKey(f => f.EditingLocationId)
.WillCascadeOnDelete(false);
}
}

Categories