many to many entity framework + Compose Primary Key - entity-framework

Hi friends I am having problems with a relationship Much to Much with Compose Primary Key.
I have the following:
public class Empleado
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key, Column(Order = 0)]
public int Id { get; set; }
[Required]
[StringLength(100)]
public string Nombre { get; set; }
[Key, Column(Order = 1)]
public int? IdentificacionId { get; set; }
public Identificacion Identificacion { get; set; }
[Required]
[StringLength(11)]
[Key, Column(Order = 2)]
public string NoIdentificacion { get; set; }
}
// Entidad relación
public class EmpleadoNomina
{
public int EmpleadoId { get; set; }
public int NominaId { get; set; }
public decimal Salario { get; set; }
public int DescuentoLey { get; set; }
public decimal? SalarioIngresoEgreso { get; set; }
public Nomina Nomina { get; set; }
public Empleado Empleado { get; set; }
}
// FluentApi
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Constraint combinado TipoId + NoID
modelBuilder.Entity<Empleado>().HasKey(x => new { x.IdentificacionId, x.NoIdentificacion });
// Relación
modelBuilder.Entity<EmpleadoNomina>().HasKey(k => new { k.NominaId, k.EmpleadoId });
}
The problem arises when the relationship table is created. To this is added the columns Employee_IdentificationId, Employee_NoIdentification. And the EmployeeId column without foreignkey.
The other problem is: I can't use .Find(id); example: db.Empleados.Find(15); This gives an error because it requires me to pass the three keys.
I just want to remove the extra columns Employee_IdentificationId, Employee_NoIdentification and only use EmpleadoId.

Don't use a composite key on Empleado - just use ID as its key. Same for Nomina. The composite key is used on the bridge table. Also, since you are already using fluent code you don't need the annotations. Behavior can be odd when you mix.
public class Empleado
{
// This will be identity key by convention
public int Id { get; set; }
// These could be set in fluent code
[Required]
[StringLength(100)]
public string Nombre { get; set; }
public string NoIdentificacion { get; set; }
// This will be an optional FK by convention
public int? IdentificacionId { get; set; }
public Identificacion Identificacion { get; set; }
public virtual ICollection<Nomina> Nominas { get; set; }
}
public class Nomina
{
// This will be identity key by convention
public int Id { get; set; }
public string XXXXXX { get; set; }
... etc
public virtual ICollection<Empleado> Empleados { get; set; }
}
public class EmpleadoNomina
{
public int EmpleadoId { get; set; }
public int NominaId { get; set; }
public decimal Salario { get; set; }
public int DescuentoLey { get; set; }
public decimal? SalarioIngresoEgreso { get; set; }
public Nomina Nomina { get; set; }
public Empleado Empleado { get; set; }
}
// FluentApi
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Empleado>()
.HasMany<Nomina>(e => e.Nominas)
.WithMany(c => c.Empleado)
.Map(cs =>
{
cs.MapLeftKey("Id");
cs.MapRightKey("Id");
cs.ToTable("EmpleadoNomina");
});
}
See here
EDIT: OK, If you need to keep the composite key on Empleado, then you will need to reference it with a composite FK. So you need to add the other 2 FK fields:
// Entidad relación
public class EmpleadoNomina
{
public int EmpleadoId { get; set; }
public int IdentificacionId { get; set; }
public string NoIdentificacion { get; set; }
public int NominaId { get; set; }
public decimal Salario { get; set; }
public int DescuentoLey { get; set; }
public decimal? SalarioIngresoEgreso { get; set; }
public Nomina Nomina { get; set; }
public Empleado Empleado { get; set; }
}
Then the fluent code:
modelBuilder.Entity<EmpleadoNomina>()
.HasRequired(en => en.Empleado)
.WithMany()
.HasForeignKey(en => new {en.EmpleadoId, en.IdentificacionId , en.NoIdentificacion });
Also, I am not sure IdentificacionId can be nullable. See here.

I solved it with Index Dataanotations to create the Unique Composited Index instead of a Composited primary key (this was responsible of my problem).
I removed the composite keys from the main class and added a list of EmployeeNomine to the two classes of entities.
I changed everything as shown below and now it is working very well. This what I wanted to do from the beginning.
// Class 2
public class Empleado
{
[Key]
public int Id { get; set; }
[Required]
[StringLength(100)]
public string Nombre { get; set; }
[Index("IX_Identificacion", 1, IsUnique = true)]
public int? IdentificacionId { get; set; }
public Identificacion Identificacion { get; set; }
[Required]
[StringLength(11)]
[Index("IX_Identificacion", 2, IsUnique = true)]
public string NoIdentificacion { get; set; }
public List<EmpleadoNomina> EmpleadoNominas { get; set; }
}
// Class 1
public class Nomina
{
[Key]
public int Id { get; set; }
[Required]
[StringLength(200)]
public string Descripcion { get; set; }
public int Frecuencia { get; set; }
public int Dia { get; set; }
public List<EmpleadoNomina> EmpleadoNominas { get; set; }
}
// Relation Entity (Table)
public class EmpleadoNomina
{
public int EmpleadoId { get; set; }
public int NominaId { get; set; }
public decimal Salario { get; set; }
public int DescuentoLey { get; set; }
public decimal? SalarioIngresoEgreso { get; set; }
public Nomina Nomina { get; set; }
public Empleado Empleado { get; set; }
}
// FluentApi
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Nominas -> Empleados
modelBuilder.Entity<EmpleadoNomina>().HasKey(k => new { k.NominaId, k.EmpleadoId });
modelBuilder.Entity<EmpleadoNomina>().HasRequired(e => e.Empleado).WithMany(n => n.EmpleadoNominas).HasForeignKey(r => r.EmpleadoId);
modelBuilder.Entity<EmpleadoNomina>().HasRequired(n => n.Nomina).WithMany(n => n.EmpleadoNominas).HasForeignKey(n => n.NominaId);
}
It's I always wanted to do. thanks for everything

Related

Migration failed while trying to create a many to many relationship

I am trying to connect two tables with a code first migration. I thought EF would create many to many relationship table itself but I get error "build failed". While building whole project everything works fine. It's just the migration.
Following are my models -
Task:
[Key]
public int Id { get; set; }
public DateTime? CreatedAt { get; set; }
public DateTime? EndedAt { get; set; }
[Column(TypeName = "text")]
public string CreatedBy { get; set; }
[Required]
[Column(TypeName = "text")]
public string Title { get; set; }
[Required]
[Column(TypeName = "text")]
public string Description { get; set; }
public virtual TaskGroups TaskGroup { get; set; }
public string Status { get; set; }
[Column(TypeName = "text")]
public string WantedUser { get; set; }
TaskGroup:
[Required]
public int Id { get; set; }
[Required]
public string GroupName { get; set; }
public virtual Tasks Tasks { get; set; }
At first I've tried with ICollection<> but I got the same error.
My project is .Net Core 3.
Any ideas?
Edit
Tasks
[Key]
public int Id { get; set; }
public DateTime? CreatedAt { get; set; }
public DateTime? EndedAt { get; set; }
[Column(TypeName = "text")]
public string CreatedBy { get; set; }
[Required]
[Column(TypeName = "text")]
public string Title { get; set; }
[Required]
[Column(TypeName = "text")]
public string Description { get; set; }
public string Status { get; set; }
[Column(TypeName = "text")]
public string WantedUser { get; set; }
public IList<TaskGroupTask> TaskGroupTask { get; set; }
TaskGroups
[Key]
public int Id { get; set; }
[Required]
public string GroupName { get; set; }
public IList<TaskGroupTask> { get; set; }
TaskGroupTask
public int TaskId { get; set; }
public int TaskGroupId { get; set; }
public Tasks Tasks { get; set; }
public TaskGroups TaskGroups { get; set; }
DbContext
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<TaskGroupTask>(e =>
{
e.HasKey(p => new { p.TaskId, p.TaskGroupId });
e.HasOne(p => p.Tasks).WithMany(t =>
t.TaskGroupTask).HasForeignKey(p => p.TaskId);
e.HasOne(p => p.TaskGroups).WithMany(tg =>
tg.TaskGroupTask).HasForeignKey(p => p.TaskGroupId);
});
}
public DbSet<TaskGroupTask> TaskGroupTask { get; set; }
You will need to create a joining entity, like MyJoiningEntity or TaskGroupTask, whose sole purpose is to create a link between Task and TaskGroup. Following models should give you the idea -
public class Task
{
public int Id { get; set; }
public string Description { get; set; }
public IList<JoiningEntity> JoiningEntities { get; set; }
}
public class TaskGroup
{
public int Id { get; set; }
public string GroupName { get; set; }
public IList<JoiningEntity> JoiningEntities { get; set; }
}
// this is the Joining Entity that you need to create
public class JoiningEntity
{
public int TaskId { get; set; }
public int TaskGroupId { get; set; }
public Task Task { get; set; }
public TaskGroup TaskGroup { get; set; }
}
Then you can configure the relation in the OnModelCreating method of your DbContext class, like -
modelBuilder.Entity<JoiningEntity>(e =>
{
e.HasKey(p => new { p.TaskId, p.TaskGroupId });
e.HasOne(p => p.Task).WithMany(t => t.JoiningEntities).HasForeignKey(p => p.TaskId);
e.HasOne(p => p.TaskGroup).WithMany(tg => tg.JoiningEntities).HasForeignKey(p => p.TaskGroupId);
});
This will define a composite primary key on JoiningEntity table based on the TaskId and TaskGroupId properties. Since this table's sole purpose is to link two other tables, it doesn't actually need it's very own Id field for primary key.
Note: This approach is for EF versions less than 5.0. From EF 5.0 you can create a many-to-many relationship in a more transparent way.
Since I have some time, I've decided to pull all pices of the code in one place. I think it would be very usefull sample how to create code-first many-to-many relations for database tables. This code was tested in Visual Studio and a new database was created without any warnings:
public class Task
{
public Task()
{
TaskTaskGroups = new HashSet<TaskTaskGroup>();
}
[Key]
public int Id { get; set; }
public DateTime? CreatedAt { get; set; }
public DateTime? EndedAt { get; set; }
[Column(TypeName = "text")]
public string CreatedBy { get; set; }
[Required]
[Column(TypeName = "text")]
public string Title { get; set; }
[Required]
[Column(TypeName = "text")]
public string Description { get; set; }
public string Status { get; set; }
[Column(TypeName = "text")]
public string WantedUser { get; set; }
[InverseProperty(nameof(TaskTaskGroup.Task))]
public virtual ICollection<TaskTaskGroup> TaskTaskGroups { get; set; }
}
public class TaskGroup
{
public TaskGroup()
{
TaskTaskGroups = new HashSet<TaskTaskGroup>();
}
[Required]
public int Id { get; set; }
[Required]
public string GroupName { get; set; }
[InverseProperty(nameof(TaskTaskGroup.TaskGroup))]
public virtual ICollection<TaskTaskGroup> TaskTaskGroups { get; set; }
}
public class TaskTaskGroup
{
[Key]
public int Id { get; set; }
public int TaskId { get; set; }
[ForeignKey(nameof(TaskId))]
[InverseProperty(nameof(TaskTaskGroup.Task.TaskTaskGroups))]
public virtual Task Task { get; set; }
public int TaskGroupId { get; set; }
[ForeignKey(nameof(TaskGroupId))]
[InverseProperty(nameof(TaskTaskGroup.Task.TaskTaskGroups))]
public virtual TaskGroup TaskGroup { get; set; }
}
public class TaskDbContext : DbContext
{
public TaskDbContext()
{
}
public TaskDbContext(DbContextOptions<TaskDbContext> options)
: base(options)
{
}
public DbSet<Task> Tasks { get; set; }
public DbSet<TaskGroup> TaskGroups { get; set; }
public DbSet<TaskTaskGroup> TaskTaskGroups { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Server=localhost;Database=Task;Trusted_Connection=True;");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TaskTaskGroup>(entity =>
{
entity.HasOne(d => d.Task)
.WithMany(p => p.TaskTaskGroups)
.HasForeignKey(d => d.TaskId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_TaskTaskGroup_Task");
entity.HasOne(d => d.TaskGroup)
.WithMany(p => p.TaskTaskGroups)
.HasForeignKey(d => d.TaskGroupId)
.HasConstraintName("FK_TaskTaskGroup_TaskCroup");
});
}
}

Defining the one to many relationship in OnModelCreating using Entity Framework Core 3.1

I am new to Entity Framework Core 3.1 and trying to define the one-to-many relationship between two tables. I am currently struggling and getting compilation errors. Could somebody tell me what the problem could be.
The error is:
PersonNote does not contain the definition for PersonNote
I am currently getting is at line
entity.HasOne(d => d.PersonNote)
How else could I define one-to-many relationship?
The two tables are Person and PersonNote. One Person can have many PersonNotes. I have defined the models for them
public class Person
{
public int Id { get; set; }
public int? TitleId { get; set; }
public string FirstName { get; set; }
public string FirstNamePref { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public DateTime? DateOfBirth { get; set; }
public string Gender { get; set; }
public int AddressId { get; set; }
public string TelephoneNumber { get; set; }
public string MobileNumber { get; set; }
public string Email { get; set; }
public int? PartnerId { get; set; }
public bool Enabled { get; set; }
public string CreatedBy { get; set; }
public DateTime Created { get; set; }
public string ModifiedBy { get; set; }
public DateTime Modified { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime RecordStartDateTime { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime RecordEndDateTime { get; set; }
public Address Address { get; set; }
public Title Title { get; set; }
public Client Client { get; set; }
internal static IEnumerable<object> Include(Func<object, object> p)
{
throw new NotImplementedException();
}
public PersonNote PersonNote { get; set; }
}
public class PersonNote
{
public int Id { get; set; }
public int PersonId { get; set; }
public string Note { get; set; }
public int AuthorId { get; set; }
public string CreatedBy { get; set; }
public DateTime Created { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime RecordStartDateTime { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime RecordEndDateTime { get; set; }
}
public IEnumerable<PersonNote> GetPersonNotes(int personId)
{
var PersonNotes = PersonNote
.Include(x => x.)
.Where(x => x.Id == personId)
.ToList();
return PersonNotes;
}
I have tried the following in OnModelCreating:
modelBuilder.Entity<PersonNote>(entity =>
{
entity.ToTable("PersonNote", "common");
entity.HasOne(d => d.PersonNote)
.WithMany(p => p.Person)
.HasForeignKey(d => d.PersonId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_commonPersonNote_commonPerson");
});
You should have have something like this (other properties are omitted):
class Person
{
[Key]
public int Id { get; set; }
public List<PersonNote> PersonNotes { get; set; }
}
class PersonNote
{
[Key]
public int Id { get; set; }
public int PersonId { get; set; }
}
class StackOverflow : DbContext
{
public DbSet<Person> Persons { get; set; }
public DbSet<PersonNote> PersonNotes { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.HasMany(p => p.PersonNotes)
.WithOne()
.HasForeignKey(p => p.PersonId);
}
}

How do I get a response based on two different IDs in my API?

public class Report
{
[Key]
public int ReportId { get; set; }
[ForeignKey("Subjects")]
public int SubjectId { get; set; }
public Subjects Subjects { get; set; }
[ForeignKey("Teacher")]
public int TeacherId { get; set; }
public Teacher Teacher { get; set; }
[ForeignKey("MarkType")]
public int MarkTypeId { get; set; }
public MarkType MarkType { get; set; }
}
public class Teacher
{
[Key]
public int TeacherId { get; set; }
[MaxLength(50)]
[Required]
public string FName { get; set; }
[MaxLength(50)]
[Required]
public string LName { get; set; }
}
public class Student
{
[Key]
public int StudentId { get; set; }
[MaxLength(50)]
[Required]
public string FName { get; set; }
[MaxLength(50)]
[Required]
public string LName { get; set; }
[ForeignKey("Grade")]
public int GradeId { get; set; }
public Grade Grade { get; set; }
}
public class Grade
{
[Key]
public int GradeId { get; set; }
public int StudentGrade { get; set; }
}
public class Subjects
{
[Key]
public int SubjectId { get; set; }
[MaxLength(50)]
[Required]
public string SubjectName { get; set; }
}
public class Terms
{
[Key]
public int TermId { get; set; }
public int Term { get; set; }
}
public class MarkType
{
[Key]
public int MarkTypeId { get; set; }
[MaxLength(20)]
[Required]
public string TypeName { get; set; }
}
public class StudentMark
{
[Key]
public int StudentMarkId { get; set; }
[ForeignKey("Report")]
public int ReportId { get; set; }
public Report Report { get; set; }
[ForeignKey("Student")]
public int StudentId { get; set; }
public Student Student { get; set; }
public int Mark { get; set; }
[ForeignKey("Terms")]
public int TermId { get; set; }
public Terms Terms { get; set; }
}
In the API I am making I want to have the ability to use two different IDs to get a more specific response.
var report = ReportDBContext.StudentMark
.Include(p => p.Student.Grade).Include(p => p.Report)
.Include(p => p.Terms).Include(a => a.Report.Subjects).Include(a => a.Terms)
.Include(a => a.Report.MarkType).Include(a => a.Report.Teacher).ToList();
This allowed me to get StudentMark as well as it's related entities but I want to have the ability to use The student's Id and the Term's Id to get a student's marks for that term and all the subjects related to the student. I am a beginner to Web API so please let me know if I need to add more context.
If you want to query by either StudentId or TermId, I suggest that you provide two different endpoints for these two different queries. Use LINQ Where to check your conditions.
public StudentMark[] GetMarksByStudentId(int studentId) {
return ReportDBContext.StudentMark
/* .Include(...) */
.Where(mark => mark.StudentId == studentId)
.ToArray();
}
public StudentMark[] GetMarksByTermId(int termId) {
return ReportDBContext.StudentMark
/* .Include(...) */
.Where(mark => mark.TermId == termId)
.ToArray();
}
If you want to query by StudentId and TermId simultaneously, introduce a query object to encapsulate your parameters. You can test for multiple conditions in the Where clause with AND &&.
public StudentMark[] FindMarks(StudentMarkQuery query) {
return ReportDBContext.StudentMark
/* .Include(...) */
.Where(mark => mark.StudentId == query.StudentId
&& mark.TermId == query.TermId)
.ToArray();
}
The StudentMarkQuery class is introduced so you can add additional parameters without changing the overall signature of the endpoint:
public class StudentMarkQuery {
public int StudentId { get; set; }
public int TermId { get; set; }
}

EF Adding an additional FK?

I have the following 2 entities:
public class Team
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Fixture> Fixtures { get; set; }
}
public class Fixture
{
public int Id { get; set; }
public Result Result { get; set; }
public int HomeTeamId { get; set; }
public int AwayTeamId { get; set; }
public virtual Team HomeTeam { get; set; }
public virtual Team AwayTeam { get; set; }
}
I have then mapped it like so:
public class FixtureMap : EntityTypeConfiguration<Fixture>
{
public FixtureMap()
{
HasRequired(x => x.AwayTeam).WithMany().HasForeignKey(x => x.AwayTeamId);
HasRequired(x => x.HomeTeam).WithMany().HasForeignKey(x => x.HomeTeamId);
}
}
But when I add a migration, EF is creating an additional FK and column to my Fixture table and I've no idea why? How can I tell it not too?
As you can see its added a column called Team_Id and created an FK from it even tho I have specified the relationship in the mapping?
use this code:
public class Team
{
public int Id { get; set; }
public string Name { get; set; }
[InverseProperty("HomeTeam")]
public virtual ICollection<Fixture> HomeFixtures { get; set; }
[InverseProperty("AwayTeam")]
public virtual ICollection<Fixture> AwayFixtures { get; set; }
}
public class Fixture
{
public int Id { get; set; }
public Result Result { get; set; }
public int HomeTeamId { get; set; }
public int AwayTeamId { get; set; }
[InverseProperty("HomeFixtures")]
[ForeignKey("HomeTeamId ")]
public virtual Team HomeTeam { get; set; }
[InverseProperty("AwayFixtures")]
[ForeignKey("AwayTeamId")]
public virtual Team AwayTeam { get; set; }
}
And :
public class FixtureMap : EntityTypeConfiguration<Fixture>
{
public FixtureMap()
{
HasRequired(x => x.AwayTeam).WithMany().HasForeignKey(x => x.AwayTeamId).willCascadeOnDelete(false);
HasRequired(x => x.HomeTeam).WithMany().HasForeignKey(x => x.HomeTeamId);
}
}

Entity Framework Code First and Invalid Object Name Error

I have a composite table called ImporterState, that are tied to a table called Importer and State. The error happens here context.Importers.Include(q => q.States). Why is this happening?
{"Invalid object name 'ImporterStates'."}
[Table("HeadlineWebsiteImport", Schema = "GrassrootsHoops")]
public class Importer
{
public int Id { get; set; }
public string Name { get; set; }
public string RssUrl { get; set; }
public string Type { get; set; }
public string Keywords { get; set; }
public bool Active { get; set; }
public DateTime DateModified { get; set; }
public DateTime DateCreated { get; set; }
public int WebsiteId { get; set; }
public HeadlineWebsite Website { get; set; }
[InverseProperty("Importers")]
public ICollection<State> States { get; set; }
}
[Table("State", Schema = "GrassrootsHoops")]
public class State
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Abbr { get; set; }
[InverseProperty("States")]
public ICollection<Headline> Headlines { get; set; }
[InverseProperty("States")]
public ICollection<Importer> Importers { get; set; }
}
The many to many is not possible using attributes only.
try using something like:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Importer>()
.HasMany(i => i.States)
.WithMany(s => s.Importers)
.Map(m =>
{
m.MapLeftKey("ImporterId");
m.MapRightKey("StateId");
m.ToTable("ImporterState");
});
}