2

I want to create a many-to-many relationship using EF 6 using a code-first approach. My entities use a composite primary key (to handle multi-tenancy).

Let's take simple and classical example. I have two entities Project and Person which have a many-to-many relationship:

public class Person
{
   [Key, Column(Order = 1),]
   public Int32 Id { get; set; } 
   [Key, Column(Order = 2)]
   public int TenantId { get; set; }
   public string Name { get; set; }
}

public class Project
{
   [Key, Column(Order = 1),]
   public Int32 Id { get; set; } 
   [Key, Column(Order = 2)]
   public int TenantId { get; set; }
   Public string Name { get; set; }
}

I also have a joining table ProjectPerson like this:

Above I have defined a Project to ProjectPerson relationship. Note that public class ProjectPerson { [Key, Column(Order = 1),] public Int32 Id { get; set; } [Key, Column(Order = 2)] [ForeignKey("Project")] public int TenantId { get; set; }

   [ForeignKey("Project")]
   public int ProjectId { get; set; }

   public DateTime AddedDate{ get; set; }

   public virtual Project Project { get; set; }
}

TenantId is used as a part of the primary and foreign key.

Up to this point, the model works as expected. But the Person to ProjectPerson relationship is missing.

I have added following two lines to the ProjectPerson class

  [ForeignKey("Person")]
  public int PersonId { get; set; }

  public virtual Person Person { get; set; }

Definitely mapping to TenantId is missing. I don't know how to define it

Update

I found this. but still im not satisfied as there is additional TenantId ( PersonTenantId) as a foreign key.

 public class ProjectPerson
 {
   [Key, Column(Order = 1),]
   public Int32 Id { get; set; } 
   [Key, Column(Order = 2)]
   [ForeignKey("Project")]
   public int TenantId { get; set; }

   [ForeignKey("Project")]
   public int ProjectId { get; set; }

   [ForeignKey("Person")]
   public int PersonId { get; set; }
   [ForeignKey("Person")]
   public int PersonTenantId { get; set; } // duplicate 


   public DateTime AddedDate{ get; set; }

   public virtual Project Project { get; set; }
   public virtual Person Person { get; set; }

}
Greatran
  • 180
  • 3
  • 18
  • Why do you need a TenantId mapping in ProjectPerson, you already have the mapping in both project and person tables? As far as ProjectPerson is concerned, TenantID is irrelevant. Its just an associative table and a given tenant can only query for either projects or person in their tenancy right? You don't need this TenancyID to be a part of this table and would end up with a 3 table join if you try to force it. That aside check out this https://febdev.wordpress.com/2013/01/30/entity-framework-mapping-references-with-composite-keys/ – rism May 06 '15 at 23:47
  • @rism two reasons: 1. without TenantId in ProjectPerson we can not create foreign key association in DBMS. 2. Tenant data should be able to move between databases. i.e. every record should be unique . – Greatran May 07 '15 at 07:11
  • I followed the instructions at https://febdev.wordpress.com/2013/01/30/entity-framework-mapping-references-with-composite-keys/ but got the following error `Insufficient mapping: Foreign key must be mapped to some AssociationSet or EntitySets participating in a foreign key association on the conceptual side.` – Greatran May 07 '15 at 08:12
  • Are you saying you've set up your keys so TenantA can have Person with ID = 1, and TenantB can also have a distinct Person record with ID = 1? – rism May 07 '15 at 08:17
  • exactly yes. specially in following situation. imaging TenantA & TenantB are in DB X while TenantC is in DB Y. later TenantB moved DB Y – Greatran May 07 '15 at 08:54

1 Answers1

2

Use the fluent API to reuse the TentantId column for both FK's in the junction table. ProjectId should also be included in the junction table's PK. Note that I modified the order of the composite primary key columns to have TenantId as the first column.

    public class Person
    {
        [Key, Column(Order = 0)]
        public int TenantId { get; set; }
        [Key, Column(Order = 1)]
        public int PersonId { get; set; }

        public string Name { get; set; }

        public virtual ICollection<ProjectPerson> ProjectPeople { get; set; }
    }

    public class Project
    {
        [Key, Column(Order = 0)]
        public int TenantId { get; set; }
        [Key, Column( Order = 1 )]
        public int ProjectId { get; set; }

        public string Name { get; set; }

        public virtual ICollection<ProjectPerson> ProjectPeople { get; set; }
    }

    public class ProjectPerson
    {
        [Key, Column( Order = 0 )]
        public int TentantId { get; set; }
        [Key, Column( Order = 1 )]
        public int ProjectId { get; set; }
        [Key, Column( Order = 2 )]
        public int PersonId { get; set; }

        public DateTime AddedDate { get; set; }

        public virtual Project Project { get; set; }
        public virtual Person Person { get; set; }
    }


    protected override void OnModelCreating( DbModelBuilder modelBuilder )
    {
        base.OnModelCreating( modelBuilder );

        modelBuilder.Entity<Project>()
            .HasMany(pr => pr.ProjectPeople )
            .WithRequired( pp => pp.Project )
            .HasForeignKey( pp => new { pp.TentantId, pp.ProjectId } );

        modelBuilder.Entity<Person>()
            .HasMany( pe => pe.ProjectPeople )
            .WithRequired( pp => pp.Person )
            .HasForeignKey( pp => new { pp.TentantId, pp.PersonId } );
    }
Moho
  • 15,457
  • 1
  • 30
  • 31