Inheritance and Many-to-Many Relationship
I have the following business role that I need to model:
A bidder could rate a seller as long as they've interacted with this person
A bidder could rate an item only if he had won the auction
The final rating for the seller though, is the average taken from the item rating and the others' ratings on himself.
The rating itself (whether for the item or the user) is the average of scores on several questions.
Accordingly, I thou开发者_运维技巧ght I should create a Ratings class, then inherit it with UserRating and ItemRating. Both of those should have an ICollection of RatingQuestion (which will eventually be a static table). The questions for the UserRating are different from those of the ItemRating, but I thought it's not really worth creating separate tables/entities for the questions (or maybe I should do a TPH inheritance?).
So, here's what I got so far:
public abstract class Rating
{
public int Id { get; set; }
public virtual User By { get; set; }
}
public class UserRating : Rating
{
public virtual User For { get; set; }
public virtual ICollection<RatingQuestion> Questions { get; set; }
}
public class ItemRating : Rating
{
public virtual Item For { get; set; }
public virtual ICollection<RatingQuestion> Questions { get; set; }
}
public class RatingQuestion
{
public int Id { get; set; }
public string Text { get; set; }
public virtual ICollection<Rating> Rating { get; set; }
}
The reason why I am putting the ICollection inside the sub-classes rather than the Rating base class is because the RatingQuestion for both is different, but I'm not sure that's the way I should be doing it, correct me if I'm wrong please.
One thing I need some help with is deciding whether to go for a TPH or a TPT inheritance. I want to keep things simple, but I would also want to keep my database normalized. Moreover, performance is a factor that needs to be taken into account.
Now the last thing I need to know how to do is: how to map the many-to-many relationship between the rating classes (the base class or sub-classes, not sure about which one I should be using) and the RatingQuestion class using the Fluent API AND add an attribute (score) which is a property of the relationship itself so I could record the score on every separate RatingQuestion.
I hope that was clear enough. All suggestions are most welcome. Thanks in advance!
UPDATE: (after Ladislav Mrnka's answer)
public abstract class Rating
{
public int Id { get; set; }
public virtual User By { get; set; }
public virtual ICollection<RatingQuestion> RatingQuestions { get; set; }
}
public class UserRating : Rating
{
public virtual User For { get; set; }
public virtual ICollection<Question> Questions { get; set; }
}
public class ItemRating : Rating
{
public virtual Item For { get; set; }
public virtual ICollection<Question> Questions { get; set; }
}
public class User
{
public int Id { get; set; }
//more properties
public virtual ICollection<UserRating> OwnRatings { get; set; }
public virtual ICollection<UserRating> RatingsForOthers { get; set; }
public virtual ICollection<ItemRating> ItemRatings { get; set; }
}
public class Item
{
public int Id { get; set; }
//more properties
public virtual ItemRating Rating { get; set; } //because an Item will have only one rating
}
public class UserRatingConfiguration : EntityTypeConfiguration<UserRating>
{
public UserRatingConfiguration()
{
HasOptional(p => p.By)
.WithMany(u => u.RatingsForOthers)
.IsIndependent()
.Map(m => m.MapKey(c => c.Id, "RatingSubmitter"));
HasRequired(p => p.For)
.WithMany(u => u.OwnRatings)
.IsIndependent()
.Map(m=>m.MapKey(c => c.Id, "RatedSeller"));
}
}
public class ItemRatingConfiguration : EntityTypeConfiguration<ItemRating>
{
public ItemRatingConfiguration()
{
HasRequired(p => p.By)
.WithMany(u => u.ItemRatings)
.IsIndependent()
.Map(m=>m.MapKey(c => c.Id, "ItemRatingSubmitter"));
}
}
I'm getting a very messed up model in SQL Server, which is obviously caused by my messed up mapping. Any suggestions or should I just forget about inheritance and the DRY principle all together in the case at hand?
You can't use direct M:N mapping if you need to add custom property to that relation. In such case you need to model junction table as another entity which will hold reference to Rating
and Question
and also include Score
property.
I would recommend using TPH inheritance. It is easier to use and it has better performance. TPT constructs really ugly queries. Also there is no reason to have RatingQuestions in derived classes. Both these collections reference same type so you can move it to parent. Moreover according to this question there are some problems with navigation properties in child classes when using TPH. I'm not sure if this problem is still valid in Code-first. Anyway your current model simply don't need navigation property on child.
If you follow my advices you don't need to add any mapping. It will map with default conventions when using these classes:
public abstract class Rating
{
public virtual int Id { get; set; }
public virtual User By { get; set; }
private ICollection<RatingQuestion> _ratingQuestions = null;
public ICollection<RatingQuestion> RatingQuestions
{
get
{
if (_ratingQuestions == null)
{
_ratingQuestions = new HashSet<RatingQuestion>();
}
return _ratingQuestions;
}
protected set { _ratingQuestions = value; }
}
}
public class ItemRating : Rating
{
public virtual Item For { get; set; }
}
public class UserRating : Rating
{
public virtual User For { get; set; }
}
public class RatingQuestion
{
public virtual int Id { get; set; }
public virtual int Score { get; set; }
public virtual Rating Rating { get; set; }
public virtual Question Question { get; set; }
}
public class Question
{
public virtual int Id { get; set; }
public virtual string Text { get; set; }
private ICollection<RatingQuestion> _ratingQuestions = null;
public ICollection<RatingQuestion> RatingQuestions
{
get
{
if (_ratingQuestions == null)
{
_ratingQuestions = new HashSet<RatingQuestion>();
}
return _ratingQuestions;
}
protected set { _ratingQuestions = value; }
}
}
精彩评论