1

I have two tables (Table A, Table B) joined with a join table (TableAB) with 3 payload columns. By Payload I mean columns apart from Id, TableAId, and TableBId.

I can insert into all tables successfully, but I need to insert data into one of the payload columns on Insert. I'm using EF 4.3, Fluent API. Can anyone help? Thanks in advance.

    public class Organisation : EntityBase<int>, IAggregateRoot
       {
    public string Name { get; set; }
    public string Url { get; set; }
    public int CountryId { get; set; }
    public int? OwnershipTypeId { get; set; }
    public int OrganisationStatusId { get; set; }

    public virtual ICollection<Feature> Features { get; set; }
    public virtual ICollection<OrganisationType> OrganisationTypes { get; set; }
    public virtual ICollection<PricePlan> PricePlans { get; set; }
    public virtual ICollection<User> Users { get; set; }

}

    public class User: EntityBase<Guid>, IAggregateRoot
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string JobTitle { get; set; }
    public int?  PhoneCallingCodeId       { get; set; }
    public int?  PhoneAreaCode{ get; set; }
    public string PhoneLocal { get; set; }
    public int? MobileCallingCodeId { get; set; }
    public int? MobileAreaCode { get; set; }
    public string MobileLocal { get; set; }      

    public virtual ICollection<Organisation.Organisation> Organisations { get; set; }

}

   public class OrganisationUser : EntityBase<int>, IAggregateRoot
{
    public DateTime StartDate { get; set; }
    public DateTime? EndDate { get; set; }
    public int OrganisationRoleId {get; set;}//Foreign Key - have tried leaving it out, tried it as public virtual Organisation Organisation {get;set;
    public bool IsApproved { get; set; }
}

     public class SDContext : DbContext 
{      

    public ObjectContext Core
    {
        get
        {
            return (this as IObjectContextAdapter).ObjectContext;
        }
    }
  public IDbSet<User> User { get; set; }

  public IDbSet<Organisation> Organisation { get; set; }

  protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

        modelBuilder.Entity<Organisation>().HasMany(u => u.Users).WithMany(o => o.Organisations).Map(m =>
        {
            m.MapLeftKey("OrganisationId");
            m.MapRightKey("UserId");
            m.ToTable("OrganisationUser");
        });

//I have tried specifically defining the foreign key in fluent, but I really need to understand how I can add the payload properties once I access and edit them.

  • 1
    Could you please show the classes you have (especially with their navigation properties) and the Fluent mapping? – Slauma Mar 29 '12 at 15:20
  • Hi, does that really matter? I know the tables are mapped correctly with the POCO as the ID's all insert fine. I just dont know how to access the payload columns. Lets say I have an Organisation, I'm adding Users, billing Plans etc. so I'm qurying to get related entities, using organisation.Add(entity) in a foreach loop to add the collections to my organisation. How do I access the join table for payload - I have created a POCO class for the join. – user1182263 Mar 29 '12 at 18:39
  • Yes, it does matter, as the long silence around your question proves. You only don't have an answer until now because you don't provide the necessary details. The last statement is important - you have an entity for the join table and it will have your payload columns, right? Where is the problem then? Load it, change it, save it... You can edit your question ("edit" link below question) to add more infos. – Slauma Mar 29 '12 at 18:50
  • You don't need to post your real code, just example some code which has the same structure as your real code. You can use dummies like `TableXYZ` but the important thing is to know what entities and navigation and FK properties you have, how it is mapped in Fluent API, etc. – Slauma Mar 29 '12 at 19:08
  • var jointable = new OrganisastionUser{Id=xx, organisationId=organisation.id, userId=user.Id, roleId=1}; xxxx.Add(jointable) - what is XXXXXXX??? – user1182263 Mar 29 '12 at 19:36

1 Answers1

2

Your mapping is not correct for your purpose. If you want to treat OrganisationUser as an intermediate entity between Organisation and User you must create relationships between Organisation and OrganisationUser and between User and OrganisationUser, not directly between Organisation and User.

Because of the intermediate entity which contains its own scalar properties you cannot create a many-to-many mapping. EF does not support many-to-many relationships with "payload". You need two one-to-many relationships:

public class Organisation : EntityBase<int>, IAggregateRoot
{
    // ...
    // this replaces the Users collection
    public virtual ICollection<OrganisationUser> OrganisationUsers { get; set; }
}

public class User : EntityBase<Guid>, IAggregateRoot
{
    // ...
    // this replaces the Organisations collection
    public virtual ICollection<OrganisationUser> OrganisationUsers { get; set; }
}

public class OrganisationUser : EntityBase<int>, IAggregateRoot
{
    public int OrganisationId { get; set; }
    public Organisation Organisation { get; set; }

    public Guid UserId { get; set; }
    public User User { get; set; }

    // ... "payload" properties ...
}

In Fluent API you must replace the many-to-many mapping by the following:

modelBuilder.Entity<Organisation>()
    .HasMany(o => o.OrganisationUsers)
    .WithRequired(ou => ou.Organisation)
    .HasForeignKey(ou => ou.OrganisationId);

modelBuilder.Entity<User>()
    .HasMany(u => u.OrganisationUsers)
    .WithRequired(ou => ou.User)
    .HasForeignKey(ou => ou.UserId);

Your derived DbContext may also contain a separate set for the OrganisationUser entity:

public IDbSet<OrganisationUser> OrganisationUsers { get; set; }

It's obvious now how you write something into the intermediate table:

var newOrganisationUser = new OrganisastionUser
{
    OrganisationId = 5,
    UserId = 8,
    SomePayLoadProperty = someValue,
    // ...
};

context.OrganisastionUsers.Add(newOrganisastionUser);
context.SaveChanges();

If you want to make sure that each pair of OrganisationId and UserId can only exist once in the link table, it would be better to make a composite primary key of those two columns to ensure uniqueness in the database instead of using a separate Id. In Fluent API it would be:

modelBuilder.Entity<OrganisationUser>()
    .HasKey(ou => new { ou.OrganisationId, ou.UserId });

More details about such a type of model and how to work with it is here:

Create code first, many to many, with additional fields in association table

Community
  • 1
  • 1
Slauma
  • 175,098
  • 59
  • 401
  • 420
  • Thanks a mill! I'll try that now today. I did read this before, but I'm amazed that this isn't supported by now in EF. Thanks so much for the help. – user1182263 Mar 30 '12 at 06:38
  • Hi, this worked. I still had problems, but it was because I was using the EntityBase which was trying to provide an ID into my Identity spec column. Really appreciate the help. Thanks. – user1182263 Mar 30 '12 at 14:24
  • Ajusting my situation to this case I'm getting the error SqlException: Foreign key 'FK_dbo.OrganisationUser_dbo.Organisation_OrganisationId' references invalid column 'OrganisationId' in referenced table 'dbo.Organisation'. Could not create constraint. Why? – Eduardo Brites Oct 16 '12 at 15:22
  • @EduardoBrites: I just wanted to suggest to create a new question about this, but I've seen you already have :) I don't know the answer though... – Slauma Oct 16 '12 at 16:42