Entity Framework Core and many to many relation - c#

I try to migrate a small application to Entity Framework Core but I cant get the many to many relation to work.
First my Entities
public class Currency : Entity<int>, IMayHaveUser
{
public string Code { get; set; }
public string Name { get; set; }
public string Symbol { get; set; }
public virtual List<CountryCurrency> CountryCurrencies { get; set; }
public bool IsUserDefined => User != null;
[ForeignKey("UserId")]
public virtual User User { get; set; }
public long? UserId { get; set; }
}
public class Country : Entity<int>, IMayHaveUser
{
public string Iso2Code { get; set; }
public virtual ICollection<Era> Eras { get; set; }
public string Name { get; set; }
public virtual List<CountryCurrency> CountryCurrencies { get; set; }
[NotMapped]
public bool IsUserDefined => User != null;
[ForeignKey("UserId")]
public virtual User User { get; set; }
public long? UserId { get; set; }
}
public class CountryCurrency : Entity<Guid>
{
public int CountryId { get; set; }
public Country Country { get; set; }
public int CurrencyId { get; set; }
public Currency Currency { get; set; }
}
and my DbContext is
modelBuilder.Entity().HasKey(currency => new {
currency.CountryId, currency.CurrencyId });
modelBuilder.Entity()
.HasOne(pt => pt.Country)
.WithMany(p => p.CountryCurrencies)
.HasForeignKey(pt => pt.CountryId);
modelBuilder.Entity<CountryCurrency>()
.HasOne(pt => pt.Currency)
.WithMany(t => t.CountryCurrencies)
.HasForeignKey(pt => pt.CurrencyId);
now when I add a currency for example
Currency currency;
Country country;
CountryCurrency countryCurrency;
currency = new Currency();
currency.Id = i++;
currency.User = null;
currency.Code = "ETB";
currency.Name = "Ethiopian Birr";
currency.Symbol = "Br";
country =
this._context.Countries.FirstOrDefault(
country1 => country1.Iso2Code == "ET");
if (country != null)
{
currency.CountryCurrencies = new List<CountryCurrency>();
countryCurrency = new CountryCurrency();
countryCurrency.Country = country;
countryCurrency.Currency = currency;
currency.CountryCurrencies.Add(countryCurrency);
this.InitialCurrencies.Add(currency);
}
this._context.Currencies.Add(currency);
so when now I'm retrieve the data in my test I get this with this code
Country = context.Countries.Include(country => country.CountryCurrencies).First();
I can't get the currency the id is set but the property not...

You have also to include the currency entity, not just the join entity
Country = context.Countries
.Include(country => country.CountryCurrencies)
.ThenInclude(e => e.Currency)
.First();

Related

How to construct a specific linq query?

I have three tables, doctor, office and appointment. Office table has DoctorId as a foreign key, and Apponitment has a foreign key OfficeId. I want to fetch all the appointments that have OfficeId equal to Ids in the list of offices that have the same doctorId. Specifically, I don't know how to extract ids from the list of offices. Here is my code, I skipped some parts for brevity:
public class Appointment1 : BaseEntity
{
public int? Patient1Id { get; set; }
[ForeignKey("Patient1Id")]
public Patient1 Patient { get; set; }
public int Office1Id { get; set; }
[ForeignKey("Office1Id")]
public Office1 Office { get; set; }
[DataType(DataType.Date)]
public DateTime StartDateAndTimeOfAppointment { get; set; }
[DataType(DataType.Date)]
public DateTime EndDateAndTimeOfAppointment { get; set; }
public bool? Status { get; set; }
public string Remarks { get; set;}
}
public class Doctor1 : BaseEntity
{
public int ApplicationUserId { get; set; }
[ForeignKey("ApplicationUserId")]
public ApplicationUser ApplicationUser { get; set; }
public string Name { get; set; }
public string Resume { get; set; }
}
public class Office1 : BaseEntity
{
public int Doctor1Id { get; set; }
[ForeignKey("Doctor1Id")]
public Doctor1 Doctor { get; set; }
public decimal InitialExaminationFee { get; set; }
public decimal FollowUpExaminationFee { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string Country { get; set; }
}
public async Task<List<Appointment1>> GetAppointmentsWithSearchingAndPaging(QueryParameters queryParameters,
int userId)
{
var doctor = await _context.Doctors1.Where(x => x.ApplicationUserId == userId)
.FirstOrDefaultAsync();
var office = await _context.Offices.Where(x => x.Doctor1Id == doctor.Id)
.FirstOrDefaultAsync();
IQueryable<Appointment1> appointment = _context.Appointments1.Include(x => x.Patient)
.Where(x => x.Office1Id == office.Id)
.AsQueryable().OrderBy(x => x.Id);
if (queryParameters.HasQuery())
{
appointment = appointment
.Where(x => x.Office.Street.Contains(queryParameters.Query));
}
appointment = appointment.Skip(queryParameters.PageCount * (queryParameters.Page - 1))
.Take(queryParameters.PageCount);
return await appointment.ToListAsync();
}
The problem is with office which gives firstordefaultasync, and should give list, because I want all the ids, but in the end I get only appointments that have one identical officeid as a foreign key...thanks in advance!
Here is the answer, I needed this part of code, my question was not precise so I appologize:
var offices = await _context.Offices.Where(x => x.Doctor1Id == doctor.Id)
.ToListAsync();
IEnumerable<int> ids = offices.Select(x => x.Id);
IQueryable<Appointment1> appointment = _context.Appointments1.Include(x => x.Patient)
.Where(x => ids.Contains(office.Id))
.AsQueryable().OrderBy(x => x.Id);

EF Core Add() only adds the last element to an ICollection

I'm trying to add an element to an ICollection object in EF Core 5. But only the last element remains on the list.
As an example, When a student submits an answer, I want to add him to the Participants of the exam(A). It works as it should. But when the same person submits another exam(B). His records are lost from the previous exam(A).
Here is my code.
SubmitExam Method in ExamController.cs
[HttpPost("submit")]
public async Task<ActionResult<GetExamDTO>> SubmitExam(GetExamDTO getExamDTO)
{
var username = User.FindFirst(ClaimTypes.Name).Value;
var user = await _userManager.Users
.Where(u => u.UserName == username)
.Include(u => u.ParticipatedExams)
.ThenInclude(er => er.Exam)
.FirstOrDefaultAsync();
if (user == null)
return Unauthorized("Invalid User");
var exam = await _dbContext.Exams
.Where(e => e.Id == getExamDTO.Id)
.Include(e => e.Creator)
.Include(e => e.Participants)
.Include(exam => exam.Questions)
.ThenInclude(question => question.Options)
.AsSplitQuery()
.SingleOrDefaultAsync();
if (exam == null)
return BadRequest("Invalid Exam Id");
if (!exam.SubmissionEnabled)
return BadRequest("Exam is over. You are too late.");
double marksObtained = 0;
double negativeMarksObtained = 0;
for (int i = 0; i != getExamDTO.Questions.Count(); ++i)
{
var question = exam.Questions.ElementAt(i);
if (question.CorrectAnswerText == getExamDTO.Questions[i].ProvidedAnswer.Text)
{
marksObtained += question.Marks;
}
else
{
marksObtained -= exam.NegativeMarks;
negativeMarksObtained -= exam.NegativeMarks;
}
}
var participatedFromExam = exam.Participants.Where(u => u.Id == user.Id)
.SingleOrDefault();
var participatedFromUser = user.ParticipatedExams
.Where(er => er.ExamId == exam.Id)
.SingleOrDefault();
System.Console.WriteLine("Submit" + participatedFromExam);
if (participatedFromExam == null && participatedFromUser == null) // If user sits for the first time, count him as new.
{
exam.Participants.Add(user); // Participants gets empty for every exam except the last submitted one. It fried my brain.
++exam.Attendees;
if (await _dbContext.SaveChangesAsync() > 0)
System.Console.WriteLine("Added submission");
var result = new ExamResult
{
Exam = exam,
Score = marksObtained
};
user.ParticipatedExams.Add(result);
await _userManager.UpdateAsync(user);
}
var examDto = examToDto(exam);
examDto.Participated = true;
examDto.MarksObtained = marksObtained;
examDto.Questions = getExamDTO.Questions;
examDto.NewSubmission = true;
examDto.NegativeMarks = negativeMarksObtained;
return Ok(examDto);
}
EntityUser.cs
public class EntityUser : IdentityUser
{
public ICollection<ExamResult> ParticipatedExams { get; set; }
public DateTime? LastActive { get; set; } = DateTime.UtcNow;
}
Exam.cs
public class Exam
{
public string Title { get; set; }
public Guid Id { get; set; }
public int Attendees { get; set; } = 0;
public string CreatorId { get; set; }
public EntityUser Creator { get; set; }
public DateTime CreatedAt { get; set; }
public int Duration { get; set; }
public double TotalMarks { get; set; }
public double NegativeMarks { get; set; }
public bool SubmissionEnabled { get; set; }
public ICollection<Question> Questions { get; set; }
public ICollection<EntityUser> Participants { get; set; }
}
ExamResult.cs
public class ExamResult
{
public Guid Id { get; set; }
public Exam Exam { get; set; }
public Guid ExamId { get; set; }
public double Score { get; set; }
}
GetExamDTO.cs
public class GetExamDTO
{
public string Title { get; set; }
public Guid Id { get; set; }
public int Attendees { get; set; } = 0;
public string Subject { get; set; }
public string CreatorId { get; set; }
public string Creator { get; set; }
public DateTime CreatedAt { get; set; }
public int Duration { get; set; }
public double TotalMarks { get; set; }
public double MarksObtained { get; set; }
public double NegativeMarks { get; set; }
public bool SubmissionEnabled { get; set; }
public bool Participated { get; set; }
public bool NewSubmission { get; set; }
public List<QuestionDTO> Questions { get; set; }
public List<ParticipantDTO> Participants { get; set; }
}
I will appreciate any help. Thanks in Advance.
fix the Examresult class by adding UserId
public partial class ExamResult
{
public Guid Id { get; set; }
public int? UserId { get; set; }
public virtual EntityUser User { get; set; }
public Guid ExamId { get; set; }
public virtual Exam Exam { get; set; }
public double Score { get; set; }
}
public partial class EntityUser : IdentityUser
{
public virtual ICollection<ExamResult> ParticipatedExams { get; set; }
public DateTime? LastActive { get; set; } = DateTime.UtcNow;
}
you can try to add fluent api to your db context
public virtual DbSet<ExamResult> ExamResults { get; set; }
.....
modelBuilder.Entity<ExamResult>(entity =>
{
entity.HasOne(d => d.User)
.WithMany(p => p.ParticipatedExams)
.HasForeignKey(d => d.UserId);
});
and code
var result = new ExamResult
{
UserId=user.Id,
ExamId = exam.Id,
Score = marksObtained
};
_dbContext.ExamResults.Add(result)
await _dbContext.SaveChangesAsync();

One to Many relationship always bring me empty

I try to use Entity Framework with code first and fluent api to implement a one to many relationship
I have two classes
namespace Mantenimiento.Business.Entities
{
public class Personal : Entity
{
[Key]
public int Id { get; set; }
public int? Dni { get; set; }
public string Nombre { get; set; }
public string Apellido { get; set; }
public string Cuil { get; set; }
public string Legajo { get; set; }
[ForeignKey("Dni")]
public ICollection<ContactoEmergencia> Contacto { get; set; }
}
namespace Mantenimiento.Business.Entities
{
public class ContactoEmergencia : Entity
{
[Key]
public int Id { get; set; }
public int? Dni { get; set; }
public string ApellidoNombre { get; set; }
public string Vinculo { get; set; }
public string Domicilio { get; set; }
public string telefono { get; set; }
public string Comentario { get; set; }
public int CreateUserId { get; set; }
[ForeignKey("Dni")]
public virtual Personal Personal { get; set; }
}
}
This is my dbContext
#region personals
modelBuilder.Entity<Personal>().ToTable("InfoPersonal").HasKey(t => t.Id);
modelBuilder.Entity<Personal>().Property(c => c.Id).UseSqlServerIdentityColumn().IsRequired();
modelBuilder.Entity<Personal>().Property(c => c.CreatedDate).HasDefaultValue(DateTime.Now);
modelBuilder.Entity<Personal>().Property(c => c.LastModifiedDate).HasDefaultValue(DateTime.Now);
modelBuilder.Entity<Personal>().Property(c => c.Deleted).HasDefaultValue(false);
modelBuilder.Entity<Personal>().HasMany<ContactoEmergencia>(c => c.Contacto).WithOne(p => p.Personal).HasForeignKey(s => s.Dni);
#endregion
#region contactoEmergencias
modelBuilder.Entity<ContactoEmergencia>().ToTable("InfoEmergencia").HasKey(d => d.Dni);
modelBuilder.Entity<ContactoEmergencia>().Property(c => c.CreatedDate).HasDefaultValue(DateTime.Now);
modelBuilder.Entity<ContactoEmergencia>().Property(c => c.LastModifiedDate).HasDefaultValue(DateTime.Now);
modelBuilder.Entity<ContactoEmergencia>().Property(c => c.Deleted).HasDefaultValue(false);
#endregion
And my query is
return await _context.personals
.Include(c => c.Contacto)
.Where(p => p.Deleted == false)
.OrderBy(s => s.Apellido)
.ToListAsync(
);
But the properties is always empty.
i need to relate Personal.Di with Contacto.Dni, i had to change the key?
You should remove ForeignKey attribute from Personal entity. In one to many relationship only child entity could accept ForeignKey.

Adding data to crosstable created by EF with AutoMapper

It is my first a many-to-many relation consisting of Team, User and TeamUser objects. In TeamController I mapped TeamForCreationDto to Team, but ICollection Members was empty. Some bug in CreateMap?Q1: How it should be combined to fill all property and tables by EF? Now I have "for" loop and there created/added TeamUser.
Q2: If I must fill both property AdminId and Admin?
A2: No, after adding Admin, property AdminId in DB thanks EF will find value automatically.
public class Team
{
public int Id { get; set; }
public string Name { get; set; }
public int AdminId { get; set; }
public User Admin { get; set; }
//public int[] MembersId { get; set; }
public ICollection<TeamUser> Members { get; set; }
}
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public ICollection<Team> TeamsAsAdmin { get; set; }
public ICollection<TeamUser> TeamsAsMember { get; set; }
}
public class TeamUser
{
public int TeamId { get; set; }
public Team Team { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
Relations between tables in ModelBuilder
builder.Entity<Team>()
.HasOne(t => t.Admin)
.WithMany(u => u.TeamsAsAdmin)
.OnDelete(DeleteBehavior.Restrict);
builder.Entity<TeamUser>()
.HasKey(tu => new { tu.TeamId, tu.UserId });
builder.Entity<TeamUser>()
.HasOne(tu => tu.User)
.WithMany(u => u.TeamsAsMember)
.HasForeignKey(tu => tu.UserId)
.OnDelete(DeleteBehavior.Cascade);
builder.Entity<TeamUser>()
.HasOne(tu => tu.Team)
.WithMany(t => t.Members)
.HasForeignKey(tu => tu.TeamId);
My CreateMap in AutoMapperProfiles()
CreateMap<TeamForCreationDto, Team>().ReverseMap().ForMember(u => u.MembersId, opt => opt.MapFrom(x => x.Members));
My TeamController.cs
public async Task<IActionResult> Create(int userId, TeamForCreationDto teamForCreationDto)
{
if (await _repoTeams.TeamExists(teamForCreationDto.Name))
return BadRequest("A team with this name already exists!");
var mappedTeam = _mapper.Map<Team>(teamForCreationDto);
//mappedTeam.AdminId = userId;
mappedTeam.Admin = await _repoUsers.GetUser(userId);
_repoTeams.Add(mappedTeam);
for (int i = 0; i < teamForCreationDto.MembersId.Length; i++)
{
TeamUser tm = new TeamUser();
tm.Team = mappedTeam;
tm.User = await _repoUsers.GetUser(teamForCreationDto.MembersId[i]);
_repoTeams.Add(tm);
}
await _repoTeams.SaveAll();
}
TeamForCreationDto.cs
public class TeamForCreationDto
{
int Id { get; set; }
public string Name { get; set; }
public string PhotoUrl { get; set; }
public int[] MembersId { get; set; }
}

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.

Categories