Does EF 4 support unidirectional one-to-many associations, as in:
public class Parent
{
public int Id { get; set; }
public string Something { get; set; }
public List<Child> AllMyChildren { get; set; }
}
public class Child
{
public int Id { get; set; }
public string Anotherthing { get; set; }
// I don't want a back-reference to the Parent!
// public int ParentId { get; set; }
}
When I try to compile my project with an association between Parent and Child where End2 Navigation is blank (because I unchecked the End2 Navigation Property checkbox in the Add Association dialog), I get
Error 2027: No mapping specified for the following EntitySet/AssociationSet - Child.
UPDATE:
And what if I just have a List or similar property on Parent rather than a List? Do I need to create a wrapping type to hold the String so that I can also hold a back-reference to Parent?
Belive that this would work using Fluent API (Only way of deifning unidirectional associations)
modelBuilder.Entity<Child>()
.HasKey(t => t.Id);
modelBuilder.Entity<Parent>()
.HasMany(p => p.AllMyChildren)
.WithRequiredPrincipal();
http://msdn.microsoft.com/en-us/library/hh295843(v=VS.103).aspx
Related
I have two entities say Parent, and Child; each parent can have at most two child references. I have set up my entities as follows:
class Parent
{
[Key]
public int ParentId { get; set; }
public int PrimaryChildId{ get; set; }
public Child PrimaryChild { get; set; }
public int SecondaryChildId { get; set; }
public Child? SecondaryChild { get; set; }
// remaining properties
}
class Child
{
[Key]
public int ChildId { get; set; }
public int ParentId { get; set; }
public Parent Parent {get; set; }
// remaining child properties
}
In the DbContext.OnModelCreating I have this code:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Parent>(builder =>
{
builder.HasOne(p => p.PrimaryChild);
builder.HasOne(p => p.SecondaryChild);
});
}
This isn't enough to accomplish what I'm trying to achieve here. I get an error:
Unable to determine the relationship represented by navigation property 'Child.Parent' of type 'Parent'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'
I've tried to set up the relationship from the Child entity, but I get different errors because this makes me set up two relationships for the same property. I don't want to have two navigation properties on my child when I know only one will be used at a time as it would make for a confusing model.
I've searched the internet a bit, but I'm not having any luck finding relationships that are set up in this manner.
I've been trying something like this for the last few days, and after trying all sorts of Data Annotations and Fluent API nonsense, the cleanest solution I could come up with turned out to be very simple which requires neither. It only requires adding a 'private' constructor to the Parent class (or a 'protected' one if you're using Lazy Loading) into which your 'DbContext' object is injected. Just set up your 'Parent' and 'Child' classes as a normal one-to-many relationship, and with your database context now available from within the 'Parent' entity, you can make 'PrimaryChild' and 'SecondaryChild' simply return a query from the database using the Find() method. The Find() method also makes use of caching, so if you call the getter more than once, it will only make one trip to the database.
Here is the documentation on this ability: https://learn.microsoft.com/en-us/ef/core/modeling/constructors#injecting-services
Note: the 'PrimaryChild' and 'SecondaryChild' properties are read-only. To modify them, set the 'PrimaryChildId' and 'SecondaryChildId' properties.
class Parent
{
public Parent() { }
private MyDbContext Context { get; set; }
// make the following constructor 'protected' if you're using Lazy Loading
private Parent(MyDbContext Context) { this.Context = Context; }
[Key]
public int ParentId { get; set; }
public int PrimaryChildId { get; set; }
public Child PrimaryChild { get { return Context.Children.Find(PrimaryChildId); } }
public int? SecondaryChildId { get; set; }
public Child SecondaryChild { get { return Context.Children.Find(SecondaryChildId); } }
// remaining properties
}
class Child
{
[Key]
public int ChildId { get; set; }
public int ParentId { get; set; }
public Parent Parent { get; set; }
// remaining child properties
}
I have two models:
Child
public class Child {
public int Id { get; set; }
public string Name { get; set; }
}
Parent
public class Parent {
public int Id { get; set; }
public int? FirstChildId { get; set; }
public Child FirstChild { get; set; }
public int? SecondChildId { get; set; }
public Child SecondChild { get; set; }
}
There is a strict relationship that one child has only one parent and one parent has zero-or-one FirstChild and zero-or-one SecondChild.
As far as I know, if we want to make a relationship to the same table (model) twice then we need to have a one-to-many relationship instead of one-to-one.
Therefore, I've modified the Child class by adding Many relationships.
public class Child {
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Parent> ParentsFirstChild { get; set; }
public ICollection<Parent> ParentsSecondChild { get; set; }
}
So far I have the next Fluent API modelBuilder that works
modelBuilder.Entity<Child>()
.HasMany(f => f.ParentsFirstChild)
.WithOptional(p => p.FirstChild)
.HasForeignKey(p => p.FirstChildId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Child>()
.HasMany(f => f.ParentsSecondChild)
.WithOptional(p => p.SecondChild)
.HasForeignKey(p => p.SecondChildId)
.WillCascadeOnDelete(false);
The problem is that if we'd put
.WillCascadeOnDelete(true)
It produces the error
Introducing FOREIGN KEY constraint
FK_dbo.Parent_dbo.Child_ParentsFirstChildId on table Parent may cause
cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON
UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
I see that EF thinks that a Child could be related to other Parents. That's why it could be cyclic, right?
Question
How to create a mapping through either DataAnnotations or Fluent API, so that in case of presence (both optional) one or both children (same class/model) during deleting of a parent, children will be removed too?
For my models, I have RealEstateTransaction and Agent. A RealEstateTransaction can have a ListingAgent and a SellingAgent.
How would I build out my models to make that relation? Something like this?
public class Agent
{
public long AgentId { get; set; }
public List<RealEstateTransaction> ListingRealEstateTransactions { get; set; }
public List<RealEstateTransaction> SellingRealEstateTransactions { get; set; }
}
public class RealEstateTransaction
{
public long RealEstateTransactionId { get; set; }
public long ListingAgentId { get; set; }
public Agent ListingAgent { get; set; }
public long SellingAgentId { get; set; }
public Agent SellingAgent { get; set; }
}
Something like this?
Indeed.
But it requires some additional mapping of the navigation properties (the FK property names follow the EF Core conventions, so no mapping is required for that). Normally EF Core is able to pair the navigation properties of the two ends of the relationship, but not when you have two relationships to one and the same entity. In such case you need to tell which navigation property of the principal corresponds to (is inverse of) the each navigation property in dependent.
Normally you can do that by either data annotation ([InverseProperty] attribute) or fluent API. But since more than one FK relationships to the same entity also introduce the so called multiple cascade paths issue with SqlServer (and some other databases), you'd need also to turn the cascade delete off for at least one of the relationships, and this can be done only with fluent API, so the minimal mapping required is something like this:
modelBuilder.Entity<Agent>()
.HasMany(e => e.ListingRealEstateTransactions)
.WithOne(e => e.ListingAgent)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Agent>()
.HasMany(e => e.SellingRealEstateTransactions)
.WithOne(e => e.SellingAgent)
.OnDelete(DeleteBehavior.Restrict);
You can skip .OnDelete(DeleteBehavior.Restrict) for one of the relationships (or change it to DeleteBehavior.Cascade which is the default for required relationships like these). Note that deleting the principal (Agent in this case) will require first manually deleting all the related dependents for each relationship having DeleteBehavior.Restrict.
That's the mandatory part. Optionally, if the RealEstateTransaction entity serves only as standard many-to-many "link" entity (has no additional properties and the pair (ListingAgentId, SellingAgentId) is unique), you could remove the RealEstateTransactionId PK property and configure fluently a composite PK:
modelBuilder.Entity<RealEstateTransaction>()
.HasKey(e = new { e.ListingAgentId, e.SellingAgentId });
References:
Relationships
Keys (primary)
do this :
public class Agent
{
public long AgentId { get; set; }
public Virtual ICollection<RealEstateTransaction> ListingRealEstateTransactions { get; set; }
public Virtual ICollection<RealEstateTransaction> SellingRealEstateTransactions { get; set; }
}
public class RealEstateTransaction
{
public long RealEstateTransactionId { get; set; }
[ForeignKey("ListingAgentId")]
public Agent ListingAgent { get; set; }
public long ListingAgentId { get; set; }
[ForeignKey("SellingAgentId ")]
public Agent SellingAgent { get; set; }
public long SellingAgentId { get; set; }
}
I am receiving the following error when attempting to create the database:
One or more validation errors were detected during model generation:
Interaction_CauseElement_Source: : Multiplicity is not valid in Role
'Interaction_CauseElement_Source' in relationship
'Interaction_CauseElement'. Because the Dependent Role properties are
not the key properties, the upper bound of the multiplicity of the
Dependent Role must be '*'.
Interaction_EffectElement_Source: : Multiplicity is not valid in Role
'Interaction_EffectElement_Source' in relationship
'Interaction_EffectElement'. Because the Dependent Role properties are
not the key properties, the upper bound of the multiplicity of the
Dependent Role must be '*'.
I've seen this error in other Stack Overflow posts, but in the examples I found, the OP was trying for a 1-to-1 relationship in both directions between the tables. That is not what I am looking for.
Here is my model:
public class Element
{
[Key]
public int ID { get; set; }
[Required, MaxLength(64)]
public string Name { get; set; }
[MaxLength(200)]
public string Description { get; set; }
}
public class Interaction
{
[Key]
public int ID { get; set; }
[Index, Required]
public int CauseID { get; set; }
[Index, Required]
public int EffectID { get; set; }
[MaxLength(64)]
public string Location { get; set; }
[ForeignKey("CauseID")]
public virtual Element CauseElement { get; set; }
[ForeignKey("EffectID")]
public virtual Element EffectElement { get; set; }
}
Items in the Elements table are unique. A pair of elements can interact with each other in any number of locations. The CauseID/EffectID pair is not going to be unique.
The only other place I am changing the model is in the OnModelCreating method. I had received this error:
Introducing FOREIGN KEY constraint
'FK_dbo.Interactions_dbo.Elements_Cause' on table
'Interactions' may cause cycles or multiple cascade paths.
Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other
FOREIGN KEY constraints. Could not create constraint. See previous
errors.
And had to create a cascade policy for the model. This code fixed that error:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//Prevent cyclic cascade on elements table
modelBuilder.Entity<Interaction>()
.HasRequired(i => i.CauseElement)
.WithRequiredDependent()
.WillCascadeOnDelete(false);
modelBuilder.Entity<Interaction>()
.HasRequired(i => i.EffectElement)
.WithRequiredDependent()
.WillCascadeOnDelete(false);
base.OnModelCreating(modelBuilder);
}
But then I received the cryptic "Multiplicity" error. It seems like it wants me to make public virtual Element CauseElement into a collection like public virtual ICollection<Element> CauseElement, but that would not properly model the relationship.
I found the solution. This article on EntityFrameworkTutoral.net helped out. Because I need TWO references from the Interaction class to the Element class, this relationship is too complex to model in EF with only the attributes.
I had to update the model and then use the fluent API to tell EF how to treat the relationships. I updated my model to the following:
public class Element
{
public Element()
{
CauseElements = new List<Interaction>();
EffectElements = new List<Interaction>();
}
[Key]
public int ID { get; set; }
[Required, MaxLength(64)]
public string Name { get; set; }
#region Navigation
public virtual ICollection<Interaction> CauseElements { get; set; }
public virtual ICollection<Interaction> EffectElements { get; set; }
#endregion
}
public class Interaction
{
[Key]
public int ID { get; set; }
[Index]
public int CauseID { get; set; }
[Index]
public int EffectID { get; set; }
[MaxLength(64)]
public string Location { get; set; }
#region Navigation
[ForeignKey("CauseID")]
public virtual Element CauseElement { get; set; }
[ForeignKey("EffectID")]
public virtual Element EffectElement { get; set; }
#endregion
}
And in my DbContext class I used the fluent API to create the link between the Interaction.CauseElement and Element.CauseElements and which property was the foreign key for the Interaction table (and the same with the Effect relationship):
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//Prevent cyclic cascade on elements table
modelBuilder.Entity<Interaction>()
.HasRequired(i => i.CauseElement)
.WithRequiredDependent()
.WillCascadeOnDelete(false);
modelBuilder.Entity<Interaction>()
.HasRequired(i => i.EffectElement)
.WithRequiredDependent()
.WillCascadeOnDelete(false);
//Create the links between the element, the key, and the collection
modelBuilder.Entity<Interaction>()
.HasRequired<Element>(i => i.CauseElement)
.WithMany(e => e.CauseElements)
.HasForeignKey(i => i.CauseID);
modelBuilder.Entity<Interaction>()
.HasRequired<Element>(i => i.EffectElement)
.WithMany(e => e.EffectElements)
.HasForeignKey(i => i.EffectID);
base.OnModelCreating(modelBuilder);
}
It seems that Entity Framework tries to automatically infer the relationships between the tables when you have a simple 1-to-many relationship. If I removed EffectElement from the Interaction class (and EffectElements from Element), EF was able to create the relationship easily. But when I added it back, I received the error again.
Since that Element type showed up twice in the Interaction class, it didn't know how to create the relationship. I had to explicitly define it in the OnModelCreating method.
You reversed the responsibilities of the "ForeignKey" attribute. It goes on the ID field, specifying the property for which it serves as the foreign key. You want something as below:
// To-One on Element
[ForeignKey("Element")]
public int ElementId { get; set; }
public virtual Element Element { get; set; }
Also, this is actually a one-to-one relationship. A one-to-many relationship in this case would be:
// To-Many on Element
public virtual ICollection<Element> Elements{ get; set; }
Using code-first Entity Framework and .NET 4, I'm trying to create a one-to-many relationship between parents to children:
public class Parent
{
[Key]
public int ParentId { get; set; }
[Required]
public string ParentName { get; set; }
public IEnumerable<Child> Children { get; set; }
}
public class Child
{
[Key]
public int ChildId { get; set; }
[ForeignKey]
public int ParentId { get; set; }
[Required]
public string ChildName { get; set; }
}
As pointed out here, in order for foreign key relationship to carry into the database, the actual objects must be linked, not just their IDs. The normal way to do this if for a child to contain a reference to its parent (example).
But how do I enforce foreign keys in my implementation, which is the other way around (parent referencing children)?
First of all: You cannot use IEnumerable<T> for a collection navigation property. EF will just ignore this property. Use ICollection<T> instead.
When you have changed this, in your particular example you don't need to do anything because the foreign key property name follows the convention (name of primary key ParentId in principal entity Parent) so that EF will detect a required one-to-many relationship between Parent and Child automatically.
If you had another "unconventional" FK property name you still could define such a mapping with Fluent API, for example:
public class Child
{
[Key]
public int ChildId { get; set; }
public int SomeOtherId { get; set; }
[Required]
public string ChildName { get; set; }
}
Mapping:
modelBuilder.Entity<Parent>()
.HasMany(p => p.Children)
.WithRequired()
.HasForeignKey(c => c.SomeOtherId);
As far as I can tell it is not possible to define this relationship with data annotations. Usage of the [ForeignKey] attribute requires a navigation property in the dependent entity where the foreign key property is in.