Entity Framework / code first / table-per-type inheritance - implementing a one-to-many association between a derived class and a concrete class

天大地大妈咪最大 提交于 2019-12-07 17:09:24

问题


I'm using MVC4 in VS2012, and I'm following a table-per-type inheritance approach. I'm trying to use the Seed() method 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, hence there is no PK property in Landlord.

The relationship I'm trying to implement is as follows:

  • A Landlord can have many ResidentialProperties (1:n)
  • A ResidentialProperty can (in the application's scope) have one Landlord (1:1)

I thought the way I had my models configured would be enough to run the update-database command from package manager successfully, and add what I have in my Seed() method to my database. This wasn't the case, I got the following error:

Unable to determine the principal end of the 'LetLord.Models.ResidentialProperty_UserProfile' relationship. Multiple added entities may have the same primary key.

After reading around I realised that the relationship I wanted to create needed to be implemented using 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 the update-database command via package manager I get the following error:

\tSystem.Data.Entity.Edm.EdmAssociationEnd: : Multiplicity is not valid in Role 'Landlord_ResidentialProperties_Target' in relationship 'Landlord_ResidentialProperties'. Because the Dependent Role refers to the key properties, the upper bound of the multiplicity of the Dependent Role must be '1'.

I'm completely at a loss as to how to remedy my problem. I've spent an awful lot of time stuck on this, which is very frustrating as it is something that I feel should be accomplished easily. If anyone has the solution, I'd be very appreciative if you could share it with me.

EDIT - output of exception thrown from the first error mentioned above:

System.Data.Entity.Infrastructure.DbUpdateException: Unable to determine the principal end of the 'LetLord.Models.ResidentialProperty_Landlord' relationship. Multiple added entities may have the same primary key. ---> System.Data.UpdateException: Unable to determine the principal end of the 'LetLord.Models.ResidentialProperty_Landlord' relationship. Multiple added entities may have the same primary key. at System.Data.Mapping.Update.Internal.UpdateTranslator.RegisterEntityReferentialConstraints(IEntityStateEntry stateEntry, Boolean currentValues) at System.Data.Mapping.Update.Internal.UpdateTranslator.RegisterReferentialConstraints(IEntityStateEntry stateEntry) at System.Data.Mapping.Update.Internal.UpdateTranslator.PullModifiedEntriesFromStateManager() at 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 entityCache) at System.Data.Objects.ObjectContext.SaveChanges(SaveOptions options) at System.Data.Entity.Internal.InternalContext.SaveChanges() --- End of inner exception stack trace --- 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) 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 making navigation property on the many side of the base class, and not the derived class.

My next error was ordering of entities in my Seed() method. I've added below what they should be like, this applies especially 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 fixing code as above, I ran into this error:

System.Data.Entity.Infrastructure.DbUpdateException: An error occurred while updating the entries. See the inner exception for details. ---> System.Data.UpdateException: An error occurred while updating the entries. See the inner exception for details. ---> System.Data.SqlClient.SqlException: The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.ResidentialProperty_dbo.Landlord_UserId". The conflict occurred in database "C:\USERS\HOME\DESKTOP\LETLORD\LETLORD\APP_DATA\LETLORD.MDF", table "dbo.Landlord", column 'UserId'. The statement has been terminated.

After a quick search I found that this error was being caused (I think) as there was already data in my database. Dropping and recreating the database got around this and when I ran the Seed() method again data was added successfully.


回答1:


If Landlord can have multiple properties and property can have multiple one landlord the model should look like:

[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's 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; } 
}


来源:https://stackoverflow.com/questions/14940907/entity-framework-code-first-table-per-type-inheritance-implementing-a-one

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!