EntityFramework Same Table Many to Many Relationship - c#

I have a table called Products which obviously contains products.
However, I need to create related products. So what I've done is create a junction table called product_related which has two PKs. ProductID from Products table and RelatedID also from Products table.
I already use EF and have set up everything on other tables. How should I add this properly in order to create a relationship with products as such:
product.Products.Add(product object here). Of course here product represent a product object that I've fetched from the db using db.Products.FirstOr....
How should I do this properly ? A many to many to the same table?
Thanks.

In order to create a many-to-many relationship with Database-First approach you need to setup a database schema that follows certain rules:
Create a Products table with a column ProductID as primary key
Create a ProductRelations table with a column ProductID and a column RelatedID and mark both columns as primary key (composite key)
Don't add any other column to the ProductRelations table. The two key columns must be the only columns in the table to let EF recognize this table as a link table for a many-to-many relationship
Create two foreign key relationships between the two tables:
The first relationship has the Products table as primary-key-table with the ProductID as primary key and the ProductRelations table as foreign-key-table with only the ProductID as foreign key
The second relationship also has the Products table as primary-key-table with the ProductID as primary key and the ProductRelations table as foreign-key-table with only the RelatedID as foreign key
Enable cascading delete for the first of the two relationships. (You can't do it for both. SQL Server won't allow this because it would result in multiple cascading delete paths.)
If you generate an entity data model from those two tables now you will get only one entity, namely a Product entity (or maybe Products if you disable singularization). The link table ProductRelations won't be exposed as an entity.
The Product entity will have two navigation properties:
public EntityCollection<Product> Products { get { ... } set { ... } }
public EntityCollection<Product> Products1 { get { ... } set { ... } }
These navigation collections are the two endpoints of the same many-to-many relationship. (If you had two different tables you wanted to link by a many-to-many relationship, say table A and B, one navigation collection (Bs) would be in entity A and the other (As) would be in entity B. But because your relationship is "self-referencing" both navigation properties are in entity Product.)
The meaning of the two properties are: Products are the products related to the given product, Products1 are the products that refer to the given product. For example: If the relationship means that a product needs other products as parts to be manufactured and you have the products "Notebook", "Processor", "Silicon chips" then the "Processor" is made of "Silicon chips" ("Silicon chips" is an element in the Products collection of the Processor product entity) and is used by a "Notebook" ("Notebook" is an element in the Products1 collection of the Processor product entity). Instead of Products and Products1 the names MadeOf and UsedBy would be more appropriate then.
You can safely delete one of the collections from the generated model if you are only interested in one side of the relationship. Just delete for example Products1 in the model designer surface. You can also rename the properties. The relationship will still be many-to-many.
Edit
As asked in a comment the model and mapping with a Code-First approach would be:
Model:
public class Product
{
public int ProductID { get; set; }
public ICollection<Product> RelatedProducts { get; set; }
}
Mapping:
public class MyContext : DbContext
{
public DbSet<Product> Products { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>()
.HasMany(p => RelatedProducts)
.WithMany()
.Map(m =>
{
m.MapLeftKey("ProductID");
m.MapRightKey("RelatedID");
m.ToTable("product_related");
});
}
}

Lets take your Example:
Related table
Related_id PK
Related_name
Date
Product Table
Product_id PK
Related_id FK
Product_Name
Date
How to Represent it in EF
Related Model Class named as RelatedModel
[Key]
public int Related_id { get; set; }
public string Related_name {get;set}
public Datetime Date{get;set;}
Product Model Class named as ProductModel
[Key]
public int Product_id { get; set; }
public string Product_name {get;set}
public string Related_id {get;set}
public Datetime Date{get;set;}
[ForeignKey("Related_id ")] //We can also specify here Foreign key
public virtual RelatedModel Related { get; set; }
In this way we can Create Relations between Two table
Now In Case of Many to Many Relation I would like to take another Example here
Suppose I have a Model Class Enrollment.cs
public class Enrollment
{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public decimal? Grade { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
Here CourseID and StudentId are the two foreign Keys
Now I Have another Class Course.cs where we will create Many to Many Relation.
public class Course
{
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Hope This will help!!!

Related

Many-to-many relationships without link tables/models?

I am creating a ASP.NET web API project (database first) and it pulls the data from the MSSQL database (read-only access). Database have several tables but there is no primary/secondary keys (we cannot change it). I have set up of one-to-many relations without any problem, but when it comes to the many-to-many, I had to use link tables for holding keys from both side.
public class Student
{
public int StudentId { get; set; }
public string Name { get; set; }
public IList<StudentCourse> StudentCourses { get; set; }
}
public class Course
{
public int CourseId { get; set; }
public string CourseName { get; set; }
public string Description { get; set; }
public IList<StudentCourse> StudentCourses { get; set; }
}
Link table:
public class StudentCourse
{
public int StudentId { get; set; }
public Student Student { get; set; }
public int CourseId { get; set; }
public Course Course { get; set; }
}
Because link table is not present in the database I am getting error of "Data.SqlClient.SqlException: 'Invalid object name 'StudentCourse' ".
public class SchoolContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=.\\SQLEXPRESS;Database=EFCore-SchoolDB;Trusted_Connection=True");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<StudentCourse>().HasKey(sc => new { sc.StudentId, sc.CourseId });
}
public DbSet<Student> Students { get; set; }
public DbSet<Course> Courses { get; set; }
public DbSet<StudentCourse> StudentCourses { get; set; }
Relationships:
modelBuilder.Entity<StudentCourse>().HasKey(sc => new { sc.SId, sc.CId });
modelBuilder.Entity<StudentCourse>()
.HasOne<Student>(sc => sc.Student)
.WithMany(s => s.StudentCourses)
.HasForeignKey(sc => sc.SId);
modelBuilder.Entity<StudentCourse>()
.HasOne<Course>(sc => sc.Course)
.WithMany(s => s.StudentCourses)
.HasForeignKey(sc => sc.CId);
I considered Joining tables on these keys but it seems not efficient way of handling relationships and getting related records. What work arounds would you suggest?
Entity Framework is an Object-to-Relational Mapper, Mapper being the key term here. In order to have a relationship between objects to map, there must be a relationship between those entities in the relational database.
Think of it this way, if there is a relationship between students and courses, how is that relationship represented within your database? If I asked you to write two SQL queries against that database, how would you return the following data?
List all students for a specific Course.
List all courses for a specific Student.
You cannot do that with just a Course and a Student table. If there is no linking table then you either have a 1-to-many relationship one way or the other, or the database handles it in a non-relational way. (Such as Student containing a string field with a comma-delimited list of Course IDs) In which case, EF will be of little help.
If the database does not support recording a mapping between students and their courses where you can query how one student can participate in many courses, while each course can have many students participating, then EF cannot be configured to somehow auto-magically read and persist such a relationship. A many-to-many table must exist within the database or such a relationship cannot exist. With EF6 and EF Core 5+ you may read that EF can handle many-to-many relationships without a linking entity, which is true, but that does not mean without a linking table. The table must exist, but you don't need to define a StudentCourse (or CourseStudent) entity.
Instead of:
public IList<StudentCourse> StudentCourses { get; set; }
in both Student and Course, Student can have:
public IList<Course> Courses { get; set; }
while Course has:
public IList<Student> Students { get; set; }
This still maps through a linking table, and provided that relationship just consists of a StudentId and CourseId in the table with no other columns that need to be mapped/configured, EF can manage that relationship and table entirely behind the scenes.
If there is a linking table, just not named what you'd expect: You can call the entity whatever you want and use configuration via attributes, DbContext.OnModelCreating(ModelBuilder), or EntityTypeConfiguration to map that entity and properties to the existing table and columns, however they might be named. But such a table must exist to support that relationship.

Class property for a Foreign key

My question is two fold and related to each other
How to define or write a property inside a class which represents a foreign key and how do you populate it using dapper
Tech i am working with :
Using dapper as an ORM
Database in MySQL
Using WPF/C# .netframework 4.7
For Ex : lets say there is are two entities customer and a product,
a customer has a Customer table and a product has a Product table
A customer can have zero or many products and a single product should only relate to one person, so this is a one to many relationship from customer to product
NOTE : I know there can be more customers for a single product and that a customer can buy more than one product, THIS is just a simple example where the problem is not the the database design but how to define those fields that are foreign keys, as properties in a class where the ORM being used is dapper and the DATABASE is MySQL
in Customer table there are 2 fields Customer ID and Customer name
in Product table there are 3 field , Product ID and Product Name and C_CustomerID, here C_CustomerID is the foreign key that references the Customer Table's Primary KEY
so you generally make two classes right a customer and a product Class with its properties
for Instance Customer table has
public int CustomerID { get; set; }
public string CustomerName { get; set; }
and Product table has
public int ProductID { get; set; }
public string ProductName { get; set; }
The problem is how to define the foreign key
is it like this
Option 1.
public int C_CustomerID { get; set; }
Option 2.
public List<CustomerID> C_CustomerID { get; set; }
Option 3.
public List<Customer> C_CustomerID { get; set; }
Option 4.
public List<Customer> Customer { get; set; }
and how do you populate these, do you use stored procedures or do you use functions from dapper or manual C# code
You should model your data after "real life", not after the database. I will skip your requirement that the product should know who bought it, that's not a simplification, but will only make it harder. A Customer can buy multiple products and should have a list of the products bought. Normally it would be done like this:
public class Customer
{
public int CustomerID { get; set; }
public string CustomerName
public List<Product> Products { get; set; }
}
public class Product
{
public int ProductID { get; set; }
public string ProductName { get; set; }
}
The foreign key relationship is an implementation detail of your database, it does not belong in your data model. You should therefore create a link table CustomerProductLink containing a CustomerId and a ProductId, here you will add a row for every Product bought by any Customer. The link table is also just an implementation detail, you shouldn't model it.
When you load a Customer you tailor your SQL to only load the products that are bought by that customer, something like:
SELECT * FROM Customers AS C
INNER JOIN CustomerProductLink AS L ON C.CustomerID = L.CustomerID
INNER JOIN Products AS P ON P.ProductId = L.ProductID
WHERE C.CustomerID = #CustomerID;
You load the customer using Dapper multimapping.
This way your model describes the data domain as it is, and how the relationship is modelled gets hidden in your data layer.
After writing this, I found this tutorial describing pretty much the same thing.
In Product Model add
public int CustomerID { get; set; }
public List<Customer> Customers { get; set; }

EF 6 code-first generating extra table for many-many table?

Referencing from the #Ogglas answer of this post,
I would like to ask if it is normal for EF to generate another table?
If the additional table should not be there, then what am I doing wrong here? Please enlighten me. TIA!
Sample code:
public class Aggregate
{
public Aggregate()
{
Episodes = new HashSet<Episode>();
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid Id { get; set; }
...
public virtual ICollection<Episode> Episodes { get; set; }
}
public class Episode
{
public Episode()
{
Aggregates = new HashSet<Aggregate>();
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public Guid Id { get; set; }
...
public virtual ICollection<Aggregate> Aggregates { get; set; }
}
public class EpisodeAggregate
{
[Key]
[Column(Order = 1)]
[ForeignKey("Episode")]
public Guid EpisodeId { get; set; }
[Key]
[Column(Order = 2)]
[ForeignKey("Aggregate")]
public Guid AggregateId { get; set; }
public virtual Episode Episode { get; set; }
public virtual Aggregate Aggregate { get; set; }
public DateTime Timestamp { get; set; }
}
In my DbContext.cs:
public DbSet<EpisodeAggregate> EpisodeAggregates { get; set; }
You are right. In a relational database, a many-to-many relation is solved using a junction table.
For a standard many-to-many relationship, you don't need to mention this junction table; entity framework recognizes the many-to-many by your use of the virtual ICollection<...> on both sides of the many-to-many relation, and will automatically create the tables for you.
To test my database theories and entity framework, I quite often use a simple database with Schools, Students and Teachers. One-to-many for School-Student and School-Teacher and many-to-many for Teacher-Student. I always see that the Teacher-Student junction table is created automatically, without ever having to mention it.
However!
Your junction table is not standard. A standard junction table has only two columns: the EpisodeId and the AggregateId. It doesn't even have an extra primary key. The combination [EpisodeId, AggregateId] is already unique and can be used as a primary key.
You have in table EpisodeAggregate an extra column: TimeStamp. Apparently you want to know when an Episode and an Aggregate got related.
"Give me the TimeStamp when Episode[4] got related with Aggregate[7]"
This makes that this table is not a standard junction table. There is no many-to-many relation between Episodes and Aggregates. You made a one-to-many relation between Episode and its Relations with the Aggregates, and similarly a one-to-many relation between Aggregates and its Relations with the Episodes.
This makes that you have to change your many-to-many into one-to-many:
class Episode
{
public Guid Id { get; set; }
// every Episode has zero or more Relations with Aggregates (one-to-many)
public virtual ICollection<EpisodeAggregateRelation> EpisodeAggregateRelations { get; set; }
...
}
class Aggregate
{
public Guid Id { get; set; }
// every Episode has zero or more Relations with Episodes(one-to-many)
public virtual ICollection<EpisodeAggregateRelation> EpisodeAggregateRelations { get; set; }
...
}
class EpisodeAggregateRelation
{
// Every Relation is the Relation between one Episode and one Aggregate
// using foreign key:
public Guid EpisodeId { get; set; }
public Guid AggregateId { get; set; }
public virtual Episode Episode { get; set; }
public virtual Aggregate Aggregate { get; set; }
public DateTime Timestamp { get; set; }
}
If you are certain the there will always be at utmost one relation between an Episode and an Aggregate, you can use the combination [EpisodeId, AggregateId] as a primary key. If you think these two might have several relations, you need to add a separate primary key.
I often use my classes in different databases, hence I don't like attributes, I solve it in fluent API in OnModelCreating:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Episode>()
.HasKey(episode => episode.Id)
// define the one-to-many with EpisodeAggregateRelations:
.HasMany(episode => episode.EpisodeAggregateRelations)
.WithRequired(relation => relation.Episode)
.HasForeignKey(relation => relation.EpisodeId);
modelBuilder.Entity<Aggregate>()
.HasKey(aggregate => aggregate.Id)
// define the one-to-many with EpisodeAggregateRelations:
.HasMany(aggregate => aggregate .EpisodeAggregateRelations)
.WithRequired(relation => relation.Aggregate)
.HasForeignKey(relation => relation.aggregateId);
The above is not needed!
Because you followed the entity framework code first conventions, you can omit these two statements. Entity framework will recognize the primary key and the one-to-many relation. Only if you want to deviate from the conventions, like a non-standard table name, or if you want to define the column order:
modelBuilder.Entity<Episode>()
.ToTable("MySpecialTableName")
.Property(episode => episode.Date)
.HasColumnName("FirstBroadcastDate")
.HasColumnOrder(3)
.HasColumnType("datetime2");
But again: you followed the conventions, all those attributes like Key, ForeignKey, DatabaseGenerated are not needed. And the column order: who cares? Let your database management system decide about the most optimum column order.
My advice would be: try to experiment: leave out this fluent API and check whether your unit tests still pass. Checked in five minutes.
The EpisodeAggregateRelation has something non-standard: it has a composite primary key. Hence you need to define this. See Configuring a composite primary key
modelBuilder.Entity<EpisodeAggregateRelation>()
.HasKey(relation => new
{
relation.EpisodId,
relation.AggregateId
});
If you already defined the one-to-many in Episodes and Aggregates, or if that was not needed because of the conventions, you don't have to mention this relation here again.
If you want, you can put the one-to-many in the fluent API part of EpisodeAggregateRelation, instead of in the fluent API part of Episode / Aggregate:
// every EpisodeAggregateRelation has one Episode, using foreign key
modelBuilder.Entity<EpisodeAggregateRelation>()
.HasRequired(relation => relation.Episode(
.WithMany(episode => episode.EpisodeAggregateRelations)
.HasForeignKey(relation => relation.EpisodeId);
// similar for Aggregate
One final tip
Don't create a HashSet in the constructor. It is a waste of processing power if you fetch data: you create the HashSet, and it is immediately replaced by the ICollection<...> that entity framework creates.
If you don't believe me: just try it out, and see that your unit tests pass, with the possible exception of the unit test that checks for an existing ICollection<...>

One-to-one relationship in Entity Framework with ASP.NET Identity tables [duplicate]

I am having an issue getting a reference to the employee object from the PayGroup object using Entity Framework 6.1. I have a foreign key in the database on PayGroup.SupervisorId -> Employee.EmployeeId. Note that this is a zero or one-to-one relationship (a pay group can only have one supervisor and an employee can only be the supervisor of one pay group).
According to this post on GitHub, it is not possible to have a foreign key on a table with a different primary key. I've added the foreign key to the database manually but I can't figure out how to set up the fluent api mapping to be able to get the employee object from pay group.
Pay Group Table
Employee Table
Note: There is a foreign key from PayGroup.SupervisorId - Employee.EmployeeId in the database.
Below are the DTO's (I don't currently have any working relationship mapping between these classes):
public class PayGroup
{
public int Id { get; set; }
public string SupervisorId { get; set; }
public virtual Employee Supervisor { get; set; }
}
public class Employee
{
public string EmployeeId { get; set; }
public string FullName { get; set; }
}
one-to-one relationship with explicit FK property (like your PayGroup.SupervisorId) is not supported.
So remove that property from the model:
public class PayGroup
{
public int Id { get; set; }
public virtual Employee Supervisor { get; set; }
}
and use the following fluent mapping:
modelBuilder.Entity<PayGroup>()
.HasRequired(e => e.Supervisor)
.WithOptional()
.Map(m => m.MapKey("SupervisorId"));
The WithOptional() call specifies two things. First that there is no inverse navigation property in Employee class, and second that the FK is optional (Allow Nulls = true in the table).
If you decide to add inverse navigation property
public class Employee
{
public string EmployeeId { get; set; }
public string FullName { get; set; }
public virtual PayGroup PayGroup { get; set; } // <=
}
change it to WithOptional(e => e.PayGroup).
If you want to make it required (Allow Nulls = false in the table), then use the corresponding WithRequiredDependent overload (Dependent here means that the Employee will be the principal and PayGroup will be the dependent).

Entity Framework, code-first, how to add primary key to table out of many-to-many relationship mapping?

I am building ASP.NET MVC 5 application. I am using Entity Framework 6.1, code first approach to generate a database. I have a many-to-many relationship between Product and Category.
public class Product
{
public long Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
// navigation
public virtual ICollection<Category> Categories { get; set; }
}
public class Category
{
public long Id { get; set; }
public string Name { get; set; }
// navigation
public virtual ICollection<Product> Products { get; set; }
}
In the Dbcontext class I override OnModelCreating method to create table for many-to-many relationship as below:
modelBuilder.Entity<Product>().HasMany<Category>(s => s.Categories).WithMany(c => c.Products)
.Map(cs =>
{
cs.MapLeftKey("ProductId");
cs.MapRightKey("CategoryId");
cs.ToTable("ProductCategories");
});
The table comes out as joining the two foreign keys. How do I add an Id (as primary key) to this junction table?
ProductCategories
- Id // add id as primary key
- ProductId
- CategoryId
Let me expand #bubi's answer:
By default, when you define many-to-many relationship (using attributes or FluentAPI), EF creates it (add additional table to DB) and allows you to add many products to a category and many categories to a product. But it doesn't allow you to access the linking table rows as entities.
If you need such feature, for example you what to manage these links some way like mark them as "deleted" or set a "priority", you need to:
Create new Entity (ProductCategoryLink)
Add it to your Context as another DbSet
Update relations in Product and Category entities accordingly.
For you it could like:
Entities
public class Product
{
[Key]
public long ProductId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
[InverseProperty("Product")]
public ICollection<ProductCategoryLink> CategoriesLinks { get; set; }
}
public class Category
{
[Key]
public long CategoryId { get; set; }
public string Name { get; set; }
[InverseProperty("Category")]
public ICollection<ProductCategoryLink> ProductsLinks { get; set; }
}
public class ProductCategoryLink
{
[Key]
[Column(Order=0)]
[ForeignKey("Product")]
public long ProductId { get; set; }
public Product Product { get; set; }
[Key]
[Column(Order=1)]
[ForeignKey("Category")]
public long CategoryId { get; set; }
public Category Category { get; set; }
}
I used attribute-way to define relations as I prefer this approach more. But you can easily replace it by a FluentAPI with two one-to-many relations:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Product to Links one-to-many
modelBuilder.Entity<ProductCategoryLink>()
.HasRequired<Product>(pcl => pcl.Product)
.WithMany(s => s.CategoriesLinks)
.HasForeignKey(s => s.ProductId);
// Categories to Links one-to-many
modelBuilder.Entity<ProductCategoryLink>()
.HasRequired<Category>(pcl => pcl.Category)
.WithMany(s => s.ProductsLinks)
.HasForeignKey(s => s.CategoryId);
}
Context
It's not required but most likely you'll need to save links directly to context, so let's define a DbSet for them.
public class MyContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<Category > Categories{ get; set; }
public DbSet<ProductCategoryLink> ProductCategoriesLinks { get; set; }
}
Two ways of implementation
Another reason why I used attributes to define relations is that it shows (both marked with [Key] attribute (also pay attention to [Column(Order=X)] attribute])) that two FKs in ProductCategoriesLink entity become a composite PK so you don't need to define another property like "ProductCategoryLinkId" and mark it as a special PK field.
You always could find desired linking entity all you need is just both PK's:
using(var context = new MyContext)
{
var link = context.ProductCategoriesLinks.FirstOrDefault(pcl => pcl.ProductId == 1
&& pcl.CategoryId == 2);
}
Also this approach restricts any chance to save several links with the same Product and Category as they are complex key. If you prefer the way when Id is separated from FK's you'll need to add UNIQUE constraint manually.
Whichever way you choose you'll reach your aim to manipulate the links as you need and add additional properties to them if you need.
Note 1
As we defined many-to-many links as separate entity Product and Category don't have direct relation to each other anymore. So you'll need to update your code:
Instead of adding Product directly to Category or Category directly to Product now you need to define a ProductCategoryLink entity and save it using one of three ways depending on your logic's context:
public void AddProductToCategory(Product product, Company company)
{
using (var context = new MyContext())
{
// create link
var link = new ProductCategoryLink{
ProductId = product.ProductId, // you can leave one link
Product = product, // from these two
CategoryId = category.CategoryId, // and the same here
Category = category
};
// save it
// 1) Add to table directly - most general way, because you could
// have only Ids of product and category, but not the instances
context.ProductCategoriesList.Add(link);
// 2) Add link to Product - you'll need a product instance
product.CategoriesLinks.Add(link);
// 3) Add link to Category - you'll need a category instance
category.ProductLinks.Add(link);
context.SaveChanges();
}
}
Note 2
Also remember as your properties now navigate to ProductCategoryLinks (not to Products for categories and not for Categories for products) if you need to query the second linked entity you need to .Include() it:
public IEnumerable<Product> GetCategoryProducts(long categoryId)
{
using (var context = new MyContext())
{
var products = context.Categories
.Include(c => c.ProductsCategoriesLinks.Select(pcl => pcl.Product))
.FirstOrDefault(c => c.CategoryId == categoryId);
return products;
}
}
UPD:
There is a same question with detailed answer on SO:
Create code first, many to many, with additional fields in association table
If you need a model like you posted (a "clean" model) you need to disable automatic migration and create the table by yourself. EF will not handle Id so it has to be autonumbering.
If you need to handle and see Id inside your app, your model is different and the Junction table must have a class in the model.

Categories