Creating many to many junction table in Entity Framework - c#

I am trying to add a many to many relationship between two of my entities. I need a junction table with an additional field, I'm aware that means EF cannot do this automatically and that I need to create an Entity for my junction table.
I have the following models
public class Supplier
{
public int Id { get; set;}
public virtual ICollection<SupplierUsers> UserPermissions { get; set; }
}
And
public class User
{
public string Id { get; set;}
public virtual ICollection<SupplierUsers> UserPermissions { get; set; }
}
I need for a user to have a permission stored in the junction table. So I have created the following entity
public class SupplierUsers
{
public string UserId { get; set; }
public int SupplierId { get; set; }
public SupplierUserPermission Permission { get; set; }
public virtual Supplier Supplier { get; set; }
public virtual User User { get; set; }
}
In my OnModelCreating I've also added the following (this is probably where I'm going wrong)
modelBuilder.Entity<SupplierUsers>()
.HasKey(x => new { x.UserId, x.SupplierId });
This works to an extent, I can successfully add a user/supplier/permission to this table.
But I cannot add the same user / supplier multiple times to this table (probably due to the PK?).
How can I alter my code so that I can add the same user or supplier multiple times in this table?
Here's what the table structure looks like at the moment:
Thank you.

If i understand you correctly you want to add multiple equal pairs of UserId and SupplierId to SupplierUsers, right?
Add a SupplierUsersId field to your SupplierUsers entity and make it primary key.
public class SupplierUsers
{
public int SupplierUsersId { get;set; }
public string UserId { get; set; }
public int SupplierId { get; set; }
public SupplierUserPermission Permission { get; set; }
public virtual Supplier Supplier { get; set; }
public virtual User User { get; set; }
}
Remove the configuration from OnModelCreating()

Related

EF model change one-to-one relationship to many-to-many relationship

I have two tables with one-to-one relationship.
public class Name
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Category { get; set; }
public int? NameId { get; set; }
[ForeignKey("NameId ")]
public virtual Name Name { get; set; }
}
I already have data in those tables.
I know the database relations are not supported to be changed.
Is it possible to change one-to-one relationships to many-to-many relationships?
What is the most suitable approach to overcome this requirement?
Yes, you can still change that, using migrations.
Step 1 is to create a linking table, like NameCategories, which looks something like this:
public class NameCategories
{
public int Id { get; set; }
public int NameId { get; set; }
public Name Name { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
Step 2 is to reference this table in the tables you already have. In Name it would look like this
public class Name
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<NameCategory> Categories { get; set; }
}
Step 3 is to add a migration. You'll have some AddColumn() and some DropColumn() statements. In between them, when all the add stuff was executed but the drops not yet, you can add SQL code to carry over all the existing relations into the newly created table. After that, the old data will be deleted by the DropColumn() code. In your example, this would look something like this
INSERT INTO NameCategories (NameId, CategoryId)
SELECT (n.Id, c.Id) FROM Names n
JOIN Categories c on c.NameId = n.Id
WHERE ..
You can execute the SQL in the migration like this:
var sql = #"...";
Sql(sql);
I hope this helps you out!

Creating new table with foreign key with Entity Framework Core

I have a DbContext which I via the developer command prompt and creating a migrations schema turn in to my database. But if you look at the product object I have a dictionary object named Parts. That property does not get added to the Product table when the database is updated in the command prompt. I don't even know if it is possible what I am trying to do.
I want to add a table in the database named Parts and then add a foreign key to the Product table which connects the Parts dictionary object in the Product table, and the the new Parts table. Is this possible with Entity Framework Core?
public class ShoppingDbContext : IdentityDbContext<User>
{
public ShoppingDbContext(DbContextOptions options) : base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
}
public DbSet<Product> Products { get; set; }
public DbSet<Order> Orders { get; set; }
}
public class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public double Price { get; set; }
public int CategoryId { get; set; }
Dictionary<string, Part> Parts { get; set; }
}
EF Core can't currently map a dictionary property directly. If you want to create an association between Products and Parts, then define each of them as an entity. You can then create navigation properties between them--a reference from Part to the Product which it belongs, and a collection of Parts on Product. For example:
public class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public double Price { get; set; }
public int CategoryId { get; set; }
public ICollection<Part> Parts { get; set; }
}
public class Part
{
public int PartId { get; set; }
public int ProductId { get; set; }
public Product Product { get; set;}
}
Part also defines a property ProductId that acts as the FK to the Product entity. You don't need to add that property--EF will simulate it for you if you don't want it, but usually it is easier to deal with entities if the FK is mapped to a property.
Relationships are tracked through object references instead of foreign key properties. This type of association is called an independent association.
More Details Here:
https://msdn.microsoft.com/en-us/data/jj713564.aspx
Sample code:
public partial class Product
{
public Product()
{
this.Parts = new HashSet<Part>();
}
public int ProductId { get; set; }
public string ProductName { get; set; }
public double Price { get; set; }
public int CategoryId { get; set; }
public virtual ICollection<Part> Parts { get; set; }
}
Basically like what Arthur said, EF Core does not support it yet.
However, another way is to create a composite table should you want to or if it's viable for your use.
Here's a simple example:
// -------------- Defining BrandsOfCategories Entity --------------- //
modelBuilder.Entity<BrandCategory>()
.HasKey(input => new { input.BrandId, input.CatId })
.HasName("BrandsOfCategories_CompositeKey");
modelBuilder.Entity<BrandCategory>()
.Property(input => input.DeletedAt)
.IsRequired(false);
// -------------- Defining BrandsOfCategories Entity --------------- //
public class BrandCategory
{
public int CatId { get; set; }
public int BrandId { get; set; }
public DateTime? DeletedAt { get; set; }
public Category Category { get; set; }
public Brands Brand { get; set; }
}
The DeletedAt is optional of course. This handles M-M Relationships.
I had the same issue, I resolved it by removing the keyword virtual on the navigation properties and with in the ApplicatinDbContext

EF6 object relationships

I have a database, which I cannot modify because it is from an external application. The database contains tables that are linked, but not via the key - foreign key structure. Simplified, the structure looks like this:
Table - Warehouse
ID (Key)
WarehouseCode
Description
Table - Location
ID (Key)
LocationCode
Description
WarehouseCode
As you can see, the two tables can be linked via the WarehouseCode, but I don't understand how I can do that in EF6.
Edit: Vol 2.
It seems that you will have to wait until EF7
• Support for specifying a foreign key associations that on the principal end specify columns(s) that comprise a unique constraint but are not the primary key,
It Seems there no way entity framework can relate ONLY one warehouse to a Location since there is no guarantee that there will be only one warehouse with that Warehouse Code.
References
1
2
So a workaround might be to establish a many to many relationship using an extra table only for this.
public class Warehouse
{
public Int32 Id { get; set; }
public String WarehouseCode { get; set; }
public String Description { get; set; }
[InverseProperty("LocationWarehouses")]
public virtual ICollection<Location> LocationsWithThisWarehouse{ get; set; }
}
public class Location
{
public Int32 Id { get; set; }
public String LocationCode { get; set; }
public String Description { get; set; }
public String WarehouseCode { get; set; }
[InverseProperty("LocationsWithThisWarehouse")]
public virtual Icollection<Warehouse> LocationWarehouses { get; set; }
}
public class YourContext: DbContext
{
public DbSet<Location> Locations { get; set; }
public DbSet<Warehouse> Warehouses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
Database.SetInitializer < YourContext > (null);
modelBuilder.Entity<Warehouse>().ToTable("Warehouse", "SchemaName");
modelBuilder.Entity<Location>().ToTable("Location", "SchemaName");
modelBuilder.Entity<Warehouse>().HasMany(g => g.LocationsWithThisWarehouse).WithMany(t => t.LocationWarehouses).Map(m =>
{
m.MapLeftKey("WarehouseCodeOnWarehouse");
m.MapRightKey("WarehouseCodeOnLocation");
m.ToTable("WarehouseAtLocation");
});
}
}

Entity Framework Cascade Delete with Multiple Column Key

I have these models set up:
public class Advisor
{
public virtual int AdvisorId { get; set; }
public virtual int UserId { get; set; }
public User User { get; set; }
public ICollection<AdvisorStudentMap> AdvisorStudentMaps { get; set; }
}
public class AdvisorStudentMap
{
[Required]
public virtual int AdvisorId { get; set; }
[Required]
public virtual int UserId { get; set; }
}
public class User
{
public virtual int UserId { get; set; }
public virtual string UserName { get; set; }
public virtual string FirstName { get; set; }
public ICollection<AdvisorStudentMap> AdvisorStudentMaps { get; set; }
}
In my OnModelCreating I have:
modelBuilder.Entity<AdvisorStudentMap>()
.HasKey(t => new {t.AdvisorId, t.UserId});
In my fluent api how do I set it up so that when I delete an advisor, it deletes the AdvisorStudentMap as well? I keep getting the error: Message=Introducing FOREIGN KEY constraint 'FK_dbo.AdvisorStudentMaps_dbo.Users_UserId' on table 'AdvisorStudentMaps' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint. See previous errors.
Update - also in OnModelCreating I have:
modelBuilder.Entity<Advisor>()
.HasRequired(t => t.AdvisorStudentMaps).WithMany().WillCascadeOnDelete(true);
With that I get the error 'Cascading foreign key 'FK_dbo.Advisors_dbo.AdvisorStudentMaps_AdvisorId_UserId' cannot be created where the referencing column 'Advisors.AdvisorId' is an identity column.
You appear to be trying to model a many-to-many relationship between Students and Advisors. This would be the way you'd normally do that:
public class Advisor
{
//Key fields don't need to be marked virtual
public int AdvisorId { get; set; }
//If you want the property to lazy load then you should mark it virtual
public virtual ICollection<Student> Students{ get; set; }
//Advisors have a UserProfile
public int UserProfileId{get;set;}
public virtual UserProfile UserProfile {get; set;}
}
public class Student
{
public int StudentId { get; set; }
public virtual ICollection<Advisor> Advisors { get; set; }
//Students also have a UserProfile
public int UserProfileId{get;set;}
public virtual UserProfile UserProfile {get; set;}
}
public class UserProfile
{
public int UserProfileId{get;set;}
//NB not marked virtual - that is only needed on navigation properties when
//we want to use lazy loading
public string UserName {get;set}
public string FirstName {get;set}
}
Entity framework will automatically create the join table to model the relationship. You don't need the AdvisorStudentMap entity unless you need to add attributes to the relationship.
As for the cascade on delete problem. If you delete a User then this can cascade to the Student table and the Advisor table. There is a cascade path from Student to StudentAdvisorMap and another from Advisor to StudentAdvisorMap. Hence multiple cascade paths. Sql Server does not allow this. You will have to explicitly implement the deletes in your code to avoid this
I figured this out with help from this link: http://blog.cdeutsch.com/2011/09/entity-framework-code-first-error-could.html
As he says on the blog, it isn't very intuitive but here is the syntax that made it work:
modelBuilder.Entity<AdvisorStudentMap>()
.HasRequired(u=>u.User)
.WithMany(m=>m.AdvisorStudentMaps)
.WillCascadeOnDelete(false);

How to configure many to many relationship using entity framework fluent API

I'm trying to set up a many to many relationship in EF code first but the default conventions is getting it wrong. The following classes describes the relationship:
class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
class Account
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
One Account can have many Products.
However the EF conventions will create the DB tables as:
Products Table
--------------
Id
Name
Account_Id <- What is this?
Accounts Table
--------------
Id
Name
This doesn't look like a many-to-many table structure? How can i get configure the fluent API to reflect the relationship and create an intermediary table:
AccountProducts Table
---------------------
Account_Id
Product_Id
modelBuilder.Entity<Account>()
.HasMany(a => a.Products)
.WithMany()
.Map(x =>
{
x.MapLeftKey("Account_Id");
x.MapRightKey("Product_Id");
x.ToTable("AccountProducts");
});
What EF has suggested is a one-to-many relationship.
One Account can have many Products, i.e. each Product has an Account_Id
If you want a many to many relationship (and create an intermediary table), the following should work
class Product
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Account> Accounts { get; set; }
}
class Account
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
Code first is creating tables in right relational way. When
One Account can have many Products.
Products table should store key for its Account, as it actually does now.
What you are trying to see in db, is many-to-many relationship, not one to many. If you want to achieve that with code first, you should redesign your entities
class Product
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Account> Accounts { get; set; }
}
class Account
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
In this case, one product can have many accounts, and one account can have many products.
public AccountProductsMap()
{
this.ToTable("AccountProducts");
this.HasKey(cr => cr.Id);
this.HasMany(cr => cr.Account)
.WithMany(c => c.Products)
.Map(m => m.ToTable("AccountProducts_Mapping"));
}

Categories