Can't create many-to-many relationship - c#

I have two entites with a many-to-many relationship. Company and SearchKeyword.
Here are the models:
class SearchKeyword
{
public int ID { get; set; }
public string Text { get; set; }
public virtual ICollection<Company> Companies { get; set; }
}
class Company
{
public int ID { get; set; }
public string Name { get; set; }
public virtual OtherDetail OtherDetails { get; set; }
public virtual ICollection<SearchKeyword> SearchKeywords { get; set; }
}
I am trying to add a SearchKeyword to a company but it won't let me. I tried this:
using (var db = new PlaceDBContext())
{
Company c = db.Companies.Single(x => x.ID == 1);
SearchKeyword sk = db.SearchKeywords.Single(x => x.ID == 1);
c.SearchKeywords.Add(sk);
db.SaveChanges();
}
It says Object reference not set to an instance of an object. I am not sure what is null. In inspector I can see c and sk both have full values. I guess I must be missing a fundamental of how the many-to-many relationship works with EF.
What am I doing wrong?

This is because SearchKeywords is null.
Either you can assign a List to it before adding a new instance
using (var db = new PlaceDBContext())
{
Company c = db.Companies.Single(x => x.ID == 1);
SearchKeyword sk = db.SearchKeywords.Single(x => x.ID == 1);
c.SearchKeywords = new List<SearchKeyword>();
c.SearchKeywords.Add(sk);
db.SaveChanges();
}
Or you can do it constructor method
class Company
{
public Company()
{
SearchKeywords = new List<SearchKeyword>();
}
public int ID { get; set; }
public string Name { get; set; }
public virtual OtherDetail OtherDetails { get; set; }
public virtual ICollection<SearchKeyword> SearchKeywords { get; set; }
}

Related

AutoMapper MapExpression for sub-entity fails

I'm mapping select expression (projection) of Linq query. This is done to decouple logic layer from data access layer and logic layer should use only DTOs.
Expression<Func<CountyInfoDto, CountyInfoDto>> selector = c =>
new CountyInfoDto
{
Id = c.Id,
Citizens = c.Citizens.Select(p => new CitizenDto
{
}).ToList()
};
var resEx = mapper.MapExpression<Expression<Func<CountyInfo, CountyInfoDto>>>(selector);
This mapping fails with error Expression of type 'DTOs.CitizenDto' cannot be used for return type 'Entities.Citizen' however in CountyInfoDto property Citizens has type CitizenDto. Please note all mapping profiles are valid and simple objects can be mapped properly.
If I do like this, all works:
Expression<Func<CountyInfoDto, CountyInfoDto>> selector = c =>
new CountyInfoDto
{
Id = c.Id
};
var resEx = mapper.MapExpression<Expression<Func<CountyInfo, CountyInfoDto>>>(selector);
or this also works:
Expression<Func<CountyInfoDto, CountyInfoDto>> selector = c =>
new CountyInfoDto
{
Id = c.Id,
Citizens = new List<CitizenDto>
{
new CitizenDto
{
Id = c.Citizens.First().Id
}
}
};
var resEx = mapper.MapExpression<Expression<Func<CountyInfo, CountyInfoDto>>>(selector);
is there any possibility to avoid this error?
Classes:
public class CountyInfo
{
public CountyInfo()
{
Citizens = new HashSet<Citizen>();
}
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<Citizen> Citizens { get; set; }
}
public class Citizen
{
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string ZipCode { get; set; }
}
public class CountyInfoDto
{
public CountyInfoDto()
{
Citizens = new List<CitizenDto>();
}
public Guid Id { get; set; }
public string Name { get; set; }
public List<CitizenDto> Citizens { get; set; }
}
public class CitizenDto
{
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string ZipCode { get; set; }
}
Mappings:
CreateMap<CountyInfo, CountyInfoDto>().ReverseMap();
CreateMap<Citizen, CitizenDto>().ReverseMap();
I'm using AutoMapper.Extensions.ExpressionMapping, after update to latest version error is: No coercion operator is defined between types 'Entities.CountyInfo' and 'DTOs.CountyInfoDto'.

How can change a MYSQL Join Query in Linq Methods

I write a MySql join code, and want to retrive same value from the Dotnetcore linq methods.
My Join code is below:
SELECT GL.Id AS GradeLevels,
CRS.Name AS CourseName,
GL.Title AS GradlevelName,
AVG (ASTSTU.ObtainedMarks)
FROM GradeLevels GL
INNER JOIN Courses AS CRS ON CRS.GradeLevelsID = GL.Id
INNER JOIN Units AS UNT ON UNT.CourseID = CRS.ID
INNER JOIN Lessons AS LSN ON LSN.UnitsId = UNT.Id
INNER JOIN Assignments AS AST ON AST.LessonId = LSN.id
INNER JOIN AssignmentStudents AS ASTSTU ON ASTSTU.AssignmentId = AST.id
WHERE CRS.SchoolSystemsID = "08d6a1f2-26df-4ad5-25d3-2a26960aa3fd" -- School System id.
GROUP BY GL.Id;
Now I want to change above MySQL Join into Dotnet core linq method to create an API that will be Showing, I try to write code for this
public async Task<ICollection<GradeLevels>> GetSchoolSystemGradLevelsAverage(Guid schoolSystemId)
{
List<GradeLevels> dashboadOverAllAverage = new List<GradeLevels>();
var dashboadOverAllAverage1 = await _GpsContext.GradeLevels
.Include(d=>d.Departments)
.ThenInclude(c=>c.Courses.Where(s=>s.SchoolSystemsID ==schoolSystemId))
.ThenInclude(u=>u.Units)
.ThenInclude(l=>l.Lessons)
.ThenInclude(a=>a.Assignment)
.ThenInclude(a=>a.assignmentStudents)
.GroupBy(g=>g.ID)
.ToListAsync();
return dashboadOverAllAverage;
}
Now I want to show the data though API and want to call to fields GradeLvels name and Average Marks.
[HttpGet()]
public async Task<IActionResult> GetCEOGradeLevelAverage(string schoolSystemId)
{
var overallgradeAverages = await _ceoDashboadRepository.GetSchoolSystemGradLevelsAverage(Guid.Parse(schoolSystemId));
List<GetGradeLevelAverageVm> getOverallAverageVms = new List<GetGradeLevelAverageVm>();
foreach (GradeLevels overallgradeAverage in overallgradeAverages)
{
getOverallAverageVms.Add(new GetGradeLevelAverageVm
{
Marks = overallgradeAverage.Id.ToString(), //Want to show lable of AvrageMark
Name = overallgradeAverage.Name //Want to show Gradelevel name
});
}
return Ok(getOverallAverageVms);
}
You do select too much from your DB. Here an example, how to select the nessecary values:
using (TestDbContext ctx = new TestDbContext())
{
var tmp = ctx.AssignmentStudents
.Include(s => s.Assignment) // Include all Childs..
.ThenInclude(a => a.Lesson)
.ThenInclude(l => l.Unit)
.ThenInclude(u => u.Course)
.ThenInclude(c => c.GradeLevel)
.Where(a => a.LessonId == 123)
.GroupBy(g => // Group by your Key-Values Grade and Course (You could take names instead of ids. Just for simplification)
new
{
GradeLevel = g.Assignment.Lesson.Unit.Course.GradeLevel.Id,
Course = g.Assignment.Lesson.Unit.Course.Id
})
.Select(s => // Select the result into an anonymous type
new
{
GradeLevels = s.Key.GradeLevel, // with same keys like grouping
Course = s.Key.Course,
AverageObtainedMarks = s.Average(a => a.ObtainedMarks) // and an average ObtainedMarks from objects matching the key
})
.Where(s => s.GradeLevel == 1);
foreach (var t in tmp)
{
Console.WriteLine(t.GradeLevels + " " + t.Course + ": " + t.AverageObtainedMarks);
}
}
Here a the classes and dbcontext I used:
public class GradeLevel
{
public int Id { get; set; }
public List<Course> Courses { get; set; }
}
public class Course
{
public int Id { get; set; }
public int GradeLevelId { get; set; }
public GradeLevel GradeLevel { get; set; }
public List<Unit> Units { get; set; }
}
public class Unit
{
public int Id { get; set; }
public int CourseId { get; set; }
public Course Course { get; set; }
public List<Lesson> Lessons { get; set; }
}
public class Lesson
{
public int Id { get; set; }
public int UnitId { get; set; }
public Unit Unit { get; set; }
public List<Assignment> Assignments { get; set; }
}
public class Assignment
{
public int Id { get; set; }
public int LessonId { get; set; }
public Lesson Lesson { get; set; }
public List<AssignmentStudent> AssignmentStudents { get; set; }
}
public class AssignmentStudent
{
public int Id { get; set; }
public int AssignmentId { get; set; }
public Assignment Assignment { get; set; }
public decimal ObtainedMarks { get; set; }
}
public class TestDbContext : DbContext
{
public DbSet<AssignmentStudent> AssignmentStudents { get; set; }
public DbSet<Assignment> Assignments { get; set; }
public DbSet<Lesson> Lessons { get; set; }
public DbSet<Unit> Units { get; set; }
public DbSet<Course> Courses { get; set; }
public DbSet<GradeLevel> GradeLevels { get; set; }
}

How to join two tables with linq?

I am trying to join two of my tables with linq based on an id, so far unseccesfully.
Here is how my models look :
public class WorkRole
{
public int WorkRoleId { get; set; }
public string RoleName { get; set; }
public string RoleDescription { get; set; }
public int CompanyId { get; set; }
public virtual Company Company { get; set; }
public virtual ICollection<WorkRolesUsersDetails> WorkRolesUsersDetails { get; set; }
}
public class WorkRolesUsersDetails
{
public int WRUDId { get; set; }
public int? WorkRoleId { get; set; }
public string UserDetailsId { get; set; }
public virtual WorkRole WorkRole { get; set; }
public virtual UserDetails UserDetails { get; set; }
public DateTime FocusStart { get; set; }
public DateTime FocusEnd { get; set; }
public bool isActive { get; set; }
}
I am trying to get in one view WorkRoleId, RoleName, RoleDescription and CompanyId from the first table and UserDetailsId, FocusStart, FocusEnd and isActive from the second table.
The farthest i got with my ideas was :
var query = db.WorkRoles.Join(db.WorkRolesUsersDetails,x => x.WorkRoleId,y => y.WorkRoleId,(x, y) => new { wr = x, wrud = y });
But sadly, it didn't work. I just don't know enough linq and couldn't get much out of other questions/answers here. Please, help.
Code for joining 2 tables is:
var list = db.WorkRoles.
Join(db.WorkRolesUsersDetails,
o => o.WorkRoleId, od => od.WorkRoleId,
(o, od) => new
{
WorkRoleId= o.WorkRoleId
RoleName= o.RoleName,
RoleDescription= o.RoleDescription,
CompanyId= o.CompanyId,
WRUDId= od.WRUDId,
UserDetailsId= od.UserDetailsId,
FocusStart=od.FocusStart,
FocusEnd=od.FocusEnd
})
If you are using EF may I suggest the Includes statement it works wonders. IF you have a foreign key assigned. It basically gets the other data with it.
static void Main(string[] args)
{
using (var context = new TesterEntities())
{
var peopleOrders = context.tePerson.Include("teOrder").First(p => p.PersonId == 1).teOrder.ToList();
peopleOrders.ForEach(x => Console.WriteLine($"{x.OrderId} {x.Description}"));
}
}
Combining manually without navigation context option.
public class Student
{
public int StudentID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<StudentTestScore> Scores { get; set; }
}
public class StudentTestScore
{
public int StudentID { get; set; }
public int Score { get; set; }
}
class Program
{
static void Main(string[] args)
{
var students = new List<Student>
{
new Student { StudentID = 1, FirstName = "Brett", LastName = "X" },
new Student { StudentID = 2, FirstName = "John", LastName = "X" }
};
var grades = new List<StudentTestScore> { new StudentTestScore { StudentID = 1, Score = 98 } };
var combined = students.Join(grades, x => x.StudentID, y => y.StudentID,
(x, y) => new
{
Student = $"{x.FirstName} {x.LastName}",
Grade = y.Score
}).ToList();
combined.ForEach(x => Console.WriteLine($"{x.Student} {x.Grade}"));
Console.ReadLine();
}

Collections duplicated when trying to update a detached entity's related collection

I have two API calls. GetExam and SaveExam. GetExam serializes to JSON which means by the time I go to save, the entity is detached. This isnt a problem, I can go retrieve the entity by its primary key and update its properties manually.
However, when I do so the exam questions get its current collection duplicated. For example, if examToSave.ExamQuestions had a few questions deleted, and a new one added all selectedExam.exam_question are duplicated and the new one is added in. Eg. if 3 questions existed, I deleted 1 and added 4 there will now be 7.
Domain models:
public partial class exam
{
public exam()
{
this.exam_question = new HashSet<exam_question>();
}
public int ID { get; set; }
public string ExamName { get; set; }
public string ExamDesc { get; set; }
public Nullable<decimal> TimeToComplete { get; set; }
public bool AllowBackStep { get; set; }
public bool RandomizeAnswerOrder { get; set; }
public int Attempts { get; set; }
public virtual ICollection<exam_question> exam_question { get; set; }
}
public partial class exam_question
{
public exam_question()
{
this.exam_answer = new HashSet<exam_answer>();
}
public int ID { get; set; }
public int ExamID { get; set; }
public string QuestionText { get; set; }
public bool IsFreeForm { get; set; }
public virtual exam exam { get; set; }
public virtual ICollection<exam_answer> exam_answer { get; set; }
}
public partial class exam_answer
{
public int ID { get; set; }
public string AnswerText { get; set; }
public int QuestionID { get; set; }
public bool IsCorrect { get; set; }
public virtual exam_question exam_question { get; set; }
}
Save method:
[Route("SaveExam")]
[HttpPost]
public IHttpActionResult SaveExam(ExamViewModel examToSave)
{
using (var db = new IntranetEntities())
{
// try to locate the desired exam to update
var selectedExam = db.exams.Where(w => w.ID == examToSave.ID).SingleOrDefault();
if (selectedExam == null)
{
return NotFound();
}
// Redacted business logic
// Map the viewmodel to the domain model
Mapper.CreateMap<ExamAnswerViewModel, exam_answer>();
Mapper.CreateMap<ExamQuestionViewModel, exam_question>().ForMember(dest => dest.exam_answer, opt => opt.MapFrom(src => src.QuestionAnswers));
Mapper.CreateMap<ExamViewModel, exam>().ForMember(dest => dest.exam_question, opt => opt.MapFrom(src => src.ExamQuestions));
var viewmodel = Mapper.Map<exam>(examToSave);
// Update exam properties
selectedExam.ExamName = viewmodel.ExamName;
selectedExam.ExamDesc = viewmodel.ExamDesc;
selectedExam.AllowBackStep = viewmodel.AllowBackStep;
selectedExam.Attempts = viewmodel.Attempts;
selectedExam.RandomizeAnswerOrder = viewmodel.RandomizeAnswerOrder;
selectedExam.exam_question = viewmodel.exam_question; // DUPLICATES PROPS
// Save
db.SaveChanges();
return Ok(examToSave);
}
}

C# EF add data to model

I have those models
class Artist
{
public int Id { get; set; }
[StringLength(500)]
public string Name { get; set; }
[StringLength(50)]
public string LastName { get; set; }
public virtual ICollection<SimilarArtist> SimilarArtists { get; set; }
}
class SimilarArtist
{
public int Id { get; set; }
public int ArtistId { get; set; }
[ForeignKey("ArtistId")]
public Artist Artist { get; set; }
public int Similar_Artist_Id { get; set; }
}
So each artist have links to other 5 from the same table. When the migration generate database it made that stracture.
SELECT [Id]
,[Name]
,[LastName]
FROM [dbo].[Artists]
SELECT [Id]
,[ArtistId]
,[Similar_Artist_Id]
FROM [dbo].[SimilarArtists]
So when I do select the model it return this
var similar = _db.Artists.FirstOrDefault(x => x.Name == id).SimilarArtists.FirstOrDefault();
//similar.ArtistId
//similar.Id
//similar.Similar_Artist_Id
//similar.Artist //the object which return main artist
The question is how I can get in "var similar" not just Similar_Artist_Id but also name and lastname in the same request (without making requests by Similar_Artist_Id)
var similarId = model.SimilarArtists.FirstOrDefault().Id;
var artiest = _db.Artists.Where(x.Id = similarId);
or just:
similar.Artist.Name
Or if you want to be able to have strongly-type property such as similar.ArtistName, create a [NotMapped] getter property.
class SimilarArtist
{
public int Id { get; set; }
public int ArtistId { get; set; }
[ForeignKey("ArtistId")]
public Artist Artist { get; set; }
public int Similar_Artist_Id { get; set; }
[NotMapped]
public string ArtistName
{
get
{
return this.Artist.Name;
}
}
}
You can do
var similar = _db.Artists.Where(x => x.Name == id)
.Select(a => a.SimilarArtists.FirstOrDefault())
.FirstOrDefault();
This gives you the first SimilarArtists (with all of its properties) of the first Artists matching the predicate x.Name == id.

Categories

Resources