I got a few entities and relationships in my application, which work perfectly fine. But there is a relationship, which I can't get right.
Scenario: I have Registrations and Professors. Each Registration has exactly two Professors assigned. But Professors can be assigned to multiple Registrations.
My Models:
public class ThesisRegistration : Entity
{
...
[Required] public long FirstExaminerId { get; set; }
public Professor FirstExaminer { get; set; }
[Required] public long SecondExaminerId { get; set; }
public Professor SecondExaminer { get; set; }
public List<ProfessorThesisRegistration> ProfessorThesisRegistrations { get; set; }
}
public class Professor : Entity
{
...
public List<ProfessorThesisRegistration> ProfessorThesisRegistrations { get; set; }
}
public class ProfessorThesisRegistration
{
public int ProfessorId { get; set; }
public Professor Professor { get; set; }
public int ThesisRegistrationId { get; set; }
public ThesisRegistration ThesisRegistration { get; set; }
}
Then the configuration:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
...
modelBuilder.Entity<ProfessorThesisRegistration>().HasKey(i => new {i.ProfessorId, i.ThesisRegistrationId});
modelBuilder.Entity<ProfessorThesisRegistration>()
.HasOne(pt => pt.Professor)
.WithMany(f => f.ProfessorThesisRegistrations)
.HasForeignKey(pt => pt.ProfessorId);
modelBuilder.Entity<ProfessorThesisRegistration>()
.HasOne(pt => pt.ThesisRegistration)
.WithMany(t => t.ProfessorThesisRegistrations)
.HasForeignKey(pt => pt.ThesisRegistrationId);
}
What do I need to change, so this relationship works? Thank you!
If I wanna go straight to the point I should say that You are completely wrong in designing your database because, with that scenario that you define, the relation between Registrations and Professors is not Many-To-Many, rather it is a One-To-Many relation.
P.S: For Being tangible I add some property to your model.
so for resolving your problem, act like below:
public class ThesisRegistration
{
public int Id { get; set; }
public string Title { get; set; }
public int FirstExaminerId { get; set; }
public virtual Professor FirstExaminer { get; set; }
public int SecondExaminerId { get; set; }
public virtual Professor SecondExaminer { get; set; }
}
and change Professor Model like below:
public class Professor
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ThesisRegistration> ThesisRegistrationsOfFirstExaminer { get; set; }
public virtual ICollection<ThesisRegistration> ThesisRegistrationsOfSecondExaminer { get; set; }
}
and in the OnModelCreating Method:
modelBuilder.Entity<ThesisRegistration>()
.HasOne(p => p.FirstExaminer)
.WithMany(p => p.ThesisRegistrationsOfFirstExaminer)
.HasForeignKey(p => p.FirstExaminerId)
.OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<ThesisRegistration>()
.HasOne(p => p.SecondExaminer)
.WithMany(p => p.ThesisRegistrationsOfSecondExaminer)
.HasForeignKey(p => p.SecondExaminerId)
.OnDelete(DeleteBehavior.NoAction);
now with this design, every Thesis can have exactly two examiners, and each professor can have multiple theses.
feel free to ask questions. good luck.
Related
I am building a platform using .NET CORE 2.1, and it is about questions and answers. Before I started I scaffolded my existing DB into the .net project so I got the model up and running.
Scaffold-DbContext "Server=(localdb)\mssqllocaldb;Database=MYDATABASE;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models
But it generated QUESTION and QUESTION_DETAIL with a many-to-one relation ship whereas QUESTION should only have 1 QUESTION DETAIl. But when I am trying to change it with the fluent API in my context it gives an error:
Cannot Convert lambda expression to type 'Type' because it is not a delegated type
Here are my 2 model classes
public partial class Question
{
public Question()
{
AnswerClientBot = new HashSet<AnswerClientBot>();
InverseOriginalQuestion = new HashSet<Question>();
QuestionFollowUpFollowUpQuestion = new HashSet<QuestionFollowUp>();
QuestionFollowUpOriginalQuestion = new HashSet<QuestionFollowUp>();
}
public int Id { get; set; }
public int BotInstanceId { get; set; }
public string LanguageCode { get; set; }
public string Question1 { get; set; }
public int? OriginalQuestionId { get; set; }
[ForeignKey("QuestionDetail")]
public int QuestionDetailId { get; set; }
public Instance BotInstance { get; set; }
public Question OriginalQuestion { get; set; }
public QuestionDetail QuestionDetail { get; set; }
public ICollection<AnswerClientBot> AnswerClientBot { get; set; }
public ICollection<Question> InverseOriginalQuestion { get; set; }
public ICollection<QuestionFollowUp> QuestionFollowUpFollowUpQuestion { get; set; }
public ICollection<QuestionFollowUp> QuestionFollowUpOriginalQuestion { get; set; }
}
public partial class QuestionDetail
{
public int Id { get; set; }
public int OriginalQuestionId { get; set; }
public string Topic { get; set; }
public string Intent { get; set; }
public string CustDetail01 { get; set; }
public string CustDetail02 { get; set; }
public string CustDetail03 { get; set; }
public string CustDetail04 { get; set; }
public string CustDetail05 { get; set; }
public string Keywords { get; set; }
public Question OriginalQuestion { get; set; }
}
And this is the context I am trying to change, the error occurs on
HasForeignKey(d => d.OriginalQuestionId)
modelBuilder.Entity<QuestionDetail>(entity =>
{
entity.ToTable("QuestionDetail", "Bot");
entity.Property(e => e.CustDetail01)
.HasColumnName("CUST_Detail01")
.HasMaxLength(250)
.IsUnicode(false);
entity.Property(e => e.CustDetail02)
.HasColumnName("CUST_Detail02")
.HasMaxLength(250)
.IsUnicode(false);
entity.Property(e => e.CustDetail03)
.HasColumnName("CUST_Detail03")
.HasMaxLength(250)
.IsUnicode(false);
entity.Property(e => e.CustDetail04)
.HasColumnName("CUST_Detail04")
.HasMaxLength(250)
.IsUnicode(false);
entity.Property(e => e.CustDetail05)
.HasColumnName("CUST_Detail05")
.HasMaxLength(250)
.IsUnicode(false);
entity.Property(e => e.Intent)
.HasMaxLength(250)
.IsUnicode(false);
entity.Property(e => e.Keywords)
.HasMaxLength(250)
.IsUnicode(false);
entity.Property(e => e.Topic)
.HasMaxLength(250)
.IsUnicode(false);
entity.HasOne(d => d.OriginalQuestion)
.WithOne(p => p.QuestionDetail)
.HasForeignKey(d => d.OriginalQuestionId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_Bot_QuestionDetail_OriginalQuestionId");
});
Does anyone have any clue how I can fix this? Thank you!
Declare both classes with navigation properties to each other. Mark one of the tables (the dependent table) with the ForeignKey attribute on its Primary Key. EF infers 1-to-1 from this:
public class Question
{
...
// [ForeignKey("QuestionDetail")]
// public int QuestionDetailId { get; set; }
public QuestionDetail QuestionDetail { get; set; }
...
}
public partial class QuestionDetail
{
//public int Id { get; set; }
[ForeignKey("Question")]
public int QuestionId { get; set; }
...
public Question Question { get; set; }
}
So I found this Cannot convert lambda expression to type 'object' because it is not a delegate type stackoverflow post, and at the start I tried to strong type it but didn't know really how to but eventually I figured it out. So I changed it to HasForeignKey<Question>(d => d.OriginalQuestionId) which worked for me.
I'm using Entity Framework Core with Code First approach but recieve following error when updating the database:
Introducing FOREIGN KEY constraint 'FK_AnEventUsers_Users_UserId' on table 'AnEventUsers' 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 or index. See previous errors.
My entities are these:
public class AnEvent
{
public int AnEventId { get; set; }
public DateTime Time { get; set; }
public Gender Gender { get; set; }
public int Duration { get; set; }
public Category Category { get; set; }
public int MinParticipants { get; set; }
public int MaxParticipants { get; set; }
public string Description { get; set; }
public Status EventStatus { get; set; }
public int MinAge { get; set; }
public int MaxAge { get; set; }
public double Longitude { get; set; }
public double Latitude { get; set; }
public ICollection<AnEventUser> AnEventUsers { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
public class User
{
public int UserId { get; set; }
public int Age { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Gender Gender { get; set; }
public double Rating { get; set; }
public ICollection<AnEventUser> AnEventUsers { get; set; }
}
public class AnEventUser
{
public int AnEventId { get; set; }
public AnEvent AnEvent { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
public class ApplicationDbContext:DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options):base(options)
{ }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AnEventUser>()
.HasOne(u => u.User).WithMany(u => u.AnEventUsers).IsRequired().OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<AnEventUser>()
.HasKey(t => new { t.AnEventId, t.UserId });
modelBuilder.Entity<AnEventUser>()
.HasOne(pt => pt.AnEvent)
.WithMany(p => p.AnEventUsers)
.HasForeignKey(pt => pt.AnEventId);
modelBuilder.Entity<AnEventUser>()
.HasOne(eu => eu.User)
.WithMany(e => e.AnEventUsers)
.HasForeignKey(eu => eu.UserId);
}
public DbSet<AnEvent> Events { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<AnEventUser> AnEventUsers { get; set; }
}
The issue I thought was that if we delete a User the reference to the AnEvent will be deleted and also the reference to AnEventUser will also be deleted, since there is a reference to AnEventUser from AnEvent as well we get cascading paths. But I remove the delete cascade from User to AnEventUser with:
modelBuilder.Entity<AnEventUser>()
.HasOne(u => u.User).WithMany(u => u.AnEventUsers).IsRequired().OnDelete(DeleteBehavior.Restrict);
But the error doesn't get resolved, does anyone see what is wrong? Thanks!
In your sample code in OnModelCreating you have declared modelBuilder.Entity<AnEventUser>().HasOne(e => e.User)... twice: at start of method and at end.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AnEventUser>() // THIS IS FIRST
.HasOne(u => u.User).WithMany(u => u.AnEventUsers).IsRequired().OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<AnEventUser>()
.HasKey(t => new { t.AnEventId, t.UserId });
modelBuilder.Entity<AnEventUser>()
.HasOne(pt => pt.AnEvent)
.WithMany(p => p.AnEventUsers)
.HasForeignKey(pt => pt.AnEventId);
modelBuilder.Entity<AnEventUser>() // THIS IS SECOND.
.HasOne(eu => eu.User) // THIS LINES
.WithMany(e => e.AnEventUsers) // SHOULD BE
.HasForeignKey(eu => eu.UserId); // REMOVED
}
Second call overrides first. Remove it.
This is what I did from the answer of Dmitry,
and It worked for me.
Class:
public class EnviornmentControls
{
public int Id { get; set; }
...
public virtual Environment Environment { get; set; }
}
And it's Mapping
public EnviornmentControlsMap(EntityTypeBuilder<EnviornmentControls> entity)
{
entity.HasKey(m => m.Id);
entity.HasOne(m => m.Environment)
.WithMany(m => m.EnviornmentControls)
.HasForeignKey(m => m.EnvironmentID)
.OnDelete(DeleteBehavior.Restrict); // added OnDelete to avoid sercular reference
}
These solutions didn't work for my case, but I found a way. I am not quite sure yet if it is safe but there's just something that's happening with deleting. So I modified the generated Migration File instead of putting an override.
onDelete: ReferentialAction.Cascade
The reason I did this because all the overriding mentioned above is not working for me so I manually removed the code which relates to Cascading of Delete.
Just check which specific relation being mentioned at the error so you can go straightly.
Hope this will be able to help for some people who's having the same issue as mine.
public Guid? UsuarioId { get; set; }
builder.Entity<Orcamentacao>()
.HasOne(x => x.Usuario)
.WithMany(x => x.Orcamentacaos)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired(false)
.HasForeignKey(x => x.UsuarioId);
I have a set of 3 models, which is an odd many-to-many-ish relationship.
public class Metric {
public int Id { get; set; }
public string Name { get; set; }
// ...
}
public class ActionPlan {
public int Id { get; set; }
public string Name { get; set; }
public DateTime StartDate { get; set; }
//...
public virtual ICollection<Metric> Metrics { get; set; }
}
public class PlanMetric {
public int PlanId { get; set; }
public int MetricId { get; set; }
public decimal GoalValue { get; set; }
public virtual ActionPlan Plan { get; set; }
public virtual Metric Metric { get; set; }
}
I have the relationships mapped as follows:
public class PlanMetricMapping : EntityTypeConfiguration<PlanMetric> {
public PlanMetricMapping() {
ToTable("PlanMetric");
HasKey(m => new {
m.MetricId,
m.PlanId
});
Property(m => m.GoalValue)
.IsRequired()
.HasPrecision(10, 2);
HasRequired(m => m.Metric)
.WithMany()
.HasForeignKey(m => m.MetricId);
HasRequired(m => m.Plan)
.WithMany()
.HasForeignKey(m => m.PlanId);
}
}
public class ActionPlanMapping : EntityTypeConfiguration<ActionPlan> {
public ActionPlanMapping() {
ToTable("ActionPlan");
HasKey(m => m.Id);
// ...
//HasMany(m=>m.Metrics) // how do I get to this data?
}
}
The problem is
1) EF is creating an ActionPlan_Id field in my Metric table, and I'm not sure why.
2) I don't know how to set up my mapping to be able to navigation from a Plan to it's Metrics.
EF is creating an ActionPlan_Id field because you have
public virtual ICollection<Metric> Metrics { get; set; }
in your ActionPlan definition, which EF interprets as a one-to-many relationship between ActionPlan and Metric. It seems like you want
public virtual ICollection<PlanMetric> PlanMetrics { get; set; }
instead.
Then, in order to get to an ActionPlan's metrics, you could go through that collection, perhaps through a Select().
I have the following classes:
public class Bicycle
{
public int BicycleId { get; set; }
public DateTime YearOfManufacture { get; set; }
public int BicycleManufactuerId { get; set; }
public BicycleManufacturer BicycleManufacturer { get; set; }
}
public class BicycleManufacturer
{
public int BicycleManufacturerId { get; set; }
public string Description { get; set; }
}
Each Bicycle must have a BicycleManufacturer (1:1). There could be some BicycleManufacturer that isn't associate with any Bicycle. Most will be associated with multiple Bicycle entities.
I have the following fluent API code to set up the FK relationship:
modelBuilder.Entity<Bicycle>()
.HasRequired(a => a.BicycleManufacturer)
.WithMany()
.HasForeignKey(u => u.BicycleManufactuerId);
This all seems to work fine. However, I would really like to remove the BicycleManufacturerId property from the Bicycle entity. It's only there to establish the FK relationship. Is there a way I can create the proper FK relationship if I remove this property?
You can remove the property and use the mapping:
modelBuilder.Entity<Bicycle>()
.HasRequired(a => a.BicycleManufacturer)
.WithMany()
.Map(m => m.MapKey("BicycleManufactuerId"));
You can also do it by convention by adding the relationship on the other side as a collection.
public class Bicycle
{
public int BicycleId { get; set; }
public DateTime YearOfManufacture { get; set; }
public int BicycleManufactuerId { get; set; }
public BicycleManufacturer BicycleManufacturer { get; set; }
}
public class BicycleManufacturer
{
public int BicycleManufacturerId { get; set; }
public ICollection<Bicycle> Bicycles { get; set; }
public string Description { get; set; }
}
I'm having issues applying multiple relationships (or possibly foreignkey) on two POCO objects. I've got the first relationship many-to-many working and when the database is created it creates the three tables (Projects, Users and ProjectsUsers) needed for the relationship.
Code so far:
public class Project
{
public int ProjectId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? CompletionDate { get; set; }
public bool Deleted { get; set; }
public ICollection<User> Users { get; set; }
}
public class User
{
public User()
{
Name = new Name();
}
public int UserId { get; set; }
public string LoginId { get; set; }
public string Password { get; set; }
public Name Name { get; set; }
public ICollection<Project> ManagedProjects { get; set; }
}
public class ProjectConfiguration : EntityTypeConfiguration<Project>
{
public ProjectConfiguration()
{
HasMany(x => x.Users)
.WithMany(x => x.ManagedProjects);
}
}
public UserConfiguration()
{
HasMany(x => x.ManagedProjects)
.WithMany(x => x.Users);
}
Now I want to add an optional one-to-one relationship of Project.ManagingUser -> User. However, I can't seem to figure out how to indicate this in the configuration.
Code for what I think is needed:
public class Project
{
public int ProjectId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? CompletionDate { get; set; }
public bool Deleted { get; set; }
public int? ManagingUserId { get; set; }
public User ManagingUser { get; set; }
public ICollection<User> Users { get; set; }
}
I don't think the User object needs to change.
This shows my last attempt on mapping the new relationship:
public ProjectConfiguration()
{
HasMany(p => p.Users)
.WithMany(u => u.Projects);
this.HasOptional(p => p.ManagingUser)
.WithOptionalDependent()
.Map(m=>m.MapKey("ManagingUserId"))
.WillCascadeOnDelete(false);
}
What is happening when the database is created, I now end up with only two tables (Projects and Users). And it looks like it is only trying to setup the one-to-one relationship.
Can someone tell me what I'm missing?
Richard I've not changed the UserConfiguration and below is the DbContext:
public class MyDbContext : DbContext
{
public MyDbContext() : base(Properties.Settings.Default.ConnectionString)
{
}
public DbSet<User> Users { get; set; }
public DbSet<Project> Projects { get; set; }
}
You probably want WithMany instead of WithOptionalDependent - it's a one:many relationship, not a one:one.
HasOptional(p => p.ManagingUser)
.WithMany()
.HasForeignKey(m => m.ManagingUserId)
.WillCascadeOnDelete(false);
EDIT
I think you're missing the OnModelCreating override from the DbContext class:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new ProjectConfiguration());
modelBuilder.Configurations.Add(new UserConfiguration());
}