EF Core Include() gives null after query - c#

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);
}
}

Related

Entity Framework Core Navigation Properties Error

I'm trying to make a simple app to try Entity Framework Core, but i a have problem with setting up relations between entities. My entities:
public class Card
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Adress { get; set; }
public DateTime DoB { get; set; }
public DateTime DoS { get; set; }
public User Portal { get; set; }
public List<Reservation> Res { get; set; }
}
public class Doctor
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Email { get; set; }
public TimeSpan Start_Working { get; set; }
public TimeSpan End_Working { get; set; }
public List<Reservation> Reservations { get; set; }
public int SpecID { get; set; }
public Spec Spec { get; set; }
}
public class Reservation
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public DateTime DoR { get; set; }
public string Info { get; set; }
public int CardID { get; set; }
public Card Card_Nav_R { get; set; }
public int DoctorID { get; set; }
public Doctor Doctor { get; set; }
}
public class Spec
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public List<Doctor> Doctors { get; set; }
}
public class User
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public int CardID { get; set; }
public Card Card { get; set; }
}
And a configuration class where i tried to set up relations:
class ApplicationContext:DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Card> Cards { get; set; }
public DbSet<Reservation> Reservations { get; set; }
public DbSet<Doctor> Doctors { get; set; }
public DbSet<Spec> Specs { get; set; }
public ApplicationContext()
{
Database.EnsureCreated();
}
protected override void OnModelCreating(ModelBuilder ModelBuilder)
{
ModelBuilder.Entity<User>().HasKey(u => u.Id);
ModelBuilder.Entity<Card>().HasKey(c => c.Id);
ModelBuilder.Entity<Doctor>().HasKey(d => d.Id);
ModelBuilder.Entity<Spec>().HasKey(s => s.Id);
ModelBuilder.Entity<Reservation>().HasKey(r => r.Id);
ModelBuilder.Entity<User>().Property(u => u.Email).IsRequired();
ModelBuilder.Entity<User>().Property(u => u.Password).IsRequired();
ModelBuilder.Entity<Card>().Property(c => c.Name).IsRequired();
ModelBuilder.Entity<Card>().Property(c => c.Surname).IsRequired();
ModelBuilder.Entity<Card>().Property(c => c.DoB).IsRequired();
ModelBuilder.Entity<Card>().Property(c => c.Adress).IsRequired();
ModelBuilder.Entity<Doctor>().Property(d => d.Name).IsRequired();
ModelBuilder.Entity<Doctor>().Property(d => d.Surname).IsRequired();
ModelBuilder.Entity<Doctor>().Property(d => d.Spec).IsRequired();
ModelBuilder.Entity<Doctor>().Property(d => d.Email).IsRequired();
ModelBuilder.Entity<Doctor>().Property(d => d.Start_Working).IsRequired();
ModelBuilder.Entity<Doctor>().Property(d => d.End_Working).IsRequired();
ModelBuilder.Entity<Reservation>().Property(r => r.Info).IsRequired();
ModelBuilder.Entity<Reservation>().Property(r => r.Card_Nav_R).IsRequired();
ModelBuilder.Entity<Reservation>().Property(r => r.Doctor).IsRequired();
ModelBuilder.Entity<Reservation>().Property(r => r.DoR).IsRequired();
ModelBuilder.Entity<Spec>().Property(s => s.Name).IsRequired();
ModelBuilder.Entity<Doctor>().HasOne<Spec>(d=>d.Spec).WithMany(s => s.Doctors).HasForeignKey(d => d.SpecID);
ModelBuilder.Entity<User>().HasOne<Card>(u => u.Card).WithOne(c => c.Portal).HasForeignKey<User>(u => u.CardID);
ModelBuilder.Entity<Reservation>().HasOne<Card>(r => r.Card_Nav_R).WithMany(c => c.Res).HasForeignKey(r => r.CardID);
ModelBuilder.Entity<Reservation>().HasOne<Doctor>(r => r.Doctor).WithMany(d => d.Reservations).HasForeignKey(r => r.DoctorID);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=Simple_Try;Trusted_Connection=True;");
}
}
So, when i tried to add migration or add something to database i saw this error:
System.InvalidOperationException: 'The property or navigation 'Spec' cannot be added to the entity type 'Doctor' because a property or navigation with the same name already exists on entity type 'Doctor'.'
I really don't know how to fix this, i tried to use annotations instead of Fluent API, but had the same result.
The cause of the exception is the following line:
ModelBuilder.Entity<Doctor>().Property(d => d.Spec).IsRequired();
because Doctor.Spec is a navigation property
public class Doctor
{
// ...
public Spec Spec { get; set; }
}
and navigation properties cannot be configured via Property fluent API.
So simply remove that line. Whether reference navigation property is required or optional is controlled via relationship configuration. In this case
ModelBuilder.Entity<Doctor>()
.HasOne(d => d.Spec)
.WithMany(s => s.Doctors)
.HasForeignKey(d => d.SpecID)
.IsRequired(); // <--
although the IsRequired is automatically derived from the FK property type - since SpecID is non nullable, then the relationship is required.
For more info, see Required and Optional Properties and Required and Optional Relationships documentation topics.

How can I avoid to make waste column in Entity Framework code-first?

I have two tables User and Message like below:
public partial class User
{
public int UserID { get; set; }
public string Name { get; set; }
public bool Status { get; set; }
public DateTime SubmitDate { get; set; }
public virtual ICollection<Message> Messages { get; set; }
}
public partial class Message
{
public int MessageID { get; set; }
public int SenderID { get; set; }
public int ReceiverID { get; set; }
public DateTime SubmitDate { get; set; }
public string Text { get; set; }
public bool Status { get; set; }
public int? ReplyToMessaageID { get; set; }
public virtual ICollection<Message> Messages { get; set; }
public virtual User SenderUser { get; set; }
public virtual User ReceiverUser { get; set; }
}
And in domain layer class:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//Configure domain classes using modelBuilder here
modelBuilder.Entity<Message>()
.HasOptional(c => c.Messages)
.WithMany()
.HasForeignKey(c => c.ReplyToMessaageID);
modelBuilder.Entity<Message>()
.HasRequired(c => c.SenderUser)
.WithMany(c => c.Messages)
.HasForeignKey(c => c.SenderID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Message>()
.HasRequired(c => c.ReceiverUser)
.WithMany(c => c.Messages)
.HasForeignKey(c => c.ReceiverID)
.WillCascadeOnDelete(false);
base.OnModelCreating(modelBuilder);
}
Everything is fine but as can be seen in the final result, Message table has a waste column with name User_UserID.
How can I avoid creating it?

MVC5 Code First One-To-One Relationship Error

I'm trying to do one-one relationship for MVC5 codefirst. I've looked this page and did exactly same things but I've got an error.
Here is my classes and context:
Product:
public class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public string ProductDescription { get; set; }
/// <summary>
/// Display order
/// </summary>
public int Order { get; set; }
public string TitleBackgroundColor { get; set; }
public virtual TblClass TblClass { get; set; }
public virtual ICollection<Order> Orders { get; set; }
public virtual ICollection<Price> Prices { get; set; }
public virtual ICollection<ProductFeature> ProductFeatures { get; set; }
}
TblClass:
public class TblClass
{
[Key, ForeignKey("Product")]
public int ProductId { get; set; }
public string ClassName { get; set; }
public virtual ICollection<Permission> Permissions { get; set; }
public virtual Product Product { get; set; }
public int ClassOrder { get; set; }
}
DBContext:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Role>()
.HasMany<UserProfile>(r => r.UserProfiles)
.WithMany(u => u.Roles)
.Map(m =>
{
m.ToTable("webpages_UsersInRoles");
m.MapLeftKey("RoleId");
m.MapRightKey("UserId");
});
modelBuilder.Entity<TblClass>()
.HasKey(c => c.ProductId);
modelBuilder.Entity<Product>()
.HasOptional(f => f.TblClass)
.WithRequired(s => s.Product)
.Map(t => t.MapKey("ProductId"));
}
And when I try to run 'update-database -verbose' I've got this error:
The navigation property 'Product' declared on type 'YazililarGaranti.Domain.Entities.TblClass' has been configured with conflicting foreign keys.
You do not have to use .Map(t => t.MapKey("ProductId") when making an 1:1 relationship. This should work:
public class Product
{
public int ProductId { get; set; }
//... other properties
public virtual TblClass TblClass { get; set; }
//... other properties
}
public class TblClass
{
//[Key, ForeignKey("Product")] <-- remove these attributes
public int ProductId { get; set; }
public string ClassName { get; set; }
//.. other properties
public virtual Product Product { get; set; }
public int ClassOrder { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//other mappings
modelBuilder.Entity<TblClass>()
.HasKey(c => c.ProductId);
modelBuilder.Entity<Product>()
.HasKey(c => c.ProductId); //consider to use database generated option
//modelBuilder.Entity<Product>().Property(t => t.ProductId)
//.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<Product>()
.HasOptional(f => f.TblClass)
.WithRequired(s => s.Product);
}
Hope this helps!

Why do I get an unneeded column in code first created table?

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.

code first many to many set name of table

I'm new to code first and derived from DB Context. Here is a excerpt of my Model.
[Table("pm_Material")]
public class Material
{
public Material()
{
this.ProductionStepLogs = new HashSet<ProductionStepLog>();
}
[Key]
public int MaterialId { get; set; }
public int MaterialTypeId { get; set; }
public string Description { get; set; }
public decimal CostRate { get; set; }
public virtual MaterialType MaterialType { get; set; }
public virtual ICollection<ProductionStepLog> ProductionStepLogs { get; set; }
}
[Table("pm_ProductionStepLog")]
public class ProductionStepLog
{
public ProductionStepLog()
{
this.Materials = new HashSet<Material>();
}
[Key]
public System.Guid ProductionStepLogId { get; set; }
public int ProductionStepId { get; set; }
public System.Guid ProductId { get; set; }
public Nullable<System.DateTime> BeginStep { get; set; }
public Nullable<System.DateTime> EndStep { get; set; }
public int UserId { get; set; }
public virtual Product Product { get; set; }
public virtual ProductionStep ProductionStep { get; set; }
public virtual ICollection<Material> Materials { get; set; }
}
The DB creation works fine, but I want to specify the name of the auto-generated many-to-many table "ProductionStepLogMaterials" using [Table("pm_ProductionStepLogMaterials")].
Is this possible?
You should override your protected override void OnModelCreating(DbModelBuilder modelBuilder) of your own DBContext class like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Material>()
.HasMany(a => a.ProductionStepLog)
.WithMany(a => a.Material)
.Map(x =>
{
x.ToTable("NameOfYourTable");
x.MapLeftKey("MaterialId");
x.MapRightKey("ProductionStepLogId");
});
}
AFAIK, this is impossible with Data Annotations, but it is possible with configuration fluent API:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<E1Type>()
.HasMany(e1 => e1.Collection)
.WithMany(e2 => e2.Collection)
.Map(config => config.ToTable("MyTable"));
}

Categories