I don't understand why EF creates a nullable TemplateTask_Id column in my TemplateTaskDependancies table. I thought using a modelbuilder configuration class would solve the problem, but I must be missing something.
My domain classes are as follows.
[Table("TemplateTaskDependancies")]
public class TemplateTaskDependancy : Dependancy<TemplateTask>,
IDependancy<TemplateTask>
{
[Column("TaskId")]
public int TaskId { get; set; }
[Column("NeededTaskId")]
public int NeededTaskId { get; set; }
[ForeignKey("TaskId")]
public override TemplateTask Task { get; set; }
[ForeignKey("NeededTaskId")]
public override TemplateTask NeededTask { get; set; }
}
public abstract class Dependancy<T> : LoggedEntity
where T : LoggedEntity
{
[Column("TaskId")]
public int TaskId { get; set; }
[Column("NeededTaskId")]
public int NeededTaskId { get; set; }
[ForeignKey("TaskId")]
public abstract T Task { get; set; }
[ForeignKey("NeededTaskId")]
public abstract T NeededTask { get; set; }
}
public interface IDependancy<T> where T : LoggedEntity
{
int Id { get; set; }
int TaskId { get; set; }
int NeededTaskId { get; set; }
T NeededTask { get; set; }
T Task { get; set; }
State { get; set; }
}
public abstract class LoggedEntity : IObjectWithState
{
public int Id { get; set; } // primary key
// todo with Julie Lerman's repository pattern
}
In my context I have
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions
.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Configurations
.Add(new TemplateTaskDependancyConfiguration());
}
public class TemplateTaskDependancyConfiguration :
EntityTypeConfiguration<TemplateTaskDependancy>
{
public TemplateTaskDependancyConfiguration()
{
HasRequired(x => x.NeededTask)
.WithMany(y=>y.NeededTasks)
.HasForeignKey(z=>z.NeededTaskId)
.WillCascadeOnDelete(false);
HasRequired(x => x.NeededTask)
.WithMany(y => y.Dependancies)
.HasForeignKey(z => z.TaskId)
.WillCascadeOnDelete(false);
HasRequired(x=>x.Task)
.WithMany(y=>y.NeededTasks)
.HasForeignKey(z=>z.NeededTaskId)
.WillCascadeOnDelete(false);
HasRequired(x => x.Task)
.WithMany(y => y.Dependancies)
.HasForeignKey(z => z.TaskId)
.WillCascadeOnDelete(false);
}
}
Because you have no primary key defined anywhere?
By the way, it's dependEncy.
It turned out that the problem was caused by an unneeded collection of
public List<TemplateTaskDependancy> Tasks
inside my TemplateTask class.
i.e the foreign key table contained an extra collection of objects.
Related
I have a project that is running with .net core 6, EF Core 6.0.9, DB -> postgresql 14.
I have these classes
public class Language
{
[Key]
public long Id { get; set; }
public string PublicId { get; set; }
public string Name { get; set; }
public virtual ICollection<Course> CoursesFrom { get; set; }
public virtual ICollection<Course> CoursesTo { get; set; }
}
public class Course
{
[Key]
public long Id { get; set; }
public string PublicId { get; set; }
public string Name { get; set; }
public virtual Language LanguageFrom { get; set; }
public virtual Language LanguageTo { get; set; }
}
The relation is defined as follows:
public class LanguageConfiguration : IEntityTypeConfiguration<Language>
{
public void Configure(EntityTypeBuilder<Language> builder)
{
...
builder.HasMany(l => l.CoursesFrom)
.WithOne(c => c.LanguageFrom);
builder.HasMany(l => l.CoursesTo)
.WithOne(c => c.LanguageTo);
}
}
public class CourseConfiguration : IEntityTypeConfiguration<Course>
{
public void Configure(EntityTypeBuilder<Course> builder)
{
...
builder.HasOne(l => l.LanguageFrom)
.WithMany(c => c.CoursesFrom)
.OnDelete(DeleteBehavior.SetNull);
builder.HasOne(l => l.LanguageTo)
.WithMany(c => c.CoursesTo)
.OnDelete(DeleteBehavior.SetNull);
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfiguration(new LanguageConfiguration());
modelBuilder.ApplyConfiguration(new CourseConfiguration());
}
And LINQ expression
var query = Context.Course
.Include(l => l.LanguageFrom)
.Include(l => l.LanguageTo)
.ToList();
Main entity is returned but fields LanguageFrom and LanguageTo are null - the Include() does nothing. What am I doing wrong?
When there are multiple navigation properties defined between two types shadow navigation properties do not work. You should define two fields to serve as foreign key in the Course entity:
public class Course
{
[Key]
public long Id { get; set; }
public string PublicId { get; set; }
public string Name { get; set; }
public long LanguageFromId { get; set; }
public long LanguageToId { get; set; }
public virtual Language LanguageFrom { get; set; }
public virtual Language LanguageTo { get; set; }
}
Then
public class CourseConfiguration : IEntityTypeConfiguration<Course>
{
public void Configure(EntityTypeBuilder<Course> builder)
{
...
builder.HasOne(l => l.LanguageFrom)
.WithMany(c => c.CoursesFrom)
.HasForeignKey("LanguageFromId")
.OnDelete(DeleteBehavior.SetNull);
builder.HasOne(l => l.LanguageTo)
.WithMany(c => c.CoursesTo)
.HasForeignKey("LanguageToId")
.OnDelete(DeleteBehavior.SetNull);
}
}
I'm trying to create a many to many relation in the Fluent API with Ef Core 6 but i am having trouble understanding how to do so.
I've looked around here in stackoverflow but couldn't understand this relation and how to reproduce it in my code.
I have a table in my SQL database called People:
People.cs:
public class People : PeopleBase
{
public People()
{
RegistrationList = new HashSet<Registration>();
}
public virtual ICollection<Registration> RegistrationList { get; set; }
public virtual User User { get; set; }
public virtual ActivityGroup ActivityGroup { get; set; }
}
PeopleBase.cs:
public abstract class PeopleBase: ModelBase
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid PeopleId { get; set; }
public Guid? UserId { get; set; }
public Guid? ActivityGroupId { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public PeopleActiveType Active { get; set; }
}
And then i have another table called ActivityGroup:
ActivityGroup.cs:
public class ActivityGroup : ActivityGroupBase
{
public ActivityGroup()
{
PeopleList = new HashSet<People>();
ActivityList = new HashSet<Activity>();
}
public ICollection<People> PeopleList { get; set; }
public ICollection<Activity> ActivityList { get; set; }
}
ActivityGroupBase.cs:
public abstract class ActivityGroupBase : ModelBase
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid ActivityGroupId { get; set; }
[Required]
[StringLength(20)]
public string Name { get; set; }
public StatusRecord Status { get; set; }
}
How would i do the mapping in the modelBuilder given that:
ActivityGroupId is the foreing key in the People database, pointing to the other table
One PeopleId can have multiple (many) ActivityGroupId
One ActivityGroupId can be assigned to multiple people.
What i've done so far:
modelBuilder.Entity<People>()
.HasOne(x => x.ActivityGroup)
.WithMany(x => x.PeopleList)
.HasForeignKey(x => x.ActivityGroupId);
Wouldn't i have to do this instead?
modelBuilder.Entity<People>()
.Hasmany(x => x.ActivityGroupList) //this is a ICollection<ActivityGroup> inside People class
.WithMany(x => x.PeopleList)
.HasForeignKey(x => x.ActivityGroupId); // this is not recognized by Ef Core
Can anyone help me please?
There are two main approaches for many-to-many relationships - with implicit junction table:
public class People : PeopleBase
{
// ...
public virtual List<Activity> Activities { get; set; }
}
public class Activity // : ...
{
// ...
public virtual List<People> PeopleList { get; set; }
}
modelBuilder.Entity<People>()
.HasMany(x => x.Activities)
.WithMany(x => x.PeopleList);
Or with explicit one:
public class People
{
// ...
public ICollection<PeopleActivity> PeopleActivities { get; set; }
}
public class Activity
{
// ...
public virtual ICollection<PeopleActivity> PeopleActivities { get; set; }
}
public class PeopleActivity
{
public Guid ActivityId { get; set; }
public Guid PeopleId { get; set; }
public Activity Activity { get; set; }
public People People { get; set; }
}
modelBuilder.Entity<PeopleActivity>()
.HasOne(pt => pt.People)
.WithMany(t => t.PeopleActivities)
.HasForeignKey(pt => pt.PeopleId);
Also maybe it worth changing entity name from People to Person (you can change table name with .ToTable("People") call)?
I hava an annotation problem:
modelBuilder.Entity<FirstClass>()
.HasOne(f => f.SecondClass)
.WithOne(s => s.FirstClass)
.HasForeignKey<FirstClass>(f => f.SecondClassId)
.OnDelete(DeleteBehavior.Cascade);
How to write this with annotations? I don't find the annotation for OnDelete.
Try this :
Your model
public class FirstClass
{
[Key]
public int Id { get; set; }
public int SecondClassId { get; set; }
[ForeignKey(nameof(SecondClassId))]
[InverseProperty("FirstClasses")]
public virtual SecondClass SecondClass { get; set; }
}
public class SecondClass
{
[Key]
public int Id { get; set; }
[InverseProperty(nameof(FirstClass.SecondClass))]
public virtual ICollection<FirstClass> FirstClasses { get; set; }
}
if you want to have only one first and one second try this code. But I don't recommend it since it will be hard to find the errors.
public class FirstClass
{
[Key]
public int Id { get; set; }
public int SecondClassId { get; set; }
[ForeignKey(nameof(SecondClassId))]
[InverseProperty("FirstClass")]
public virtual SecondClass SecondClass { get; set; }
}
public class SecondClass
{
[Key]
public int Id { get; set; }
[InverseProperty(nameof(FirstClass.SecondClass))]
public virtual FirstClass FirstClass { get; set; }
}
Your db context:
public class FirstClassDbContext : DbContext
{
public FirstClassDbContext()
{
}
public FirstClassDbContext(DbContextOptions<FirstClassDbContext> options)
: base(options)
{
}
public DbSet<FirstClass> FirstClasses { get; set; }
public DbSet<SecondClass> SecondClasses { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Server=localhost;Database=FirstClass;Trusted_Connection=True;");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<FirstClass>(entity =>
{
entity.HasOne(d => d.SecondClass)
.WithMany(p => p.FirstClasses)
.HasForeignKey(d => d.SecondClassId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_FirstClass_SecondClass");
});
// or for one to one you can use yours, but I don't recommend it since
//it will be hard to find the errors and it will not do anything for you.
//Only confusing queries.
modelBuilder.Entity<FirstClass>()
.HasOne(f => f.SecondClass)
.WithOne(s => s.FirstClass)
.HasForeignKey(d => d.SecondClassId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_FirstClass_SecondClass");
}
}
I think it is not necessary.
public class GeneralClass
{
public int Id { get; set; }
}
public class FirstClass : GeneralClass
{
public int? DerivedClassId { get; set; }
public string Name { get; set; }
[InverseProperty(nameof(EFCoreTest.SecondClass.FirstClass))]
public SecondClass SecondClass { get; set; }
public DerivedClass DerivedClass {get; set; }
}
public class SecondClass : GeneralClass
{
public int FirstClassId { get; set; }
public string Url { get; set; }
[ForeignKey(nameof(FirstClassId))]
public FirstClass FirstClass { get; set; }
}
public class ApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Server=(localdb)\MSSQLLocalDB;Database=sample;Trusted_Connection=True");
}
public DbSet<FirstClass> FirstClasses { get; set; }
public DbSet<SecondClass> SecondClasses {get; set; }
}
This generates the table definition
CREATE TABLE [dbo].[SecondClasses] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[FirstClassId] INT NOT NULL,
[Url] NVARCHAR (MAX) NULL,
CONSTRAINT [PK_SecondClasses] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_SecondClasses_FirstClasses_FirstClassId] FOREIGN KEY ([FirstClassId]) REFERENCES [dbo].[FirstClasses] ([Id]) ON DELETE CASCADE
);
GO
CREATE UNIQUE NONCLUSTERED INDEX [IX_SecondClasses_FirstClassId]
ON [dbo].[SecondClasses]([FirstClassId] ASC)
;
I'm using EF Code First to query and create a database. One of my entities (relationship) has two navigation properties to the same entity (activity). My problem is that if I use EF to create the database schema it will create four foreign key columns and constraints instead of two.
Here are the relevant code parts:
activity class:
public class Activity {
public virtual ICollection<Relationship> Successors { get; set; }
public virtual ICollection<Relationship> Predecessors { get; set; }
}
relationship class:
public class Relationship {
public virtual Activity Activity1 { get; set; }
public int Activity1_ID { get; set; }
public virtual Activity Activity2 { get; set; }
public int Activity2_ID { get; set; }
}
Relationship mapping class:
this.HasRequired(t => t.Activity1)
.WithMany(t => t.Predecessors)
.HasForeignKey(m => m.Activity1_ID)
.WillCascadeOnDelete(false);
this.HasRequired(t => t.Activity2)
.WithMany(t => t.Successors)
.HasForeignKey(m => m.Activity2_ID)
.WillCascadeOnDelete(false);
Database structure:
Is there a way to prevent the creation of the last two columns?
This should create you only 2 foreign key columns.
public class Activity
{
public int Id { set; get; }
public virtual ICollection<Relationship> Successors { get; set; }
public virtual ICollection<Relationship> Predecessors { get; set; }
}
public class Relationship
{
public int Id { set; get; }
public virtual Activity Activity1 { get; set; }
public int Activity1_ID { get; set; }
public virtual Activity Activity2 { get; set; }
public int Activity2_ID { get; set; }
}
And the DbContext class where i am specifying the relationship/FK nature on my OnModelCreating.
public class MyDb: DbContext
{
public MyDb():base("EfDbContext")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Relationship>()
.HasRequired(f => f.Activity1)
.WithMany(f => f.Predecessors)
.HasForeignKey(g => g.Activity1_ID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Relationship>().
HasRequired(f => f.Activity2)
.WithMany(f => f.Successors)
.HasForeignKey(g => g.Activity2_ID)
.WillCascadeOnDelete(false);
}
}
Here is the case, I have 2 entities, such as Contract、Media。
public class Media : Entity
{
public string Name {get; set;}
public bool Enabled
*//other properties can be ignored..*
}
public class Contract : Entity
{
public string Code {get; set;}
*//other properties can be ignored..*
}
Contract has many Medias, it seems that they are many to many.
But!! at ef code first, i need 3 more fields in the ContractMedia table(ef auto generated).
such as StartDate,EndDate and Price. these could not be added in Media entity.
How to map at this case??
If you want to create many to many relationship with additional data in association table, you have to make the association table as entity. The pure many to many relationship is only in pure table with entity id's.
In you case it will be:
public class Media // One entity table
{
public int Id { get; set; }
public string Name { get; set; }
public bool Enabled { get; set; }
public virtual ICollection<ContractMedia> ContractMedias { get; set; }
}
public class Contract // Second entity table
{
public int Id { get; set; }
public string Code { get; set }
public virtual ICollection<ContractMedia> ContractMedias { get; set; }
}
public class ContractMedia // Association table implemented as entity
{
public int MediaId { get; set; }
public int ContractId { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public double Price { get; set; }
public virtual Media Media { get; set; }
public virtual Contract Contract { get; set; }
}
And after you created models/entities, you need to define relationships in context:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ContractMedia>()
.HasKey(c => new { c.MediaId, c.ContractId });
modelBuilder.Entity<Contract>()
.HasMany(c => c.ContractMedias)
.WithRequired()
.HasForeignKey(c => c.ContractId);
modelBuilder.Entity<Media>()
.HasMany(c => c.ContractMedias)
.WithRequired()
.HasForeignKey(c => c.MediaId);
}
Also you can refer to these links:
Many to many mapping with extra fields in Fluent API
Entity Framework CodeFirst many to many relationship with additional information
Create code first, many to many, with additional fields in association table
Adding to #Tomas answer without having to use Fluent API.
public class Media // One entity table
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ContractMedia> ContractMedias { get; set; }
}
public class Contract // Second entity table
{
public int Id { get; set; }
public string Code { get; set }
public virtual ICollection<ContractMedia> ContractMedias { get; set; }
}
public class ContractMedia // Association table implemented as entity
{
[Key]
[Column(Order = 0)]
[ForeignKey("Media")]
public int MediaId { get; set; }
[Key]
[Column(Order = 1)]
[ForeignKey("Contract")]
public int ContractId { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public double Price { get; set; }
public virtual Media Media { get; set; }
public virtual Contract Contract { get; set; }
}
EF Core needs to use Fluent API but it would look like this:
internal class MyContext : DbContext
{
public MyContext(DbContextOptions<MyContext> options)
: base(options)
{
}
public DbSet<Post> Posts { get; set; }
public DbSet<Tag> Tags { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.HasMany(p => p.Tags)
.WithMany(p => p.Posts)
.UsingEntity<PostTag>(
j => j
.HasOne(pt => pt.Tag)
.WithMany(t => t.PostTags)
.HasForeignKey(pt => pt.TagId),
j => j
.HasOne(pt => pt.Post)
.WithMany(p => p.PostTags)
.HasForeignKey(pt => pt.PostId),
j =>
{
j.Property(pt => pt.PublicationDate).HasDefaultValueSql("CURRENT_TIMESTAMP");
j.HasKey(t => new { t.PostId, t.TagId });
});
}
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public ICollection<Tag> Tags { get; set; }
public List<PostTag> PostTags { get; set; }
}
public class Tag
{
public string TagId { get; set; }
public ICollection<Post> Posts { get; set; }
public List<PostTag> PostTags { get; set; }
}
public class PostTag
{
public DateTime PublicationDate { get; set; }
public int PostId { get; set; }
public Post Post { get; set; }
public string TagId { get; set; }
public Tag Tag { get; set; }
}
Source:
https://learn.microsoft.com/en-us/ef/core/modeling/relationships?tabs=fluent-api%2Cfluent-api-simple-key%2Csimple-key#join-entity-type-configuration