Inheritance and Many-to-Many Relationship - entity-framework-4

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 thought 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; }
}
}

Related

Entity Framework database mapping relationships (Duplicate creation using Seed() method)

I created a post with an issue and another issue.
These can be looked at for references but i consider them as handled.
My question arising from these issues and the action i (need or not need) to apply bothers me because i don't quite understand EF its behavior and expectations.
I have a Product, PurchasePrice and SalesPrice entity where my initial thought was that 1 Product can have multiple PurchasePrices but that 1 PurchasePrice only can exist in 1 Product (same for SalesPrice).
Therefore these relations:
// NOTE that BaseEntity just has a "int ID" prop and datetimes/stamps
public class Product : BaseEntity
{
public ICollection<PurchasePrice> PurchasePrices { get; set; }
public ICollection<PurchasePrice> SalesPrices { get; set; }
}
public class PurchasePrice:BaseEntity
{
public Product Product { get; set; }
}
public class SalesPrice:BaseEntity
{
public Product Product { get; set; }
}
Now, lets add a Supplier Entity to it because that is why i seperate Sales & Purchase apart and don't create an Enum out of it, because 1 Product (in database) can have multiple suppliers, each having their own Sales/Purchase prices AND another Productnumber value.
So above becomes:
public class Product : BaseEntity
{
public ICollection<PurchasePrice> PurchasePrices { get; set; }
public ICollection<PurchasePrice> SalesPrices { get; set; }
// added
public ICollection<Supplier> Suppliers { get; set; }
}
public class PurchasePrice:BaseEntity
{
public Product Product { get; set; }
// added
public Supplier Supplier { get; set; }
}
public class SalesPrice:BaseEntity
{
public Product Product { get; set; }
// added
public Supplier Supplier { get; set; }
}
// added Entity Supplier into the party
public class Supplier : BaseEntity
{
public ICollection<Product> Products { get; set; }
public ICollection<PurchasePrice> PurchasePrices { get; set; }
public ICollection<SalesPrice> SalesPrices { get; set; }
}
Lets continue a little furhter because it doesn't stop there, i want to keep track of these Product-Supplier-Prices relations so i created a Entity called 'ProductSupplierForContract' which would have the following structure:
public class ProductSupplierForContract:BaseEntity
{
public string ProductnumberValue { get; set; }
public int Product_Id { get; set; }
public int Supplier_Id { get; set; }
public int? Contract_Id { get; set; }
public virtual Product Product { get; set; }
public virtual Supplier Supplier { get; set; }
public virtual Contract Contract { get; set; }
}
Finally i have a Contract Entity which has the following structure:
public class Contract:BaseEntity
{
[Required]
public ICollection<Product> Products { get; set; }
public ICollection<ProductSupplierForContract> ProductSupplierForContracts { get; set; }
}
So Product becomes:
public class Product : BaseEntity
{
public ICollection<PurchasePrice> PurchasePrices { get; set; }
public ICollection<PurchasePrice> SalesPrices { get; set; }
public ICollection<Supplier> Suppliers { get; set; }
// added
public ICollection<Contract> Contracts { get; set; }
}
Custom Seeding (inherits from DropCreateDatabaseAlways):
protected override void Seed(ApplicationDbContext context)
{
PurchasePrice purchaseprice = new PurchasePrice((decimal)17.70);
ctx.PurchasePrices.Add(purchaseprice);
Product product1 = new Product("test product 1",purchaseprice);
ctx.Products.Add(product1);
base.Seed(ctx);
}
I also have mappings defined in Fluent API:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// setting the Product FK relation required + related entity
modelBuilder.Entity<Entity.ProductSupplierForContract>().HasRequired(psfc => psfc.Product)
.WithMany(p => p.ProductSupplierForContracts)
.HasForeignKey(psfc => psfc.Product_Id);
// setting the Supplier FK relation required + related entity
modelBuilder.Entity<Entity.ProductSupplierForContract>().HasRequired(psfc => psfc.Supplier)
.WithMany(s => s.ProductSupplierForContracts)
.HasForeignKey(psfc => psfc.Supplier_Id);
// setting the Contract FK relation required + related entity
modelBuilder.Entity<Entity.ProductSupplierForContract>().HasOptional(psfc => psfc.Contract)
.WithMany(c => c.ProductSupplierForContracts)
.HasForeignKey(psfc => psfc.Contract_Id);
}
Now, initially i didn't had any issues and i really really don't understand what has brought up this sudden change that i now got duplicates Products when i seed my database. I can strip it down to just adding a simple PurchasePrice with a value and a Product having a reference to this PurchasePrice and there is my duplicate.
Changing the relation inside the PurchasePrice class of the Entity Product, to a ICollection doesn't create a duplicate but i don't want this collection because it is not a Many to Many relation ...
I have tried enormous amounts of things but nothing that "resolved" this (if this is a problem to start with, for me yes but maybe not for EF) like removing inhertance BaseEntity, changinge Mapping (Fluent AND annotations), changed the way i seeded and initialized everthing, defining ID's myself, you name it ...
Mind that the purpose is not to optimize the way i seed in anyway but to have a decent working Model AND to understand what EF does and what it wants.
My questions:
Why is this duplicate occuring/appearing ?
If i want to be able to have 1 instance holding the relation of
Price-Supplier-Product-Contract, how should i do this? Answer is here
I fixed my problem by redesigning the model. I have added a additional Entity ProductForSupplier which holds the relation of a Product & Supplier and a Productnumber.
public class ProductForSupplier:BaseEntity
{
public string ProductnumberValue { get; set; }
[Required]
public Product Product { get; set; }
[Required]
public Supplier Supplier { get; set; }
}
Added a Entity ProductsForContract which will hold the amount of a Product-Supplier relation for 1 contract:
public class ProductsForContract
{
public int ProductsForContractId { get; set; }
public int Amount { get; set; }
public ProductForSupplier ProductForSupplier { get; set; }
public Contract Contract { get; set; }
}
And the Existing Entity ProductSupplierForContract becomes:
public class ProductSupplierForContract:BaseEntity
{
public ICollection<ProductsForContract> ProductsForContract { get; set; }
[Required]
public Contract Contract { get; set; }
}
This gives me the flexibility to keep relations of any kind between the entities and also has taken care of the duplicate (which i still don't know the cause of).

Defining multiple Foreign Key for the Same table in Entity Framework Code First

I have two entities in my MVC application and I populated the database with Entity Framework 6 Code First approach. There are two city id in the Student entity; one of them for BirthCity, the other for WorkingCity. When I define the foreign keys as above an extra column is created named City_ID in the Student table after migration. Id there a mistake or how to define these FKs? Thanks in advance.
Student:
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public int BirthCityID { get; set; }
public int LivingCityID { get; set; }
[ForeignKey("BirthCityID")]
public virtual City BirthCity { get; set; }
[ForeignKey("LivingCityID")]
public virtual City LivingCity { get; set; }
}
City:
public class City
{
public int ID { get; set; }
public string CityName { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
To achieve what you want you need to provide some aditional configuration.Code First convention can identify bidirectional relationships, but not when there are
multiple bidirectional relationships between two entities.You can add configuration (using Data Annotations or the Fluent API) to present this
information to the model builder. With Data Annotations, you’ll use an annotation
called InverseProperty. With the Fluent API, you’ll use a combination of the Has/With methods to specify the correct ends of these relationships.
Using Data Annotations could be like this:
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public int BirthCityID { get; set; }
public int LivingCityID { get; set; }
[ForeignKey("BirthCityID")]
[InverseProperty("Students")]
public virtual City BirthCity { get; set; }
[ForeignKey("LivingCityID")]
public virtual City LivingCity { get; set; }
}
This way you specifying explicitly that you want to relate the BirthCity navigation property with Students navigation property in the other end of the relationship.
Using Fluent Api could be like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>().HasRequired(m => m.BirthCity)
.WithMany(m => m.Students).HasForeignKey(m=>m.BirthCityId);
modelBuilder.Entity<Student>().HasRequired(m => m.LivingCity)
.WithMany().HasForeignKey(m=>m.LivingCityId);
}
With this last solution you don't need to use any attibute.
Now, the suggestion of #ChristPratt in have a collection of Student in your City class for each relationship is really useful. If you do that, then the configurations using Data Annotations could be this way:
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public int BirthCityID { get; set; }
public int LivingCityID { get; set; }
[ForeignKey("BirthCityID")]
[InverseProperty("BirthCityStudents")]
public virtual City BirthCity { get; set; }
[ForeignKey("LivingCityID")]
[InverseProperty("LivingCityStudents")]
public virtual City LivingCity { get; set; }
}
Or using Fluent Api following the same idea:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>().HasRequired(m => m.BirthCity)
.WithMany(m => m.BirthCityStudents).HasForeignKey(m=>m.BirthCityId);
modelBuilder.Entity<Student>().HasRequired(m => m.LivingCity)
.WithMany(m => m.LivingCityStudents).HasForeignKey(m=>m.LivingCityId);
}
Sheesh. It's been a long day. There's actually a very big, glaring problem with your code, actually, that I completely missed when I commented.
The problem is that you're using a single collection of students on City. What's actually happening here is that EF can't decide which foreign key it should actually map that collection to, so it creates another foreign key specifically to track that relationship. Then, in effect you have no navigation properties for the collections of students derived from BirthCity and LivingCity.
For this, you have to drop down to fluent configuration, as there's no way to configure this properly using just data annotations. You'll also need an additional collection of students so you can track both relationships:
public class City
{
...
public virtual ICollection<Student> BirthCityStudents { get; set; }
public virtual ICollection<Student> LivingCityStudents { get; set; }
}
Then, for Student:
public class Student
{
...
public class StudentMapping : EntityTypeConfiguration<Student>
{
public StudentMapping()
{
HasRequired(m => m.BirthCity).WithMany(m => m.BirthCityStudents);
HasRequired(m => m.LivingCity).WithMany(m => m.LivingCityStudents);
}
}
}
And finally in your context:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new Student.StudentMapping());
}

Circular reference with ManyToMany relationship and automapper

Given the following Domain Entities:
public class Person {
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual ISet<PersonClub> Clubs { get; set; }
public Person() {
this.Clubs = new HashSet<PersonClub>();
}
}
public class Club {
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual ISet<PersonClub> Members { get; set; }
public Club() {
this.Persons = new HashSet<PersonClub>();
}
}
public class PersonClub {
public virtual Guid Id { get; set; }
public virtual Person Person { get; set; }
public virtual Club Club { get; set; }
}
and DTO's:
public class PersonDTO {
public Guid Id { get; set; }
public string Name { get; set; }
public ISet<ClubDTO> Clubs { get; set; }
public PersonDTO() {
this.Clubs = new HashSet<ClubDTO>();
}
}
public class ClubDTO {
public Guid Id { get; set; }
public string Name { get; set; }
public ISet<PersonDTO> Members { get; set; }
public ClubDTO() {
this.Members = new HashSet<PersonDTO>();
}
}
Is there a way to map these Domain Entities to their DTO's? The problem is that PersonDTO needs a collection of ClubDTO, not just Club, and vice versa for ClubDTO needing a collection of PersonDTO and not just Person.
This design causes an infinite loop when trying to map PersonClub -> PersonDTO and PersonClub -> ClubDTO:
Mapper.CreateMap<Person, PersonDTO>();
Mapper.CreateMap<Club, ClubDTO>();
Mapper.CreateMap<PersonClub, ClubDTO>()
.ConvertUsing(x => Mapper.Map<Club, ClubDTO>(x.Club));
Mapper.CreateMap<PersonClub, PersonDTO>()
.ConvertUsing(x => Mapper.Map<Person, PersonDTO>(x.Person));
I understand why this is happening, but am curious to how others handle this situation.
In this situation when calling Mapper.Map<Person, PersonDTO>(personEntity), it isn't necessary to load all members of all clubs that a person is a part of (The relationship doesn't need to go that deep ever). Same is true for Mapper.Map<Club, ClubDTO>(clubEntity).
Is this a design flaw? Would it be better to not have a PersonClub domain entity and just have public virtual ISet<Club> Clubs { get; set; } as the ManyToMany relationship? Although I believe this would still cause the circular reference.
Any input is appreciated.

Linking three Models with many to many links between all Models

I've been struggling with this for a few days.
I have three models that link together with many to many relationships.
Rules:
A requirement can have many controls and vice versa
A procedure can have many controls and vice versa
I am currently showing all controls linked to requirements in my requirements views without a problem and i've even got the create / update working through the creation of viewmodels that hold the assigned data
I'd like to show the list of all procedures that are linked to the controls which are linked to the requirement I am viewing. I won't want to edit them at that level as that will be done through the control Controller. It's a link through two join tables that i'm unable figure out :(
Models:
public class Requirement
{
[Key]
public int RequirementId { get; set }
public string Name { get; set; }
public string Details { get; set; }
public virtual ICollection<Control> Controls { get; set; }
}
public class Control
{
public int ControlId { get; set; }
public string Name { get; set; }
public virtual ICollection<Requirement> Requirements { get; set; }
public virtual ICollection<Procedure> Procedures { get; set; }
}
public class Procedure
{
[Key]
public int ProcedureId { get; set; }
public string Name { get; set; }
public string Details { get; set; }
public virtual ICollection<Control> Controls { get; set; }
}
Dbcontext:
public class CompliancePortalContext : DbContext
{
public CompliancePortalContext()
: base("CompliancePortalContext")
{ }
public DbSet<Control> Controls { get; set; }
public DbSet<Procedure> Procedures { get; set; }
public DbSet<Requirement> Requirements { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Procedure>().HasMany(c => c.Controls).WithMany(p => p.Procedures).Map(
mc =>
{
mc.MapLeftKey("ProcedureId");
mc.MapRightKey("ControlId");
mc.ToTable("ProcedureControl");
});
modelBuilder.Entity<Requirement>().HasMany(c => c.Controls).WithMany(r => r.Requirements).Map(
mc =>
{
mc.MapLeftKey("RequirementId");
mc.MapRightKey("ControlId");
mc.ToTable("RequirementControl");
});
base.OnModelCreating(modelBuilder);
}
}
This should give you all the procedures linked to all the controls linked to all requirements.
from r in Requirements
from c in r.Controls
from p in c.Procedures
select p

LINQ to entities against EF in many to many relationship

I'm using ASP.NET MVC4 EF CodeFirst.
Need help to write LINQ (to entities) code in Index action to get collection of Courses which are attended by selected student. The relationship is many to many with join table with payload.
//StudentController
//-----------------------
public ActionResult Index(int? id)
{
var viewModel = new StudentIndexViewModel();
viewModel.Students = db.Students;
if (id != null)
{
ViewBag.StudentId = id.Value;
// *************PROBLEM IN LINE DOWN. HOW TO MAKE COURSES COLLECTION?
viewModel.Courses = db.Courses
.Include(i => i.StudentsToCourses.Where(t => t.ObjStudent.FkStudentId == id.Value));
}
return View(viewModel);
}
The error I got is:
The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
I have modeles (the third one is for join table with payload):
//MODEL CLASSES
//-------------
public class Student
{
public int StudentId { get; set; }
public string Name { get; set; }
public virtual ICollection<StudentToCourse> StudentsToCourses { get; set; }
}
public class Course
{
public int CourseId { get; set; }
public string Title { get; set; }
public virtual ICollection<StudentToCourse> StudentsToCourses { get; set; }
}
public class StudentToCourse
{
public int StudentToCourseId { get; set; }
public int FkStudentId { get; set; }
public int FkCourseId { get; set; }
public string Classroom { get; set; }
public virtual Student ObjStudent { get; set; }
public virtual Course ObjCourse { get; set; }
}
Then, here is modelview I need to pass to view
//VIEWMODEL CLASS
//---------------
public class StudentIndexViewModel
{
public IEnumerable<Student> Students { get; set; }
public IEnumerable<Course> Courses { get; set; }
public IEnumerable<StudentToCourse> StudentsToCourses { get; set; }
}
EF does not support conditional include's. You'll need to include all or nothing (ie no Whereinside the Include)
If you need to get the data for just certain relations, you can select it into an anonymous type, something like (the obviously untested);
var intermediary = (from course in db.Courses
from stc in course.StudentsToCourses
where stc.ObjStudent.FkStudentId == id.Value
select new {item, stc}).AsEnumerable();
Obviously, this will require some code changes, since it's no longer a straight forward Course with a StudentsToCourses collection.

Resources