EF: How to get "Grade" entity for "Student" having GradeId=1 - c#

I have below database structure with data,
And below are entity and dbContext classes,
public class SchoolContext : DbContext
{
public DbSet<Grade> Grades { get; set; }
public DbSet<Student> Students { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Server=(localdb)\MSSQLLocalDB;Database=SchoolDB;Trusted_Connection=True;");
}
}
public class Grade
{
public int Id { get; set; }
public string GradeName { get; set; }
public virtual ICollection<Student> Students { get; set; } = new HashSet<Student>();
}
public class Student
{
public int Id { get; set; }
public string StudentName { get; set; }
}
Now, I'm trying to get Grade entity for Student having GradeId=1. How to pass GradeId?
static void Main(string[] args)
{
var gradeId = 1;
var context = new SchoolContext();
var data = context.Grades.Include(x => x.Students.Where(how to pass gradeId here)).SingleOrDefault();
}

Seems like you are in fact trying to get Grade with Id equals to 1 and pre-load related data. So you can try something along these lines:
var data = context.Grades.Include(x => x.Students).SingleOrDefault(x => x.Id == 1);

One possible solution:
var data = context.Grades
.Join(context.Students.Where(s => s.GradeId == 1),
g => g.Id,
s => s.GradeId,
(g, s) => new { Id = s.Id, GradeName = g.GradeName }).ToList();

Related

How to return objects from many to many relation Db - entityframework

Im trying to return a List of products of a specific user by user Id, but that seems to not work
My Product class
public class Product
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
public List<User>? Users { get; set; }
}
My user class
public class User
{
[Key]
public Guid Id { get; set; }
[Required] [MaxLength(15)]
public string Username { get; set; }
[Required]
public string Name { get; set; }
public string Surname { get; set; }
public string PasswordHash { get; set; }
public string Salt { get; set; }
public List<Product>? Products { get; set; }
}
So im adding and Product to Db, this is working
And then im adding the product to Order by this method
Guid id is a user's id
public void AddProduct(Product product, Guid id)
{
var user = _context.Users.First(u => u.Id == id);
var p = _context.Products.First(p => p.Id == product.Id);
if (user.Products == null || p.Users == null)
{
user.Products = new List<Product>();
p.Users = new List<User>();
}
user.Products.Add(p);
p.Users.Add(user);
_context.SaveChanges();
}
And this also seems to work:
image of ProductUser table from db
So how can I return a List of Products which specific user have?
I've tried this:
private Order BuildOrder(Guid id)
{
var user = _context.Users.First(u => u.Id == id);
/*if (user.Products is null)
{
user.Products = new List<Product>();
}*/
var x = _context.Products.Where(p => p.Id == 1);
/*
var products = user.Products.ToList();*/
var order = new Order
{
Products = x.ToList()
};
return order;
But this is returning me null, like Adding Products is not working
Result of this method
Order class:
public class Order
{
public List<Product> Products { get; set; }
}
DbContext:
using Application.Api.Models;
using Microsoft.EntityFrameworkCore;
namespace Application.Api.Data;
public class ApplicationContext : DbContext
{
public ApplicationContext(DbContextOptions<ApplicationContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.UseSerialColumns();
modelBuilder.Entity<User>(eb =>
{
eb.HasMany(u => u.Products).WithMany(p => p.Users);
});
}
public DbSet<User> Users { get; set; }
public DbSet<Product> Products { get; set; }
}
If that's not enough informations comment what I need to add
you need to explicitly include the Products for the user
var speceficUserWithProducts = context.Users.Include(u => u.Products).FirstOrDefault(u => u.Id == id);

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

Name convention in Automapper

I know that Automapper can automatically map from:
class SomeClassA
{
public int Id { get; set; }
public B Member { get; set; }
}
class SomeClassB
{
public int Id { get; set; }
public string Name { get; set; }
}
to:
class SomeClassADto
{
public int Id { get; set; }
public int Member_Id { get; set; }
public string Member_Name { get; set; }
}
But how can I make Automapper map from my SomeClassADto to SomeClassA automatically?
No the prettiest thing in the world, but it does work. Downside is maintenance is a pain as you add properties to the DTO and class you need to update the mapping.
var config = new MapperConfiguration(x =>
{
x.CreateMap<SomeClassADto, SomeClassB>()
.ForMember(i => i.Id, i => i.MapFrom(src => src.Member_Id))
.ForMember(i => i.Name, i => i.MapFrom(src => src.Member_Name));
x.CreateMap<SomeClassADto, SomeClassA>()
.AfterMap((s, d, r) => d.Member = r.Mapper.Map<SomeClassB>(s));
});
IMapper mapper = config.CreateMapper();
var foo = mapper.Map<SomeClassA>(new SomeClassADto() { Id = 1, Member_Id = 2, Member_Name = "Name" });

Foreign Key Binding issue

Hi I have a list of people that also have a List of contacts.
I'm trying to link my Contact Methods so that it'll be populated with the correct Person_Id
Example
Id Name Method Value Person_Id
1 John Phone 7777777777 2
2 Joan Phone 8888888888 8
3 Jack Phone 9999999999 9
Currently it displays Person_Id as all nulls, I believe I didn't create my ContactMethod Class correctly. If I can get help establishing a proper foreign key. I think that's my issue.
// Primary Keys -------------------------------------------------------
public int Id { get; set; }
// Associations -------------------------------------------------------
public virtual Person Person { get; set; }
// Fields -------------------------------------------------------------
public string Name {get; set;
public string Value { get; set; }
I populate the information through a migration script, here's a snippet
var person = people.Find(x => x.ContactBase_GUID == contactBaseGuid);
contactMethods.AddRange(channels.Select(pair => new { pair, method = reader.Get<string>(pair.Key) })
.Where(x => x.method != null)
.Select(x => new ContactMethod { Value = x.method, Person = person }));
Working Method not utilizing foreign keys.
ContactMethod Class
// Associations -------------------------------------------------------
public int? Person_Id { get; set; }
MigrationScript
var person = people.Find(x => x.ContactBase_GUID == contactBaseGuid);
contactMethods.AddRange(channels.Select(pair => new { pair, method = reader.Get<string>(pair.Key) })
.Where(x => x.method != null)
.Select(x => new ContactMethod { Value = x.method, Person = person.Id }));
I'm going to suppose that you have a model like this:
public class Contact
{
public int Id { get; set; }
public virtual Person Person { get; set; }
public string Name { get; set; }
public string Value { get; set; }
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Contact> Contacts { get; set; }
}
Now,to achieve the escenario that you want, you need to configure a one-to many relationship between Contact and Person. There are two ways to do that, using Data Annotations or using Fluent Api. I'm going to use Fluent Api in this case. The easy way is override the OnModelCreating method of your Context to configure the relationship, for example, at this way:
public class YourContext : DbContext
{
//...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.HasMany(p => p.Contacts)
.WithRequired(c => c.Person)
.Map(f => f.MapKey("Person_Id"));
}
}
As you can see, I'm specifying a PK that is not declared in your Contact class, that is the escenario that you want. Now with this configuration, you could do this, for example:
Person john=new Person(){Name = "John"};
var contactList = new List<Contact>() {new Contact(){Name = "Phone",Value = "555-444-333",Person = john},
new Contact() { Name = "email", Value = "john#gmail.com",Person = john}};
using (YourContext db = new YourContext())
{
db.Contacts.AddRange(contactList);
db.SaveChanges();
}
Update
If you want to do the same configuration using Data Annotations, your model would be like this:
public class Contact
{
[Key]
public int Id { get; set; }
[InverseProperty("Contacts")]
public virtual Person Person { get; set; }
public string Name { get; set; }
public string Value { get; set; }
}
public class Person
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Contact> Contacts { get; set; }
}
If you want to use a FK related to Person in your Contact class, you could do this:
public class Contact
{
[Key]
public int Id { get; set; }
public int? Person_Id { get; set; }
[ForeignKey("Person_Id"), InverseProperty("Contacts")]
public virtual Person Person { get; set; }
public string Name { get; set; }
public string Value { get; set; }
}
This way you can use directly the Person_Id FK in your second migration script:
var person = people.Find(x => x.ContactBase_GUID == contactBaseGuid);
contactMethods.AddRange(channels.Select(pair => new { pair, method = reader.Get<string>(pair.Key) })
.Where(x => x.method != null)
.Select(x => new ContactMethod { Value = x.method, Person_Id = person.Id }));

Categories

Resources