Mapping many to many relationship - entity-framework-4.1

I am have some trouble getting Entity Framework to handle a many to many relationship in my data schema. Here is my model:
public class User
{
public int UserId { get; set; }
public int Username { get; set; }
public IEnumerable<Customer> Customers { get; set; }
...
}
public class Customer
{
public int CustomerId { get; set; }
...
}
public class CustomerUser
{
public int CustomerUserId { get; set; }
public int CustomerId { get; set; }
public int UserId { get; set; }
public DateTime CreatedTimestamp { get; set; }
...
}
Here is the mapping:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>().HasKey(u => u.UserId).ToTable("Users");
modelBuilder.Entity<Customer>().HasKey(c => c.CustomerId).ToTable("Customer");
modelBuilder.Entity<CustomerUsers>().HasKey(cu => cu.CustomerUserId).ToTable("CustomerUsers");
modelBuilder.Entity<CustomerUsers>()
.HasRequired(cu => cu.User)
.WithRequiredDependent()
.Map(m =>
{
m.ToTable("Users");
m.MapKey("CustomerUsers.UserId");
});
}
My database has a Users, Customers, and CustomerUsers table with columns that match the model.
I am trying to execute the following query:
result = (from u in context.Users
join customerUsers in context.CustomerUsers on u.UserId equals customerUsers.User.UserId
join customers in context.Customers on customerUsers.CustomerId equals customers.CustomerId into ps
select new
{
User = u,
Customers = ps
}).ToList().Select(r => { r.User.Customers = r.Customers.ToList(); return r.User; });
When I run the code, I get the following error:
The Column 'CustomerUserId' specified as part of this MSL does not exist in MetadataWorkspace
Can anyone see what is wrong with my approach?
Thanks!
I should note that I am intentionally trying to not include a reference to the CustomerUsers table from either the Customer or User class. The majority of the time, the payload of the CustomerUsers table is not important, only which customers are associated to which users. There are some reporting scenarios where the additional information in the join table is necessary, but since this is not the typical situation, I would like to avoid cluttering up the models by having this additional indirection.

Instead of trying to map this as many to many, map it as two one to many relationships. See the discussion of many to many join tables with payload in Many-to-Many Relationships in this tutorial:
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/creating-a-more-complex-data-model-for-an-asp-net-mvc-application

For your model you will need probably two one-to-many relationships and the following navigation properties:
public class User
{
public int UserId { get; set; }
public int Username { get; set; }
// ...
public ICollection<CustomerUser> CustomerUsers { get; set; }
}
public class Customer
{
public int CustomerId { get; set; }
//...
public ICollection<CustomerUser> CustomerUsers { get; set; }
}
public class CustomerUser
{
public int CustomerUserId { get; set; }
public int CustomerId { get; set; }
public int UserId { get; set; }
public DateTime CreatedTimestamp { get; set; }
//...
public User User { get; set; }
public Customer Customer { get; set; }
}
And the following mapping:
modelBuilder.Entity<CustomerUser>()
.HasRequired(cu => cu.User)
.WithMany(u => u.CustomerUsers)
.HasForeignKey(cu => cu.UserId);
modelBuilder.Entity<CustomerUser>()
.HasRequired(cu => cu.Customer)
.WithMany(c => c.CustomerUsers)
.HasForeignKey(cu => cu.CustomerId);

Related

EF Code First Many to Many relationship creates a duplicate joining table?

I'm trying to create a many to many relationship between Product and Category with a joining table using EF 6.4.4 and MySQL 8, but unfortunately without success?
public class AppDbContext : DbContext
{
//Schema Tables
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
public DbSet<ProductCategory> ProductCategories { get; set; }
}
public abstract class BaseEntity
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
}
public class Product : BaseEntity
{
public virtual IList<Category> Categories { get; set; }
}
public class Category : BaseEntity
{
public virtual IList<Product> Products { get; set; }
}
now for the joining table I tried this:
public class ProductCategory
{
[Key, Column(Order = 1)]
public int ProductID { get; set; }
[Key, Column(Order = 2)]
public int CategoryID { get; set; }
public Product Product { get; set; }
public Category Category { get; set; }
}
and this:
public class ProductCategory : BaseEntity
{
public int ID { get; set; }
[ForeignKey(nameof(Product)), Column(Order = 1)]
public int ProductID { get; set; }
[ForeignKey(nameof(Category)), Column(Order = 2)]
public int CategoryID { get; set; }
public Product Product { get; set; }
public Category Category { get; set; }
}
and also this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ProductCategory>().HasKey(x => new { x.ProductID, x.CategoryID });
}
the real problem is no matter what I do EF creates a duplicate joining table?
products
categories
productcategories
productcategory1 (duplicate)
Edit: apparently you can't do this according to this post and this one two, but there is a workaround.
// a workaround for the problem
public abstract class BaseEntity
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
}
public class Product : BaseEntity
{
public virtual IList<ProductCategory> ProductCategories { get; set; }
}
public class Category : BaseEntity
{
public virtual IList<ProductCategory> ProductCategories { get; set; }
}
public class ProductCategory
{
[Key, Column(Order = 1)]
public int ProductID { get; set; }
[Key, Column(Order = 2)]
public int CategoryID { get; set; }
public virtual Product Product { get; set; }
public virtual Category Category { get; set; }
}
I think you have created productcategory class twice. You can remove second one and add fk relationship s in first class definition
You likely need to be explicit with the relationship mapping. I would get rid of the ProductCategories DbSet in the DbContext. If this is EF6 or EF Core 5 then I'd get rid of the ProductCategory entity definition all-together, especially if the table just consists of the two FKs/composite key:
EF 6
modelBuilder.Entity<Product>()
.HasMany(x => x.Categories)
.WithMany(x => x.Products)
.Map(x => {
x.ToTable("ProductCategories");
x.MapLeftKey("ProductId");
x.MapRightKey("CategoryId");
});
EF Core < 5
For EF Core 2/3 the many-to-many needs to be mapped more as a many-to-one-to-many where the collections on each side need to be declared as ProductCategory entities.
public class Product
{
// ...
public virtual ICollection<ProductCategory> ProductCategories { get; set; } = new List<ProductCategory>();
}
public class Category
{
// ...
public virtual ICollection<ProductCategory> ProductCategories { get; set; } = new List<ProductCategory>();
}
EF Core 5
EF Core 5 adds UsingEntity to help define the joining table for many-to-many relationships leaving Product to have Categories (instead of ProductCategories) and Category to have Products (likewise).
modelBuilder.Entity<Product>()
.HasMany(x => x.Categories)
.WithMany(x => x.Products)
.UsingEntity<ProductCategory>(
x => x.HasOne(pc => pc.Product).WithMany().HasForeignKey(pc => pc.ProductId),
x => x.HasOne(pc => pc.Category).WithMany().HasForeignKey(pc => pc.CategoryId));

EF Core 2 Stopping Circular Dependency on Many to Many Relationship

I am using the Sakila Sample Database from MySql on a MySql server. The Diagram looks as follows.
The important tables are the store, inventory and film tables. The is a many-to-many relationship between the tables and the linker table is the inventory table.
I scaffolded this Database in a new dotnetcore project using EFCore 2.
I am trying to get a list of stores and their list of films.
The Entities are defined as follows:
Store
public class Store
{
public Store()
{
Customer = new HashSet<Customer>();
Inventory = new HashSet<Inventory>();
Staff = new HashSet<Staff>();
}
public byte StoreId { get; set; }
public byte ManagerStaffId { get; set; }
public short AddressId { get; set; }
public DateTimeOffset LastUpdate { get; set; }
public Address Address { get; set; }
public Staff ManagerStaff { get; set; }
public ICollection<Customer> Customer { get; set; }
public ICollection<Inventory> Inventory { get; set; }
public ICollection<Staff> Staff { get; set; }
}
Inventory
public partial class Inventory
{
public Inventory()
{
Rental = new HashSet<Rental>();
}
public int InventoryId { get; set; }
public short FilmId { get; set; }
public byte StoreId { get; set; }
public DateTimeOffset LastUpdate { get; set; }
public Film Film { get; set; }
public Store Store { get; set; }
public ICollection<Rental> Rental { get; set; }
}
Film
public partial class Film
{
public Film()
{
FilmActor = new HashSet<FilmActor>();
FilmCategory = new HashSet<FilmCategory>();
Inventory = new HashSet<Inventory>();
}
public short FilmId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public short? ReleaseYear { get; set; }
public byte LanguageId { get; set; }
public byte? OriginalLanguageId { get; set; }
public byte RentalDuration { get; set; }
public decimal RentalRate { get; set; }
public short? Length { get; set; }
public decimal ReplacementCost { get; set; }
public string Rating { get; set; }
public string SpecialFeatures { get; set; }
public DateTimeOffset LastUpdate { get; set; }
public Language Language { get; set;
public Language OriginalLanguage { get; set; }
public ICollection<FilmActor> FilmActor { get; set; }
public ICollection<FilmCategory> FilmCategory { get; set; }
public ICollection<Inventory> Inventory { get; set; }
}
My context looks as follows:
modelBuilder.Entity<Inventory>(entity =>
{
entity.ToTable("inventory", "sakila");
entity.HasIndex(e => e.FilmId)
.HasName("idx_fk_film_id");
entity.HasIndex(e => new { e.StoreId, e.FilmId })
.HasName("idx_store_id_film_id");
And lastly the repo looks as follows:
public IEnumerable<Store> GetStores()
{
return _context.Store.
Include(a => a.Inventory).
ToList();
}
Problem:
When I call this method from a Controller to get the list of stores I don´t get any json response on Postman. Yet if I debug into the list that is returned from the Controller I find the list of stores.
The problem is that the list contains:
store->inventory->film->store->inventory->film->store... Etc. Creating a circular dependency that fills up the allowed Process memory of the request.
Possible Solutions:
I think it has to do with the fact that on the Context both the Foreign Keys are defined as HasIndex instead of HasKey
entity.HasIndex(e => new { e.StoreId, e.FilmId })
.HasName("idx_store_id_film_id");
When I define it as HasKey then I get an Error:
'The relationship from 'Rental.Inventory' to 'Inventory.Rental' with
foreign key properties {'InventoryId' : int} cannot target the primary
key {'StoreId' : byte, 'FilmId' : short} because it is not compatible.
Configure a principal key or a set of compatible foreign key
properties for this relationship.'
To answer #hamzas comment, I did find a solution to this problem. I used EFCore to build the entities and the DBContext through scaffolding (DB First). As a best practice you should be using Models (Dtos) to represent the Data for the client. EFCore is very helpful in giving us the flexibility to access this M to N relationship however we want. This gives us the flexibility to represent this Data to the client however we want.
Whatever your use case might be. You have to convert the M to N relationship into an 1 to N model.
Use Case #1: You want to show all the movies for a specific store.
Solution
Step #1: You create a StoreDto (Model)
public class StoreDto
{
int StoreId { get; set; }
ICollection<FilmDto> Films { get; set; }
= new List<FilmDto> ();
}
Step #2: Create a FilmDto
public class FilmDto
{
int FilmId { get; set; }
int StoreId { get; set; }
string FilmName { get; set; }
}
Step #3: You provide a Mapping with auto mapper
public class MappingProfiles : Profile
{
public MappingProfiles()
{
CreateMap<Store, StoreDto>();
CreateMap<Film, FilmDto>();
}
}
Step #4: Query the data correctly, Unfortunately I don´t have this example anymore to test this code, so here is where you´ll have to experiment a bit
public Store GetFilmsForStore(byte StoreId)
{
return _context.Store.
Include(a => a.Inventory).
ThenInclude(i => i.Film)
ToList();
}
On the "Include" part you want to only get the Inventory entries where StoreId == Inverntory.StoreId and then Include the Films Object from the resulting list.
I hope you get the jist of it. You want to break up your m to n relationships and make them seem like 1 to m for your clients.

Populating a list using Foreign Key Values in MVC

I'm developing a prototype platform using MVC in which users can make a profile and use that profile to make text posts, like a social media site. I have the following two tables in my database:
Profiles
public partial class Profile
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Profile()
{
this.Posts = new HashSet<Post>();
}
public int Id { get; set; }
public string UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Country_ { get; set; }
public System.DateTime DoB { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Post> Posts { get; set; }
}
}
Posts
public partial class Post
{
public int Id { get; set; }
public string Text { get; set; }
public System.DateTime DATE { get; set; }
public int ProfileId { get; set; }
public virtual Profile Profile { get; set; }
}
}
In my ViewModel I have a list of Posts called PostList, which I want to populate with all of the Post records made by the user. So, I need to populate the list with all records where the ProfileId in Posts is equal to the Id of Profile, which is determined by whether or not the UserId in Profile is equal to the current user's Identity.
In short, I need:
Post List = Posts where ProfileId = Profiles.Id Where Profiles.UserId = CurrentUserId.
Any ideas? I've tried the following, but it's totally wrong:
var userId = User.Identity.GetUserId();
ViewModels.ProfileViewModel pVm = new ViewModels.ProfileViewModel();
pVm.PostList = db.Posts.Include(db.Profiles).Where(a => a.UserId == userId).ToList();
pVm.UserName = User.Identity.GetUserName();
return View(pVm);
You just walk the relationship in your query:
pVm.PostList = db.Posts.Where(a => a.Profile.UserId == userId);
It's unnecessary to use Include since Profile will automatically be joined to make the query.

unidirectional many-to-many relationship with Code First Entity Framework

I am new to EF, and trying to get many-to-many unidirectional relationship with code first approach. For example, if I have following two classes (not my real model) with be a N * N relationship between them, but no navigation property from "Customer" side.
public class User {
public int UserId { get; set; }
public string Email { get; set; }
public ICollection TaggedCustomers { get; set; }
}
public class Customer {
public int CustomerId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
The mapping code looks like ...
modelBuilder.Entity()
.HasMany(r => r.TaggedCustomers)
.WithMany(c => c.ANavgiationPropertyWhichIDontWant)
.Map(m =>
{
m.MapLeftKey("UserId");
m.MapRightKey("CustomerId");
m.ToTable("BridgeTableForCustomerAndUser");
});
This syntax force me to have "WithMany" for "Customer" entity.
The following url, says "By convention, Code First always interprets a unidirectional relationship as one-to-many."
Is it possible to override it, or should I use any other approach?
Use this:
public class User {
public int UserId { get; set; }
public string Email { get; set; }
// You must use generic collection
public virtual ICollection<Customer> TaggedCustomers { get; set; }
}
public class Customer {
public int CustomerId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
And map it with:
modelBuilder.Entity<User>()
.HasMany(r => r.TaggedCustomers)
.WithMany() // No navigation property here
.Map(m =>
{
m.MapLeftKey("UserId");
m.MapRightKey("CustomerId");
m.ToTable("BridgeTableForCustomerAndUser");
});

EF Code First Additional column in join table for ordering purposes

I have two entities that have a relationship for which I create a join table
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Image> Images { get; set; }
}
public class Image
{
public int Id { get; set; }
public string Filename { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>()
.HasMany(i => i.Images)
.WithMany(s => s.Students)
.Map(m => m.ToTable("StudentImages"));
}
I would like to add an additional column to allow chronological ordering of the StudentImages.
Where should I add insert the relevant code?
Do you want to use that new column in your application? In such case you cannot do that with your model. Many-to-many relation works only if junction table doesn't contain anything else than foreign keys to main tables. Once you add additional column exposed to your application, the junction table becomes entity as any other = you need third class. Your model should look like:
public class StudentImage
{
public int StudentId { get; set; }
public int ImageId { get; set; }
public int Order { get; set; }
public virtual Student Student { get; set; }
public virtual Image Image { get; set; }
}
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<StudentImage> Images { get; set; }
}
public class Image
{
public int Id { get; set; }
public string Filename { get; set; }
public virtual ICollection<StudentImage> Students { get; set; }
}
And your mapping must change as well:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<StudentImages>().HasKey(si => new { si.StudentId, si.ImageId });
// The rest should not be needed - it should be done by conventions
modelBuilder.Entity<Student>()
.HasMany(s => s.Images)
.WithRequired(si => si.Student)
.HasForeignKey(si => si.StudentId);
modelBuilder.Entity<Image>()
.HasMany(s => s.Students)
.WithRequired(si => si.Image)
.HasForeignKey(si => si.ImageId);
}