Entity Framework / code first / table-per-type inheritance - implementing the mutual relationship between a derived class and a specific class
I am using MVC4 in VS2012 and I am following the type based inheritance method. I am trying to use a method Seed()
to add data to my database.
I have the following classes:
landlord
[Table("Landlord")]
public class Landlord : UserProfile
{
// A Landlord can have many ResidentialProperties
[ForeignKey("ResidentialPropertyId")]
public virtual ICollection<ResidentialProperty> ResidentialProperties { get; set; }
}
ResidentialProperty
[Table("ResidentialProperty")]
public class ResidentialProperty
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ResidentialPropertyId { get; set; }
// A ResidentialProperty has 1 Landlord
public virtual int UserId { get; set; }
public virtual UserProfile UserProfile { get; set; }
}
So, Landlord
inherits from UserProfile
, therefore, inLandlord.
The link I am trying to implement is as follows:
- A
Landlord
can have manyResidentialProperties
(1: n) - A
ResidentialProperty
can (in the application scope) have oneLandlord
(1: 1)
I thought the way my models were configured would be sufficient to successfully run the command update-database
from the package manager and add my database to my method Seed()
. It wasn't, I got the following error:
Unable to determine the major end of the relationship "LetLord.Models.ResidentialProperty_UserProfile". Multiple added objects can have the same primary key.
After reading, I realized that the relationship I wanted to create had to be implemented using the Fluent API, so I tried the following:
modelBuilder.Entity<Landlord>()
.HasMany(x => x.ResidentialProperties)
.WithOptional()
.HasForeignKey(x => x.ResidentialPropertyId);
modelBuilder.Entity<ResidentialProperty>()
.HasRequired(x => x.UserProfile);
When I try to execute a command update-database
through the package manager, I get the following error:
\ tSystem.Data.Entity.Edm.EdmAssociationEnd :: multiplicity is not valid in the Landlord_ResidentialProperties_Target role in the Landlord_ResidentialProperties relation. Since the dependent role is a key property, the upper bound for the multiplicity of the dependent role should be "1".
I am completely at a loss as to how to fix my problem. I spent a lot of time on this, which is very frustrating as I feel like it needs to be done easily. If anyone has a solution, I would be very grateful if you could share it with me.
EDIT - Throwing out the exception thrown by the first error mentioned above:
System.Data.Entity.Infrastructure.DbUpdateException: Unable to determine the primary end of the LetLord.Models.ResidentialProperty_Landlord relationship. Multiple added objects can have the same primary key. ---> System.Data.UpdateException: Unable to determine the base end of the LetLord.Models.ResidentialProperty_Landlord relationship. Multiple added objects can have the same primary key. at System.Data.Mapping.Update.Internal.UpdateTranslator.RegisterEntityReferentialConstraints (IEntityStateEntry stateEntry, Boolean currentValues) at System.Data.Mapping.Update.Internal.UpdateTranslator.RegisterReferential.Constraints (IEntityStateEntry stateEntry, Boolean currentValues) UpdateTranslator.PullModifiedEntriesFromStateManager () in System.Data.Mapping.Update.Internal.UpdateTranslator.ProduceCommands () at System.Data.Mapping.Update.Internal.UpdateTranslator.Update (IEntityStateManager stateManager, IEntityAdapter adapter) at System.Data.EntityClient.EntityAdapter.Update (IEntityStateManager entityCaveCache options) SaveOptions) in System.Data.Entity.Internal.InternalContext.SaveChanges () --- End of internal check of the exception stack --- in System.Data.Entity.Internal.InternalContext.SaveChanges () in System.Data.Entity.Internal. LazyInternalContext.SaveChanges () at System.Data.Entity.DbContext.SaveChanges () at LetLord.Migrations.Configuration.Seed (LetLordContext context) at c: \ Users \ Home \ Desktop \ LetLord \ LetLord \ Migrations \ Configuration.cs: string 83IEntityAdapter) in System.Data.EntityClient.EntityAdapter.Update (IEntityStateManager entityCache) in System.Data.Objects.ObjectContext.SaveChanges (SaveOptions options) in System.Data.Entity.Internal.InternalContext.Save internal () --- End exception stack checks --- at System.Data.Entity.Internal.InternalContext.SaveChanges () at System.Data.Entity.Internal.LazyInternalContext.SaveChanges () at System.Data.Entity.DbContext.SaveChanges () at LetLord.Migrations .Configuration.Seed (LetLordContext context) at c: \ Users \ Home \ Desktop \ LetLord \ LetLord \ Migrations \ Configuration.cs: line 83IEntityAdapter) to System.Data.EntityClient.EntityAdapter.Update (IEntityStateManager entityCache) to System.Data.Objects.ObjectContext.SaveChanges (SaveOptions options) to System.Data.Entity.Internal.InternalContext.Save internal () --- End exception stack checks --- at System.Data.Entity.Internal.InternalContext.SaveChanges () at System.Data.Entity.Internal.LazyInternalContext.SaveChanges () at System.Data.Entity.DbContext.SaveChanges () at LetLord.Migrations .Configuration.Seed (LetLordContext context) at c: \ Users \ Home \ Desktop \ LetLord \ LetLord \ Migrations \ Configuration.cs: line 83SaveChanges () --- End of internal check of exception stack --- in System.Data.Entity.Internal.InternalContext.SaveChanges () in System.Data.Entity.Internal.LazyInternalContext.SaveChanges () in System.Data.Entity.DbContext .SaveChanges () in LetLord.Migrations.Configuration.Seed (LetLordContext context) in c: \ Users \ Home \ Desktop \ LetLord \ LetLord \ Migrations \ Configuration.cs: line 83SaveChanges () --- End of internal check of exception stack --- in System.Data.Entity.Internal.InternalContext.SaveChanges () in System.Data.Entity.Internal.LazyInternalContext.SaveChanges () in System.Data.Entity.DbContext .SaveChanges () in LetLord.Migrations.Configuration.Seed (LetLordContext context) in c: \ Users \ Home \ Desktop \ LetLord \ LetLord \ Migrations \ Configuration.cs: line 83
EDIT - solution:
Entities should be as per Ladislav's answer below, my mistake was that the navigation property is on many sides of the base class and not in the derived class.
My next mistake was ordering objects in my method Seed()
. I added below what they should be, this is especially important when adding entities with associations.
protected override void Seed(LetLord.Models.LetLordContext context)
{
try
{
#region Landlords
// create a list of landlords first - a landlord can have many residential properties, make this = new List<ResidentialProperty>()
var landlords = new List<Landlord>
{
// landlord 1
new Landlord { UserName="Frank", FirstName="Frank", LastName="O'Sullivan", Email="a@a.com", AccountType=(int)AccountType.Landlord, ResidentialProperties = new List<ResidentialProperty>() }
// ...
};
landlords.ForEach(l => context.UserProfile.AddOrUpdate(l));
context.SaveChanges();
#endregion
#region ResidentialProperties
// then create a list of properties
var residentialProperties = new List<ResidentialProperty>
{
// Property 1 associated with LandLord 1
new ResidentialProperty { PropTypeValue=(int)PropertyType.Detached, Description="Some description...", NumberOfBedrooms="4", NumberOfReceptionRooms="2", NumberOfBathrooms="2", HasBackGarden=true, HasFrontGarden=true, HasSecureParking=false, IsDisabledFriendly=false, DateAdded=DateTime.Now, Landlord = landlords.FirstOrDefault(u => u.UserId == 11) } // in my case, user 11 is landlord 1
// ...
};
residentialProperties.ForEach(rp => context.ResidentialProperty.AddOrUpdate(rp));
context.SaveChanges();
#endregion
// then add our list of properties to our list of landlords
landlords[0].ResidentialProperties.Add(residentialProperties[0]);
landlords[1].ResidentialProperties.Add(residentialProperties[1]);
landlords[2].ResidentialProperties.Add(residentialProperties[2]);
landlords[3].ResidentialProperties.Add(residentialProperties[3]);
landlords[4].ResidentialProperties.Add(residentialProperties[4]);
landlords[5].ResidentialProperties.Add(residentialProperties[5]);
landlords[6].ResidentialProperties.Add(residentialProperties[6]);
landlords[7].ResidentialProperties.Add(residentialProperties[7]);
landlords[8].ResidentialProperties.Add(residentialProperties[8]);
landlords[9].ResidentialProperties.Add(residentialProperties[9]);
context.SaveChanges();
}
catch (Exception ex)
{
string lines = ex.ToString();
System.IO.StreamWriter file = new System.IO.StreamWriter(@"C:\Users\Home\Desktop\error.txt");
file.WriteLine(lines);
file.Close();
}
When I tried to run update-database
after correcting the code as above, I ran into this error:
System.Data.Entity.Infrastructure.DbUpdateException: An error occurred while updating records. See Inner Exception for details. ---> System.Data.UpdateException: An error occurred while updating records. See Inner Exception for details. ---> System.Data.SqlClient.SqlException: The INSERT statement was in conflict with the FOREIGN KEY constraint "FK_dbo.ResidentialProperty_dbo.Landlord_UserId". The conflict occurred in the database "C: \ USERS \ HOME \ DESKTOP \ LETLORD \ LETLORD \ APP_DATA \ LETLORD.MDF", table "dbo.Landlord", column "UserId". The application has been completed.
After a quick search, I found that this error was triggered (I think) since there was already data in my database. Dropping and re-creating the database got around this and when I ran the method the Seed()
data was added successfully again.
source to share
If a Landlord can have multiple properties and a property can have multiple one landlord, the model should look like this:
[Table("Landlord")]
public class Landlord : UserProfile
{
// A Landlord can have many ResidentialProperties
public virtual ICollection<ResidentialProperty> ResidentialProperties { get; set; }
}
[Table("ResidentialProperty")]
public class ResidentialProperty
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ResidentialPropertyId { get; set; }
// A ResidentialProperty has 1 Landlord
// Foreign key is on many side and it contains value of primary key on one side
// In your case FK must contain value of property landlord
public virtual int UserId { get; set; }
// ForeignKey attribute pairs navigation property on many side with foreign key propety
[ForeignKey("UserId")]
public virtual Landlord Landlord { get; set; }
}
source to share