Question

I would like to be able to add a collection of Notes to any of my main entities in my NHibernate application. I can see how you could do this with a seperate junction table per entity. However, I would like to be able to avoid this and only have one junction table - if this is possible.

Below is the code so far, however this will result in all Notes being loaded for every Entity and I only want to load the notes for that particular entity. What are the alternative approaches I need to take?

    public class Entity
    {
        public virtual int Id { get; set; }
    }

    public class EntityType1 : Entity
    {
        public EntityType1()
        {
            Notes = new List<Note>();
        }
        public virtual string EntityTypeName { get; set; }
        public virtual IList<Note> Notes {get;set;}
    }

    public class EntityType2 : Entity
    {
        public EntityType2()
        {
            Notes = new List<Note>();
        }
        public virtual string EntityType2Name { get; set; }
        public virtual IList<Note> Notes { get; set; }
    }

    public class Note
    {
        public virtual int Id { get; set; }
        public virtual IList<Entity> Entities { get; set; }
        public virtual string NoteText { get; set; }
    }
}

namespace FluentNHib.Mappings
{
    public class EntityMap : ClassMap<Entity>
    {
        public EntityMap()
        {
            Id(m => m.Id);
        }
    }
    public class EntityType1Map : ClassMap<EntityType1>
    {
        public EntityType1Map()
        {
            Id(m => m.Id);
            Map(m => m.EntityTypeName1);
            HasManyToMany(m => m.Notes).Table("EntityToNotes")
            .ParentKeyColumn("EntityId")
            .ChildKeyColumn("NoteId")
            .LazyLoad()
            .Cascade.SaveUpdate();
        }
    }

    public class EntityType2Map : ClassMap<EntityType2>
    {
        public EntityType2Map()
        {
            Id(m => m.Id);
            Map(m => m.EntityType2ame);
            HasManyToMany(m => m.Notes).Table("EntityToNotes")
            .ParentKeyColumn("EntityId")
            .ChildKeyColumn("NoteId")
            .LazyLoad()
            .Cascade.SaveUpdate();

        }
    }

    public class NoteMap : ClassMap<Note>
    {
        public NoteMap()
        {
            Id(m => m.Id);
            Map(m => m.NoteText);
        }
    }
Was it helpful?

Solution

I am not sure what the real issue is:

...however this will result in all Notes being loaded for every Entity and I only want to load the notes for that particular entity...

Is the issue in lazy loading? or in fact that Entity1 and Entity2 can have same ID, therefore the references are mixed? (I expect that and this should be part of the answer below)

Anyhow, I would say that we can achieve what you need: map the Note with just one table EntityToNotes. And that is good.

But, in general, I would descourage you from using the many-to-many. It is just my own feeling, experience. Below are some links with more explanation:

Draft of the SOLUTION:

So, firstly we have to extend the table "EntityToNotes" with two columns

  • EntityToNoteId column - we need a primary key for new pairing object
  • Discriminator column

The Discriminator column will be used for (almost like a standard inheritance)

  1. inserting Discriminator value during creation
  2. filtering te IList<Notes> per Entity

These could be the pairing Entity (with an abstract base gathering the common stuff)

public abstract class EntityToNote<TEntity>
{
    public abstract string Discriminator { get; set; }

    public virtual TEntity Entity {get;set;}
    public virtual Note    Note   {get;set;}
}
// the pairing objects
public class EntityType1ToNote : EntityToNote<EntityType1>
{
    string _discriminator = "EntityType1"; // here we set the discriminator
    public virtual string Discriminator
    {
        get { return _discriminator; }
        set { _discriminator = value; }
    }
...
// Similar for other pairing objects

The Entities will now be referencing lists of pairing objects

public class EntityType1 : Entity
{
    public virtual IList<EntityType1ToNote> Notes {get;set;}
    ...

public class EntityType2 : Entity
{
    public virtual IList<EntityType2ToNote> Notes { get; set; }
    ...

Here is snippet of the mapping (all other Entities will have usual mapping, including ClassMaps for EntityType1ToNote, EntityType2ToNote...)

public class EntityType1Map : ClassMap<EntityType1>
{
    public EntityType1Map()
    {
        Id(m => m.Id);
        Map(m => m.EntityTypeName1);
        HasMany(m => m.Notes)
          // this "table" setting is redundant, it will come from EntityType1ToNote
          //.Table("EntityToNotes")
          .KeyColumn("EntityId")
          // here is the trick, that only related rows will be selected
          .Where("Discriminator = 'EntityType1'")
          .Cascade.AllDeleteOrphan();
    }
}

As I tried to explain in the links provided, we gained this way a lot. Mostly the ability to use more columns on the pairing table - e.g. Discriminator (later we can have more columns like SortBy...) and we are able to use powerful searching with subqueries - see Query on HasMany reference

Also, in fact, the pairing could be mapped via the real inheritance... But the main point here is: Instead of many-to-many we introduced the pairing object and gained a lot

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top