ForeignKey - Definition in Many-to-Many (EntityFramework)? - c#

Because I don't like the naming convention the entityFramework (Code first) uses I usually use a foreignKey property to assign my own property:
public class User {
public virtual Customer Customer { get; set; }
[ForeignKey("Customer")]
public Guid CustomerId { get; set; }
}
Works just fine, a column "CustomerId" is created inside the "User"-Table.
But how do I achieve the same when I need a many-to-many relationship?
public class User {
public virtual ICollection<Role> Roles { get; set; }
}
public class Role {
public virtual ICollection<User> Users { get;set; }
}
This results in a Table "UserRole" which contains "User_id" and "Role_Id".
Still, I prefer "UserId" and "RoleId". I could just create a UserRole-Class myself containing the references to User and Role but there should be a more elegant way. Is there any?

You could configure that many-to-many relationship on your context using Fluent Api as I show below:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasMany<Role>(u => u.Roles)
.WithMany(c => c.Users)
.Map(cs =>
{
cs.MapLeftKey("UserId");
cs.MapRightKey("RoleId");
cs.ToTable("UserRoles");
});
}
This way you could name the junction table and the FK columns with the names that you want.
Another way is create an entity that represent the junction table and stablish two one-to-many relationships with User and Role, but if you don't need to add extra columns to the junction table, It's recommended use the first variant, but you can find some advantages in map this table explicitly.

Related

Purpose of OnModelCreating - EF Core Database first Approach

I am studying EF Core with database first. There is no issue to get entities and DbContext after reverse-engineering. But I couldn't understand the role(or purpose) OnModelCreating Method in DbContext(database first approach).
Here is code snippet.
public partial class VitiLevuContext : DbContext
{
public virtual DbSet<Order> Orders { get; set; }
public virtual DbSet<Invoice> Invoices { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Invoice>(entity =>
{
entity.ToTable("Invoice");
entity.Property(e => e.DueAmount)
.IsRequired();
entity.Property(e => e.PaidAmount).HasColumnType("money");
entity.HasOne(d => d.Order)
.WithMany(p => p.Invoices)
.OnDelete(DeleteBehavior.Cascade)
.HasForeignKey(d => d.OrderId)
.HasConstraintName("FK__Invoice__OrderId__44FF419A");
});
modelBuilder.Entity<Order>(entity =>
{
entity.ToTable("Order");
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
Database has a relation and "NOT NULL Contraints".
ALTER TABLE [dbo].[Invoice] ADD FOREIGN KEY ([OrderId]) REFERENCES [dbo].Order ON DELETE CASCADE.
ALTER TABLE [dbo].[Invoice] ADD [DueAmount] int NOT NULL
The OnModelCreating method represents well. I created very simple Rest API project and tested add/delete for Order/Invoice.
"NOT NULL Constraints" and "Cascade deleting" may be verified on database not EF model side.
(In case of creating an invoice instance with null DueAmount, I expected exceptions before submitting to SQL)
My question is very simple.
Can I delete "OnModelCreating" method if don't consider migration?
(I thought the OnModelCreating method is only for migration purpose.)
If you follow the Entity framework model naming convention and your model directly reflects your database table name, column names and so on, you don't need the OnMOdelCreating method. This is because the entity framework will generate the binding behind the scene.
But, if you want customization, for example, your model field name does not match your database table column name, you configure that on the OnModelCreating method. Another way of using this configuration is called fluent API.
This doesn't mean you have to use the OnModelCreating method. There are other options for customization. Which is DataAnotation.
For example:
If you have a model named User...
public class User
{
public int Id { get; set; }
public string FullName { get; set; }
public string Password { get; set; }
}
on your DbContext, you set the following
public AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) {}
public DbSet<User> Users { get; set; }
}
So, by convention, the Entity framework expects
A table named Users, because of the name you used on the DbSet property for the User model.
It uses Id as the primary key.. because the model property name Id
Entity framework will set this all up for you.
When we come to the custom configuration, let's say your model property name Password is not the same as the Users table column name Pwd. You have to tell the entity framework in one of the following ways.
using the OnModelCreating method (fluent API)
protected override void OnModelCreating(ModelBuilder modelBuild)
{
modelBuilder.Entity<User>(entity => {
entity.Property(p => p.Password)
.HasColumnName("Pwd");
})
}
The other way is Data annotation
public class User
{
public int Id { get; set; }
public string FullName { get; set; }
[Column("Pwd")]
public string Password { get; set; }
}

ASP.NET MVC5 - unwanted field created after update database

I'm trying to create a 'one to many' relation between the classes 'ApplicationUser' and one recently created called 'Issue'.
So, in Models / IdentityModels.cs / ApplicationUser i added this property:
public ICollection<Issue> Issues { get; set; }
And Issue.cs has this code:
namespace Test.Models
{
public class Issue
{
public int Id { get; set; }
public ApplicationUser Courier { get; set; }
public ApplicationUser Customer { get; set; }
}
}
I'm using automatic migrations. So, after building and running 'update-database', the Issues table was created with these fields:
Id
ApplicationUser_Id
CourierId
CustomerId
My question is why was the field 'ApplicationUser_Id ' created and how can i prevent it?
The problem is that EF thinks you actually want three one-to-many relationships between Issue and ApplicationUser:
one for ICollection<Issue> Issues on ApplicationUser (ApplicationUser_Id)
one for ApplicationUser Courier on Issue (CourierId)
one for ApplicationUser Customer on Issue (CustomerId)
(Note that EF allows to define relationships from either side.)
If you want that ApplicationUser.Issues contains all Issues of this User, whether he is a Courier or Customer, you will need the additional ApplicationUser_Id key. Configuring EF so that this works will be quite a pain.
Maybe a simpler solution will do: introduce two collections on ApplicationUser.
public ICollection<Issue> CourierIssues { get; set; }
public ICollection<Issue> CustomerIssues { get; set; }
And then configure the backlinks in the ModelBuilder using the fluent API to eliminate the ApplicationUser_Id key:
modelBuilder.Entity<ApplicationUser>().HasMany(au => au.CourierIssues).WithOptional(i => i.Courier);
modelBuilder.Entity<ApplicationUser>().HasMany(au => au.CustomerIssues).WithOptional(i => i.Customer);
Because you have two foreign keys to ApplicationUser, Courier and Customer, but only one collection referencing Issue on ApplicationUser. EF has no way of know which foreign key it should line up with, so it just created a new one. To handle this properly, you need to utilize fluent config:
public class ApplicationUser
{
...
public class Mapping : EntityTypeConfiguration<ApplicationUser>
{
HasMany(m => m.Issues).WithRequired(m => m.Customer);
}
}
Then, in your context:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new ApplicationUser.Mapping());
}
The problem here of course, is that you are likely wanting to track collections for both the Customer and Courier collections. For that, you need two collections:
public virtual ICollection<Issue> CustomerIssues { get; set; }
public virtual ICollection<Issue> CourierIssues { get; set; }
Then, the following fluent config:
HasMany(m => m.CustomerIssues).WithRequired(m => m.Customer);
HasMany(m => m.CourierIssues).WithRequired(m => m.Courier);

How to seed new junction table

I am using Code first in Entity framework. There are two tables in my database - Clients and Products. There is some data in it. I have added a new, junction table that has foreign keys to both of them. How should I seed that table? And will Entity framework add new rows when I add new Client or Product, because it seems that it doesn't.:
public class UserPriceList
{
public UserPriceList()
{
PriceFactor = 1M;
}
[Key]
public int UserPriceListId { get; set; }
[Index("IX_UserPrice", 1)]
public int ProductId { get; set; }
public virtual Product Product { get; set; }
[Index("IX_UserPrice", 2)]
public int ClientId { get; set; }
public virtual Client Client { get; set; }
public decimal PriceFactor { get; set; }
}
Your UserPriceList looks a lot like a junction table, but EntityFramework is not viewing it that way because you defined it as an entity with additional properties. A junction table is automatically created behind the scenes as a table with ProductId and ClientId by adding an ICollection to the Client and ICollection to the product. There is no defined model, you would interact with it by Client.Products.Add(someProduct) and it would populate the junction table behind the scenes.
To get your UserPriceList working as a junction table you could try something like this in your OnModelCreating
modelBuilder.Entity<Product>()
.HasMany(x => x.Clients)
.WithMany(x => x.Products)
.Map(x =>
{
x.ToTable("UserPriceList"); // third table is named Cookbooks
x.MapLeftKey("ProductId");
x.MapRightKey("ClientId");
});
to explicitly map UserPriceList as the junction table. You may have problems with the non nullable decimal (or maybe not since you're setting a value in the constructor) and with having the UserPriceList defined as an entity.
Your could also just interact with the UserProductList as an entity and explicitly add items to it.
Probably the safest (as in most likely to work) way to go would be to remove the ICollection<Product> from Client and the ICollection<Client> from product add an ICollection<UserPriceList> to both.

Can't map one to zero or one relation

I am using code first to generate tables.
I have User object:
public class ApplicationUser
{
public int? ImageId { get; set; }
public virtual Image Image { get; set; }
public virtual ICollection<Image> Images { get; set; }
and class Image:
public class Image
{
public int ImageId { get; set; }
public int CreatedBy { get; set; }
public virtual ApplicationUser CreatedByUser { get; set; }
I map objects via fluent api:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// User can create many images
modelBuilder.Entity<Image>()
.HasRequired(e => e.CreatedByUser)
.WithMany(e => e.Images)
.HasForeignKey(e => e.CreatedBy)
.WillCascadeOnDelete(false);
// User can and doesn't have to have image
modelBuilder.Entity<ApplicationUser>()
.HasRequired(e => e.Image)
.WithOptional()
.WillCascadeOnDelete(false);
The first part create me one relation UserId > CreatedBy fine.
But second relation is 1:1 and related fields are UserId > ImageId which is not what I am trying to make.
I have tried to use HasOptional instead HasRequired but then I get additional keys in tables.
What should I do to map this two tables?
UPDATE 1
Based on answer. I leave User and Image classes the same.
User have One image (for profile) and list of images (all other images that user created).
And I use fluent api to connect tables:
But EF generate me additional key and doesn't use User > ImageId as key I can't understand why?
modelBuilder.Entity<ApplicationUser>()
.HasOptional(e => e.Image)
.WithOptionalDependent()
.WillCascadeOnDelete(false);
Unfortunately, EF does not support 1:1 mappings in this way. If you think about it, the reason should be obvious. How would you model this in a database? You can't. The best you can do is create dual 1:many and many:1 connections.
ie, if you have two ApplicationUser rows, they could both have the same ImageId. There's no way to guarantee that there is only one row (at least not without constraints, which EF doesn't support).
EF only supports 1:1 when using a shared primary key. That means both entities have to use the same Key name, and they both have to be primary keys, and one has to also make the other a Foreign key.

Cascade delete (potentially null) collection with EF CodeFirst

I have this class:
public class Participant
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public virtual PersonName Name { get; set; }
public virtual ICollection<ResponseSet> SurveyResponses { get; set; }
}
When the participant is first created there won't be any survey responses (so I don't think the answer is to make this collection required). However, I'd like to have a cascading delete on all ResponeSets in SurveyResponses if I delete a Participant. I added this to my DbContext:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Participant>()
.HasOptional(d=>d.SurveyResponses)
.WithMany()
.WillCascadeOnDelete();
}
But this doesn't quite work. Yes, it has created cascading deletes. But it's just added a SurveyResponses_Id FK on the Participants table. Unless I'm missing something that's not what I'm trying to do because the Participant can have more than 1 ResponseSet in the collection.
Unfortunately, I've found a lot of answers to this question but none of them seem to deal with collections.
First the relationship between Person and Response Surveys is 1:n. Because that the foreign key of person must be in the Response survey table. The collection Survey responses is just a navigation property and not have really a column representation in the database.
modelBuilder.Entity<ResponseSet>()
.HasRequired(s =>s.Participant)
.WithMany(s =>s.SurveyResponses)
.Map(s =>s.MapKey("ParticipantId"))
.WillCascadeOnDelete(true);

Categories

Resources