Search code examples
c#fluent-nhibernate

Convert many-to-many to parent-child relation


I have two classes User and Appointment that used to be linked in many-to-many relation. But now i have to be able to manage the state of this relation (i need to be able to track whether the user has accepted an appointment or rejected or not yet responded).

The classes are mapped to respective tables Users and Appointments. Both tables are connected via UserAppointments table that has composite primary key consisting of fk to users and fk to appointments table.

The current implementation throws an exception with message: "Invalid index 2 for this SqlParameterCollection with Count=2." when i try to add NEW UserAppointment object to the collection of an Appointment object

The User mapping

 public class UserMap : ClassMap<User>
    {
        public UserMap()
        {
            Id(d => d.Id);
            Map(d => d.FirstName).Column("FirstName");
            Map(d => d.LastName).Column("LastName");
            Map(d => d.Email).Column("Email");            
            HasMany(u => u.UserAppointments);  
            Table("Users");

        }
    }

The Appointment mapping

public class AppointmentMap : ClassMap<Appointment>
    {
        public AppointmentMap()
        {
            Id(c => c.Id);
            HasMany(a => a.AppointmentParticipants).Inverse().Cascade.All();
            References(a => a.Location).Column("FkAddressId");
            Map(a => a.DateAndTime).Column("AppointmentDate").CustomType<UtcDateTimeType>();
            Map(a => a.Description).Column("AppointmentDescription");
            Map(a => a.Status).Column("AppointmentStatus").CustomType<AppointmentStatus>();
            HasMany(a => a.AppointmentEstates).Inverse().Cascade.All();
            Table("Appointments");
        }

The UserAppointments mapping

public class UserAppointmentsMap : ClassMap<UserAppointment>
    {
        public UserAppointmentsMap()
        {
            CompositeId()
                .KeyReference(a => a.Appointment, "FkAppointmentsId")
                .KeyReference(u => u.User, "FkUserId");
            References(a => a.User).Column("FkUserId");
            References(a => a.Appointment).Column("FkAppointmentsId");
            Table("UserAppointments");
        }
    }

The expected result is when I'm adding new UserAppointment to the collection of an existing Appointment a new record to be created in the UserAppointments table pointing to the relevant User and Appointment entity


Solution

  • I finally managed to resolve the issue.... Actually there were several issues with my code:

    1. The issue with the "Invalid index 2 for this SqlParameterCollection with Count=2." was caused by the UserAppointmentsMap. Once you define a property as a part of a composite key it should not be mapped again as a reference.

      public class UserAppointmentsMap : ClassMap<UserAppointment>
      {
          public UserAppointmentsMap()
          {
              CompositeId()
                  .KeyReference(a => a.Appointment, "FkAppointmentsId")
                  .KeyReference(u => u.User, "FkUserId");
              Table("UserAppointments");
          }
      }
      
    2. The Composite Id with foreign key should be done with KeyReference instead of KeyProperty

    3. I was having issue with wrongly generated query(the foreign key was wrong i.e. instead of FkAppointmentsId it was looking for Appointments_Id) so I had to specify the column name explicitly

      public class AppointmentMap : ClassMap<Appointment>
      {
          public AppointmentMap()
          {
              Id(c => c.Id);
              HasMany(a => a.AppointmentParticipants).Inverse().KeyColumn("FkAppointmentsId").Cascade.All();
              References(a => a.Location).Column("FkAddressId");
              Map(a => a.DateAndTime).Column("AppointmentDate").CustomType<UtcDateTimeType>();
              Map(a => a.Description).Column("AppointmentDescription");
              Map(a => a.Status).Column("AppointmentStatus").CustomType<AppointmentStatus>();
              HasMany(a => a.AppointmentEstates).Inverse().KeyColumn("FkAppointmentsId").Cascade.All();
              Table("Appointments");
          }
      }