0
votes

I have been spinning around in the same place trying to figure this issue with a many-to-many table with extra columns. I am swallowing my pride and asking more experienced Fluent NHibernate experts if you can help out here.

I have been trying to use a component for a many-to-many table but I don't seem to find where to point the component to cause a join on a specific column. I guess as long as I populate the completion information object, the presence and usage of a component is not a requirement for me.

This is my simplified schema.

User
...................
Id
Name 

Task
...................
Id
Name


Job
...................
Id
Name

JobTask
...................
JobId
TaskId
CompletedByUserId
CompletionDate
CompletionNotes

Then I have objects like so:

 public class Job
 {
    long Id { get; set; }
    IList<Task> Tasks { get; set; }
 }


 public class Task
 {
    long Id { get; set; }
    string Name { get; set; }
    CompletionInfo CompletionInfo { get; set; }
 }

 public class CompletionInfo
 {
    User User { get; set; }
    DateTime CompletionDate { get; set; }
 }

Can you help with your ideas how to implement this in fluent nhibernate?

I had this mapping before on the JobMap and it would work but only for the many-to-many columns ( TaskId, JobId). I need the extra information related to that relationship(dateCompleted, userCompleted,etc)

 HasManyToMany<Task>(job => job.Tasks)
            .Table(ManyToManyTable)
             .ParentKeyColumn("JobId")
            .ChildKeyColumn("TaskId")
            .Not.LazyLoad()
            .Cascade.Delete();

In order to simplify things with the many-to many I thought to create an object to represent the relationship and encapsulate a task. Like so: And I would add a proxy in the Job task to modify the actual Task properties. But that is not going anywhere.

This must be something common out there, I am very surprised there not much regarding this issue. Naturally I would have loved a way to extend the HasManyToMany but not sure how, hence this post.

public class JobTask : Entity<long, JobTask>
{ 
    public virtual Job ParentJob { get; set; }

    public virtual Task Task   { set;set; }

    public virtual TaskCompletionDetails CompletionInformation
    {
        get
        {
            if (this.Task == null)
                return null;

            return Task.CompletionInformation;
        }

        set
        {
            if (this.Task == null)
                return;

            this.Task.CompletionInformation = value;
        }
    }


    public virtual string CompletionNotes
    {
        get
        {
            if (this.Task == null || this.Task.CompletionInformation == null)
                return null;

            return Task.CompletionInformation.Notes;
        }

        set
        {
            if (this.Task == null)
                return;

            this.Task.CompletionInformation.Notes = value;
        }
    }

    public virtual DateTime? CompletionDate
    {
        get
        {
            if (this.Task == null || this.Task.CompletionInformation == null)
                return null;

            return Task.CompletionInformation.Date;
        }

        set
        {
            if (this.Task == null)
                return;

            this.Task.CompletionInformation.Date = value;
        }
    }

    public virtual IUser User
    {
        get
        {
            if (this.Task == null || this.Task.CompletionInformation == null)
                return null;

            return Task.CompletionInformation.User;
        }

        set
        {
            if (this.Task == null || value == null)
                return;

            if (this.Task.CompletionInformation != null)
                this.Task.CompletionInformation.User = value;
        }
       }
    }
 }

This would be the map direction I have started for it:

public class JobTaskMap : ClassMap<JobTask>
{
    private const string ModelTable = "[JobTasks]";

    public JobTaskMap()
    {
        Table(ModelTable);

        Id(jobTask => jobTask.Id)
            .Column("Id")
            .GeneratedBy.Identity();

        References<Job>( jobTask => jobTask.ParentJob)
            .Column("JobId")
            .Fetch.Join();

        Component<Task>( jobTask => (Task) jobTask.Task,
                // This is the task
                 comp =>
                 { 
                     // This is the task completion information
                     comp.Component<TaskCompletionDetails>(
                         task => (TaskCompletionDetails)task.CompletionInformation,
                         compInfo =>
                         {    
                             compInfo.Map(info => info.Date)
                                .Column("CompletionDate")
                                .Nullable();

                             compInfo.Map(info => info.Notes)
                                .Column("CompletionNotes")
                                .Nullable();

                             compInfo.References<User>(info => info.User)
                                .Column("CompletedByUserId")
                                .Nullable()
                                .Fetch.Join();
                         });  
                 });

    }

These are some other related reading I have followed without success:

Reference:

http://wiki.fluentnhibernate.org/Fluent_mapping

Fluent Nhibernate Many-to-Many mapping with extra column

1

1 Answers

1
votes

heres a way which depends on the id generation strategy used. if Identity is used then this won't do (at least NH discourages use of Identity for various reasons), but with every strategy that inserts the id itself it would work:

class JobMap : ClassMap<Job>
{
    public JobMap()
    {
        Id(x => x.Id);

        HasMany(x => x.Tasks)
            .KeyColumn("JobId");
    }
}

class TaskMap : ClassMap<Task>
{
    public TaskMap()
    {
        Table("JobTask");
        Id(x => x.Id, "TaskId");

        Component(x => x.CompletionInfo, c =>
        {
            c.Map(x => x.CompletionDate);
            c.References(x => x.User, "CompletedByUserId");
        });

        Join("Task", join =>
        {
            join.Map(x => x.Name, "Name");
        });
    }
}