One-To-One relationship with FK distinct from PK - c#

I have 2 tables in database: ReceivedGoods and ReceivedGoodsProperties
ReceivedGoods contains ReceivingId as PK and must have its extending data in ReceivedGoodsProperties which contains ReceivingId as FK referencing to the ReceivedGoods's ReceivingId. Current ReceivedGoodsProperties, however, has its own PK Id and is therefore distinct from FK. So I have following:
public class ReceivedGoods
{
...
public int ReceivingId { get; set; }
...
public virtual ReceivedGoodsProperties properties { get; set; }
}
public class ReceivedGoodsProperties
{
...
public int Id { get; set; } // This is PK
public int ReceivingId { get; set; } // This is FK
...
public virtual ReceivedGoods goods { get; set; }
}
I would like to get ReceivedGoods object and have properties automatically loaded as well but I am not able to figure out, how to set up this within EF.
I've tried something like this (from the ReceivedGoodsProperties side mapping):
this.HasRequired(p => p.goods)
.WithRequiredDependent(d => d.properties)
.Map(m => m.MapKey("ReceivingId"));
but I am ending up with following error:
ReceivingId: Name: Each property name in a type must be unique. Property
name 'ReceivingId' is already defined.
When commenting out ReceivingId in ReceivedGoodsProperties, upper exception is not thrown, ReceivedGoods is loaded correctly except the properties property.
Can somebody explain me, how to do one-to-one mapping in situation like this?

Could you try:
public class ReceivedGoods
{
...
public int ReceivingId { get; set; }
...
public virtual ReceivedGoodsProperties properties { get; set; }
}
public class ReceivedGoodsProperties
{
...
public int Id { get; set; } // This is PK
[ForeignKey( "goods " )]
public int ReceivingId { get; set; } // This is FK
...
[Required]
public virtual ReceivedGoods goods { get; set; }
}
BTW, in C# the standard guidelines is to PascalCase members, so Goods and Properties

Try defining the relationship this way:
this.HasRequired(p => p.goods)
.WithRequiredDependent(p => p.properties)
.HasForeignKey(p => p.ReceivingId);
If you follow the standard EF naming conventions, it can usually figure out these relationships on its own. You only really run in to trouble when your navigation property names don't correspond to the class name, or if you have multiple FKs to the same destination in the source table.
If you want the navigation properties to get filled out "automatically", use the Include extension method on the query, as in:context.Goods.Include(g=>g.properties). You don't have to declare them as virtual unless you want to make use of lazy loading.
You may need to come at this from the other entity:
this.HasRequired(p => p.properties)
.WithRequiredPrincipal(p => p.goods)
.HasForeignKey(p => p.ReceivingId);

Related

Multiple relations of same type

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; }
}

Codefirst Migrations not working with EntityFramework, MVC5, ComplexType and fluent

I like to use composition to group up like properties in a class so that they are easier to use when the number of fields increase. My question is, can entity framework handle modeling when we use composition and ComplexTypes in a class? When I use the two together, I get the following error during migrations:
The expression 'x => x.AllQuantities.OnHandUom' is not a valid
property expression. The expression should represent a property: C#:
't => t.MyProperty' VB.Net: 'Function(t) t.MyProperty'.
Here is the code. This is a simple model.
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public AllQuantities AllQuantities { get; set; }
}
Note, AllQuantities is a Complex Type added through composition (I want the fields to appear in the same table as the products, but I want to group them under AllQuanties (Actually I have many more fields, but I have simplified the problem for here.))
[ComplexType]
public class AllQuantities
{
public double OnHand { get; set; }
public Uom OnHandUom { get; set; }
public int OnHandUomID { get; set; }
}
Please note in my Uom class, I am trying to set up a navigation property back to products through the complex type.
public class Uom
{
public int ID { get; set; }
public string Name { get; set; }
public double QtyInUom { get; set; }
public virtual ICollection<Product> OnHandUoms { get; set; }
}
Then, I have used the following fluent code to set up the model in my context file so that the navigation property works.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>()
.HasRequired(x => x.AllQuantities.OnHandUom)
.WithMany(y => y.OnHandUoms)
.HasForeignKey(z => z.AllQuantities.OnHandUomID)
.WillCascadeOnDelete(false);
base.OnModelCreating(modelBuilder);
}
Note the
"HasRequired(x => x.AllQuantities.OnHandUom)."
I believe the dot notation is causing some kind of a problem. To my mind, it shouldn't.
I have tried the model creation by adding all the fields from my ComplexType AllQuantities to the product directly, as per normal, then everything works, however, when I separate the two as listed above, I get the error mentioned earlier. I would like to use the ComplexType so that I can group like fields.
My question is, is there any way to make the above work with a ComplexType and compostion?

EF6 code first multiple 1-to-many mapping issue / "Multiplicity" error

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; }

Entity Framework: Two foreign keys connected by one collection

I know that there is related topic: two Foreign Keys from same table, but I can't find there fix to my problem. I am pretty new to EF.
I have the following model classes (Code-First):
public class Member
{
[Key]
public int MemberID { get; set; }
public string Name {get; set;}
public string Surname { get; set; }
public virtual ICollection<Marriage> Marriages { get; set; }
}
public class Marriage
{
[Key]
public int MarriageID { get; set; }
public string MarriagePlace { get; set; }
public DateTime MarriageDate { get; set; }
[ForeignKey("Husband")]
public int HusbandID { get; set; }
[ForeignKey("Wife")]
public int WifeID { get; set; }
public virtual Member Husband { get; set; }
public virtual Member Wife { get; set; }
}
My problem is that both Husband and Wife should be connected to the same Marriage collection in Member class. I did that:
modelBuilder.Entity<Marriage>()
.HasRequired<Member>(m => m.Husband)
.WithMany(m => m.Marriages)
.HasForeignKey(m => m.HusbandID)
.WillCascadeOnDelete(false);
And husband is now connected to Merriages collection. But everything breaks when I'm trying to add the same thing for Wife property:
modelBuilder.Entity<Marriage>()
.HasRequired<Member>(m => m.Wife)
.WithMany(m => m.Marriages)
.HasForeignKey(m => m.WifeID)
.WillCascadeOnDelete(false);
and I am getting an error:
Error 1 Schema specified is not valid. Errors: The relationship 'FamilyTree.Models.Marriage_Husband' was not loaded because the type 'FamilyTree.Models.Member' is not available. C:\Users\Sumiteru\Documents\Visual Studio 2013\Projects\FamilyTree\FamilyTree\App.xaml 9 21 FamilyTree
That error occurs becouse EF didn't know what navigation property he must use. In OnModelBuliding method you are setting a Member's foreign key to Marriages twice, so EF is confused about what navigation property should use when it will populating Marriages and throws an exception.
There's a topic on EF codeplex forum, where user named 'moozzyk' explains that EF behavior more clearly (in comment): Link.
As a solution, You should do another collection navigation property in Member class and map Wife's or Husbend's foreign key to it. You can find the same solution on that SO anwser: Link.

One to one optional relationship using Entity Framework Fluent API

We want to use one to one optional relationship using Entity Framework Code First. We have two entities.
public class PIIUser
{
public int Id { get; set; }
public int? LoyaltyUserDetailId { get; set; }
public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
}
public class LoyaltyUserDetail
{
public int Id { get; set; }
public double? AvailablePoints { get; set; }
public int PIIUserId { get; set; }
public PIIUser PIIUser { get; set; }
}
PIIUser may have a LoyaltyUserDetail but LoyaltyUserDetail must have a PIIUser.
We tried these fluent approach techniques.
modelBuilder.Entity<PIIUser>()
.HasOptional(t => t.LoyaltyUserDetail)
.WithOptionalPrincipal(t => t.PIIUser)
.WillCascadeOnDelete(true);
This approach didn't create LoyaltyUserDetailId foreign key in PIIUsers table.
After that we tried the following code.
modelBuilder.Entity<LoyaltyUserDetail>()
.HasRequired(t => t.PIIUser)
.WithRequiredDependent(t => t.LoyaltyUserDetail);
But this time EF didn't create any foreign keys in these 2 tables.
Do you have any ideas for this issue?
How can we create one to one optional relationship using entity framework fluent api?
EF Code First supports 1:1 and 1:0..1 relationships. The latter is what you are looking for ("one to zero-or-one").
Your attempts at fluent are saying required on both ends in one case and optional on both ends in the other.
What you need is optional on one end and required on the other.
Here's an example from the Programming E.F. Code First book
modelBuilder.Entity<PersonPhoto>()
.HasRequired(p => p.PhotoOf)
.WithOptional(p => p.Photo);
The PersonPhoto entity has a navigation property called PhotoOf that points to a Person type. The Person type has a navigation property called Photo that points to the PersonPhoto type.
In the two related classes, you use each type's primary key, not foreign keys. i.e., you won't use the LoyaltyUserDetailId or PIIUserId properties. Instead, the relationship depends on the Id fields of both types.
If you are using the fluent API as above, you do not need to specify LoyaltyUser.Id as a foreign key, EF will figure it out.
So without having your code to test myself (I hate doing this from my head)... I would translate this into your code as
public class PIIUser
{
public int Id { get; set; }
public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
}
public class LoyaltyUserDetail
{
public int Id { get; set; }
public double? AvailablePoints { get; set; }
public PIIUser PIIUser { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<LoyaltyUserDetail>()
.HasRequired(lu => lu.PIIUser )
.WithOptional(pi => pi.LoyaltyUserDetail );
}
That's saying LoyaltyUserDetails PIIUser property is required and PIIUser's LoyaltyUserDetail property is optional.
You could start from the other end:
modelBuilder.Entity<PIIUser>()
.HasOptional(pi => pi.LoyaltyUserDetail)
.WithRequired(lu => lu.PIIUser);
which now says PIIUser's LoyaltyUserDetail property is optional and LoyaltyUser's PIIUser property is required.
You always have to use the pattern HAS/WITH.
HTH and FWIW, one to one (or one to zero/one) relationships are one of the most confusing relationships to configure in code first so you are not alone! :)
Just do like if you have one-to-many relationship between LoyaltyUserDetail and PIIUser so you mapping should be
modelBuilder.Entity<LoyaltyUserDetail>()
.HasRequired(m => m.PIIUser )
.WithMany()
.HasForeignKey(c => c.LoyaltyUserDetailId);
EF should create all foreign key you need and just don't care about WithMany !
There are several things wrong with your code.
A 1:1 relationship is either: PK<-PK, where one PK side is also an FK, or PK<-FK+UC, where the FK side is a non-PK and has a UC. Your code shows you have FK<-FK, as you define both sides to have an FK but that's wrong. I recon PIIUser is the PK side and LoyaltyUserDetail is the FK side. This means PIIUser doesn't have an FK field, but LoyaltyUserDetail does.
If the 1:1 relationship is optional, the FK side has to have at least 1 nullable field.
p.s.w.g. above did answer your question but made a mistake that s/he also defined an FK in PIIUser, which is of course wrong as I described above. So define the nullable FK field in LoyaltyUserDetail, define the attribute in LoyaltyUserDetail to mark it the FK field, but don't specify an FK field in PIIUser.
You get the exception you describe above below p.s.w.g.'s post, because no side is the PK side (principle end).
EF isn't very good at 1:1's as it's not able to handle unique constraints. I'm no expert on Code first, so I don't know whether it is able to create a UC or not.
(edit) btw: A 1:1 B (FK) means there's just 1 FK constraint created, on B's target pointing to A's PK, not 2.
public class User
{
public int Id { get; set; }
public int? LoyaltyUserId { get; set; }
public virtual LoyaltyUser LoyaltyUser { get; set; }
}
public class LoyaltyUser
{
public int Id { get; set; }
public virtual User MainUser { get; set; }
}
modelBuilder.Entity<User>()
.HasOptional(x => x.LoyaltyUser)
.WithOptionalDependent(c => c.MainUser)
.WillCascadeOnDelete(false);
this will solve the problem on REFERENCE and FOREIGN KEYS
when UPDATING or DELETING a record
Try adding the ForeignKey attribute to the LoyaltyUserDetail property:
public class PIIUser
{
...
public int? LoyaltyUserDetailId { get; set; }
[ForeignKey("LoyaltyUserDetailId")]
public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
...
}
And the PIIUser property:
public class LoyaltyUserDetail
{
...
public int PIIUserId { get; set; }
[ForeignKey("PIIUserId")]
public PIIUser PIIUser { get; set; }
...
}
This is of no use to the original poster, but for anyone still on EF6 who needs the foreign key to be different from the primary key, here's how to do it:
public class PIIUser
{
public int Id { get; set; }
//public int? LoyaltyUserDetailId { get; set; }
public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
}
public class LoyaltyUserDetail
{
public int Id { get; set; }
public double? AvailablePoints { get; set; }
public int PIIUserId { get; set; }
public PIIUser PIIUser { get; set; }
}
modelBuilder.Entity<PIIUser>()
.HasRequired(t => t.LoyaltyUserDetail)
.WithOptional(t => t.PIIUser)
.Map(m => m.MapKey("LoyaltyUserDetailId"));
Note that you can't use the LoyaltyUserDetailId field because, as far as I can tell, it can only be specified using the fluent API. (I've tried three ways of doing it using the ForeignKey attribute and none of them worked).
The one thing that is confusing with above solutions is that the Primary Key is defined as "Id" in both tables and if you have primary key based on the table name it wouldn't work, I have modified the classes to illustrate the same, i.e. the optional table shouldn't define it's own primary key instead should use the same key name from main table.
public class PIIUser
{
// For illustration purpose I have named the PK as PIIUserId instead of Id
// public int Id { get; set; }
public int PIIUserId { get; set; }
public int? LoyaltyUserDetailId { get; set; }
public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
}
public class LoyaltyUserDetail
{
// Note: You cannot define a new Primary key separately as it would create one to many relationship
// public int LoyaltyUserDetailId { get; set; }
// Instead you would reuse the PIIUserId from the primary table, and you can mark this as Primary Key as well as foreign key to PIIUser table
public int PIIUserId { get; set; }
public double? AvailablePoints { get; set; }
public int PIIUserId { get; set; }
public PIIUser PIIUser { get; set; }
}
And then followed by
modelBuilder.Entity<PIIUser>()
.HasOptional(pi => pi.LoyaltyUserDetail)
.WithRequired(lu => lu.PIIUser);
Would do the trick, the accepted solution fails to clearly explain this, and it threw me off for few hours to find the cause

Categories