Composite Key/Id Mapping with NHibernate
Asked Answered
M

2

19

I have the following tables in my database:

Announcements:
- AnnouncementID (PK)
- Title

AnouncementsRead (composite PK on AnnouncementID and UserID):
- AnnouncementID (PK)
- UserID (PK)
- DateRead

Users:
- UserID (PK)
- UserName

Usually I'd map the "AnnouncementsRead" using a many-to-many relationship but this table also has an additional "DateRead" field.

So far I have defined the following entities:

    public class Announcement
    {
        public virtual int AnnouncementID { get; set; }
        public virtual string Title { get; set; }
        public virtual IList<AnnouncementRead> AnnouncementsRead { get; private set; }

        public Announcement()
        {
            AnnouncementsRead = new List<AnnouncementRead>();
        }
    }

    public class AnnouncementRead
    {
        public virtual Announcement Announcement { get; set; }
        public virtual User User { get; set; }
        public virtual DateTime DateRead { get; set; }
    }

    public class User
    {
        public virtual int UserID { get; set; }
        public virtual string UserName { get; set; }
        public virtual IList<AnnouncementRead> AnnouncementsRead { get; private set; }

        public User()
        {
            AnnouncementsRead = new List<AnnouncementRead>();
        }
 }

With the following mappings:

public class AnnouncementMap : ClassMap<Announcement>
{
    public AnnouncementMap()
    {
        Table("Announcements");
        Id(x => x.AnnouncementID);
        Map(x => x.Title);
        HasMany(x => x.AnnouncementsRead)
            .Cascade.All();
    }
}

public class AnnouncementReadMap : ClassMap<AnnouncementRead>
{
    public AnnouncementReadMap()
    {
        Table("AnnouncementsRead");
        CompositeId()
            .KeyReference(x => x.Announcement, "AnnouncementID")
            .KeyReference(x => x.User, "UserID");
        Map(x => x.DateRead);
    }
}

public class UserMap : ClassMap<User>
{
    public UserMap()
    {
        Table("Users");
        Id(x => x.UserID);
        Map(x => x.UserName);
        HasMany(x => x.AnnouncementsRead)
            .Cascade.All();
    }
}

However when I run this I receive the following error:

"composite-id class must override Equals(): Entities.AnnouncementRead"

I'd appreciate it if someone could point me in the right direction. Thanks

Mismatch answered 9/8, 2010 at 10:32 Comment(0)
C
9

You should do just what NHibernate is telling you. AnnouncementRead should override Equals and GetHashCode methods. They should be based on fields that are part of primary key

Chitin answered 9/8, 2010 at 11:2 Comment(0)
J
1
  1. When implementing equals you should use instanceof to allow comparing with subclasses. If Hibernate lazy loads a one to one or many to one relation, you will have a proxy for the class instead of the plain class. A proxy is a subclass. Comparing the class names would fail.
    More technically: You should follow the Liskows Substitution Principle and ignore symmetricity.
  2. The next pitfall is using something like name.equals(that.name) instead of name.equals(that.getName()). The first will fail, if that is a proxy.

http://www.laliluna.de/jpa-hibernate-guide/ch06s06.html

Josi answered 18/1, 2013 at 16:19 Comment(1)
The question relates to C# and NHibernate, not Hibernate and Java. There's no such thing instanceof, no such problem like .name-vs-.getName. While your answer is in general OK and a person experienced both in C# and Java can "port the concepts" across language/platform boundary. However, there are not many such people, and that makes your answer hardly usable.Horripilate

© 2022 - 2024 — McMap. All rights reserved.