Fluent NHibernate: How do I map Many-to-Many Relationships with additional attributes on the relationship table?
I'm trying to map a many-to-many relationship between two entities, but I need to decorate that entity with a number of properties - see the diagram below:
Reads is my relationship table in this case - I added an identity column on it in order to avoid using a composite key, but the valuable information here is really the UserId, the FeedItemId, and the TimeRead attribute. Here is how I have tried to map this relationship, based on similar examples I've seen on StackOverFlow:
User
public class UserMap : ClassMap<User>
{
public UserMap()
{
Id(x => x.UserId).GeneratedBy.Identity();
Map(x => x.UserName).Length(DataConstants.UserNameLength).Unique().Not.Nullable();
Map(x => x.EmailAddress).Length(DataConstants.EmailAddressLength).Unique().Not.Nullable();
Map(x => x.DateJoined).Not.Nullable();
Map(x => x.Password).Length(DataConstants.PasswordHashLength).Not.Nullable();
HasManyToMany(x => x.UserRoles).Cascade.AllDeleteOrphan().AsBag().Table("UsersInRole");
HasManyToMany(x => x.SubscribedFeeds).Cascade.DeleteOrphan().AsBag().Table("Subscriptions");
HasManyToMany(x => x.OwnedFeeds).Cascade.All().AsBag().Table("FeedOwners");
HasMany(x => x.Reads).Cascade.DeleteOrphan().Fetch.Join().Inverse().KeyColumn("UserId");
}
}
FeedItem
public class FeedItemMap : ClassMap<FeedItem>
{
public FeedItemMap()
{
Id(x => x.FeedItemId).GeneratedBy.Identity();
Map(x => x.OriginalUri).Not.Nullable().Unique().Length(DataConstants.FeedUriLength);
Map(x => x.DatePublished).Not.Nullable();
Map(x => x.Title).Not.Nullable().Length(DataConstants.FeedItemTitleLength);
References(x => x.Feed);
HasManyToMany(x => x.Tags).Cascade.All().Table("PostTags");
HasManyToMany(x => x.Categories).Cascade.All().Table("PostsInCategory");
HasMany(x => x.Reads).Cascade.AllDeleteOrphan().Inverse().Fetch.Join().KeyColumn("FeedItemId");
}
}
Reads:
public class FeedReadMap : ClassMap<FeedRead>
{
public FeedReadMap()
{
Table("Reads");
//CompositeId()
// .KeyProperty(x => x.TimeRead, "TimeRead")
// .KeyReference(x => x.ItemRead, "FeedItemId")
// .KeyReference(x => x.Reader, "UserId");
Id(x => x.ReadId).GeneratedBy.Identity();
Map(x => x.TimeRead).Not.Nullable();
References(x => x.Reader).Not.Nullable().Cascade.SaveUpdate().Column("UserId");
References(x => x.ItemRead).Not.Nullable().Cascade.SaveUpdate().Column("FeedItemId");
}
}
This code doesn't raise errors as-is, but nothing is ever persisted to the Reads table when I try to do the following:
var read = new FeedRead {ItemRead = feed.Items[0], Reader = user, TimeRead = DateTime.Now};
user.Reads.Add(read);
feed.Items[0].Reads.Add(read);
_repository.SaveUser(user);
This is probably because of the fact that both User and FeedItem have a .Inverse on the relationship mapping - I did it this way because it's what I saw in most other examples that tried to model this same relationship.
When I remove the .Inverse off of the User mapping, I get this error instead:
NHiber开发者_如何学Gonate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing.
My ultimate goal is to be able to do Session.SaveOrUpdate(user) and have it insert any new feed reads directly into the Reads table, but I haven't been able to find a way to do it yet. What am I doing wrong?
I've read through virtually every other question on StackOverFlow about this topic already and haven't found a clear answer to this question.
It all depends on how you want to save new Reads.
If you want to save new Reads via User, then you need Cacascade.AllDeleteOrphan() for User. As it stands, it will not cascade the new reads as you only have DeleteOrphan.
public class UserMap : ClassMap<User>
{
public UserMap()
{
Id(x => x.UserId).GeneratedBy.Identity();
Map(x => x.UserName).Length(DataConstants.UserNameLength).Unique().Not.Nullable();
Map(x => x.EmailAddress).Length(DataConstants.EmailAddressLength).Unique().Not.Nullable();
Map(x => x.DateJoined).Not.Nullable();
Map(x => x.Password).Length(DataConstants.PasswordHashLength).Not.Nullable();
HasManyToMany(x => x.UserRoles).Cascade.AllDeleteOrphan().AsBag().Table("UsersInRole");
HasManyToMany(x => x.SubscribedFeeds).Cascade.DeleteOrphan().AsBag().Table("Subscriptions");
HasManyToMany(x => x.OwnedFeeds).Cascade.All().AsBag().Table("FeedOwners");
HasMany(x => x.Reads).Cascade.AllDeleteOrphan().Fetch.Join().Inverse().KeyColumn("UserId");
}
}
Just a thought, I always like to minimize bi-directional relationships in an attempt to keep the domain simpler. Hence I would probably not have the collections on User or FeedItem if it could be avoided.
精彩评论