One to many linq Eager load - c#

I am working with the following technologies: C#, SQL Server, ASP.NET and Entity Framework and Linq.
I have a many to many relation , using eager load. I want to get all the courses where a student is inscribed. As you can see I have a one to many relation from student to inscribe table.
The model classes:
public class Courses
{
[Required]
public int Id { get; set; }
//more properties here
public student stud { get; set; }
}
public class Enroll
{
[Key]
public intId { get; set; }
//properties here
[Required]
public string StudentId{ get; set; }
public Courses Courses{ get; set; }
}
public class student{
public intId { get; set; }
//other properties
public Inscripe Inscription {get;set}
}
This is what my controller:
public IEnumerable<course> GetCoursesStudent(Int studentId)
{
//some code here to validate
var result = _dbContext
.Enroll.Include(c => c.Courses)
.Where(c => c.StudentId == studentId)
.SelectMany(c => c.Courses).ToList();
}
problem :
I receive an error from the SelectMany: the type aregument for method Queribly.selectMany(IQueryableExpression>> can not be infered from the usage.
How can I fix it?

The type you specified for the IEnumerable is wrong. It should be "Courses" instead of "course":
public IEnumerable<Courses> GetCoursesStudent(Int studentId)
{
var result = _dbContext
.Enroll
.Include(c => c.Courses)
.Where(c => c.StudentId == studentId)
.SelectMany(c => c.Courses)
.ToList();
}
And the "Courses" property of Enroll class should be an enumerable:
public class Enroll
{
[Key]
public intId { get; set; }
[Required]
public string StudentId { get; set; }
public IEnumerable<Courses> Courses { get; set; }
}

Related

How do I apply a Where clause to an entity that's a couple layers deep in a LINQ expression?

I'm working on a LINQ expression that will pull in related tables to a Person table we've got. The query I've written does work, but it takes a long time to run and brings back more data than I need. Here's the LINQ expression I have now:
using (var ctx = new AppEntities())
{
People = ctx.People.Where(p => p.Inactive == false)
.Include(p => p.Agency)
.Include(p => p.PersonnelCertifications.Select(pc => pc.CertificationType))
.OrderBy(p => p.LastName)
.ThenBy(p => p.FirstName)
.ToList();
}
We're working with .NET 4.5.2 and EF 6.4. The Person table has a relatonship with the PersonnelCertification table. And that has a relationship with the CertificationType table. Ideally, what I need to add is a filter so that only CertificationType.CertType == "Operator". I tried adding a Where clause after the Include of PersonnelCertifications, but that didn't work. The second Where clause was still only working with the Person table. Is there a way of doing what I want? If so, how is this done?
Here's the table definitions with extraneous fields removed for brevity:
public partial class Person
{
public Person()
{
PersonnelCertifications = new HashSet<PersonnelCertification>();
}
public long ID { get; set; }
public virtual ICollection<PersonnelCertification> PersonnelCertifications { get; set; }
}
public partial class PersonnelCertification
{
public long ID { get; set; }
public long CertificationTypeID { get; set; }
public long PersonID { get; set; }
public virtual CertificationType CertificationType { get; set; }
public virtual Person Person { get; set; }
}
public partial class CertificationType
{
public CertificationType()
{
PersonnelCertifications = new HashSet<PersonnelCertification>();
}
public long ID { get; set; }
[Required]
[StringLength(30)]
public string CertType { get; set; }
public virtual ICollection<PersonnelCertification> PersonnelCertifications { get; set; }
}
.Where(p => p.PersonnelCertifications.Any(pc => pc.CertificationType == "Operator")) should give you the people you are looking for.

Entity Framework Core - Call A Populate Extension Method Inside Include

I will explain my issue using an example.
Lets say I have this following classes and methods (I created them only for this example)
public class Student
{
public string Name { get; set; }
public string Id { get; set; }
public Subject Expertise { get; set; }
}
public class Subject
{
public string Name { get; set; }
public Teacher Teacher { get; set; }
}
public class Teacher
{
public string Name { get; set; }
public string LicenseId{ get; set; }
public License License { get; set; }
}
public class License
{
public string LicsenseType;
}
public static IQueryable<Subject> PopulateWithTeacherAndLicense(this IQueryable<Subject> subjects)
{
return subjects
.Include(c => c.Teacher)
.ThenInclude(p => p.License);
}
Now lets assume I want to select all students with all their subject,teacher and license. In order to do so, I want to use my PopulateWithTeacherAndLicense. I want the query to look something like:
db.Students.Include(s => s.Expertise.PopulateWithTeacherAndLicense())
And not have to do Include(s=>s.Expertise).TheInclude(s => s.Teacher)...
You can create extension method for Student collection itself
public static IQueryable<Student> IncludeExpertise(this IQueryable<Student> students)
{
return students
.Include(s => s.Expertise)
.ThenInclude(c => c.Teacher)
.ThenInclude(p => p.License);
}

How to solve Code First from database many-to-many mapping in Entity Framework?

Bellow code snippet showing my scenario:
[Table("User")]
public partial class UserModel
{
public UserModel()
{
UserRole = new HashSet<UserRoleModel>();
}
public int UserID { get; set; }
public string FullName { get; set; }
public virtual ICollection<UserRoleModel> UserRole { get; set; }
}
[Table("UserRole")]
public partial class UserRoleModel
{
public UserRoleModel()
{
User = new HashSet<UserModel>();
}
public int RoleID { get; set; }
public string RoleName { get; set; }
public virtual ICollection<UserModel> User { get; set; }
}
Now within OnModelCreating(DbModelBuilder modelBuilder) EF Generate code like bellow
modelBuilder.Entity<UserModel>()
.HasMany(e => e.UserRole)
.WithMany(e => e.User)
.Map(m => m.ToTable("UserRoleMapping").MapLeftKey("UserID").MapRightKey("UserRoleID"));
now this is fine add / insert data into UserRoleMapping table. But how to
Get / Update data from UserRoleMapping table ?
I try to solve this issue following create-code-first-many-to-many the post and come-up with third class with join entity
public partial class UserRoleMappingModel
{
[Key, Column(Order = 0)]
public Guid UserId { get; set; }
public UserModel User { get; set; }
[Key, Column(Order = 1)]
public int RoleId { get; set; }
public UserRoleModel UserRole { get; set; }
}
then add public virtual ICollection<UserRoleMappingModel> UserRoleMapping { get; set; } in both the UserModel and UserRoleModel class
But when I try to GET value from database using bellow code
var results = _userRepository.GetAll()
.Include(r => r.UserRoleMapping
.Select(s => s.UserRole))
.SingleOrDefault(e => e.ID == id);
It throws ERROR
"An error occurred while executing the command definition. See the
inner exception for details.System.Data.SqlClient.SqlException
(0x80131904): Invalid object name 'dbo.UserRoleMappingModel'.\r\n
Even I tried bellow Configuration within OnModelCreating, but nothing work as expected
modelBuilder.Entity<UserRoleMappingModel>()
.HasKey(e => new { e.UserId, e.RoleId });
Your class UserRoleMappingModel has no Table-Attribute. Bcause of this, EF searches for a Table UserRoleMappingModel instead von UserRoleMapping.
You have to choose: Either map the n-to-n relationship and don't access the mapping-table or load the table to access the values in it.
As workaround you could implement a Not-Mapped column:
[Table("User")]
public partial class UserModel
{
public UserModel()
{
UserRole = new HashSet<UserRoleModel>();
}
public int UserID { get; set; }
public string FullName { get; set; }
public virtual ICollection<UserRoleMappingModel> Mappings { get; set; }
public virtual ICollection<UserRoleModel> UserRole
{
get
{
return this.Mappings.Select(s => s.UserRole);
}
}
}
AS per GertArnold response I solve the issue in bellow way.
1st Remove below settings
modelBuilder.Entity<UserModel>()
.HasMany(e => e.UserRole)
.WithMany(e => e.User)
.Map(m => m.ToTable("UserRoleMapping").MapLeftKey("UserID").MapRightKey("UserRoleID"));
2nd Add bellow settings
modelBuilder.Entity<UserRoleMappingModel>()
.HasKey(e => new { e.UserId, e.RoleId });
3rd add table property in Mapping Model
[Table("UserRoleMapping")]
public partial class UserRoleMappingModel
{
[Key, Column(Order = 0)]
public Guid UserId { get; set; }
public UserModel User { get; set; }
[Key, Column(Order = 1)]
public int RoleId { get; set; }
public UserRoleModel UserRole { get; set; }
}
4th Create a Mapping Repository
IUserRoleMappingRepository
5th a simple get Method (Problem Solved)
var results = _userRoleMappingRepository.SearchFor(e => e.UserId == id)
.Select(s => new
{
s.UserId,
s.UserRoleId,
s.UserRole.RoleName
})
.FirstOrDefault();
Point to be noted : using bellow query I able to get result but unable to serialize with Newtonsoft.Json due to self referencing issue
var results = _userRepository.GetAll()
.Include(r => r.UserRoleMapping
.Select(s => s.UserRole))
.SingleOrDefault(e => e.ID == id);
Try bellow JsonSerializerSettingssetting alternatively but unable to serialize sucessfully
PreserveReferencesHandling = PreserveReferencesHandling.All / Object
ReferenceLoopHandling = ReferenceLoopHandling.Serialize / Ignore

How to write eagerly loading query on many to many relation with additional filtering and sorting

I need to create a eager loading query that will perform these tasks:
Get parent Entity by id
Filter child entities by criteria
Sort child list of entities
My non-eager query looks like this:
var company = _dbContext.Companies.FirstOrDefault(c => c.Id == companyId);
if (company != null)
{
company.CompanyProducts =
company.CompanyProducts
.Where(cp => cp.IsBuyable && cp.Product.IsPublished)
.OrderByDescending(c => c.Product.PublishDate)
.ThenBy(c => c.Product.Name)
.ToList();
}
return company;
Where Entities have this structure:
public class Company
{
public long Id { get; set; }
public string Name { get; set; }
[ForeignKey("CompanyId")]
public virtual ICollection<CompanyProduct> CompanyProducts { get; set; }
}
public class CompanyProdcut
{
public long Id { get; set; }
public long CompanyId { get; set; }
public long ProductId { get; set; }
public bool IsBuyable { get; set; }
public virtual Company Company { get; set; }
public virtual Product Product { get; set; }
}
public class Product
{
public long Id { get; set; }
public string Name { get; set; }
public DateTime PublishDate { get; set; }
public bool IsPublished { get; set; }
[ForeignKey("ProductId")]
public virtual ICollection<CompanyProduct> CompanyProducts { get; set; }
}
public class MyDbContext : DbContext
{
public MyDbContext() : base("name=connectionString")
{
Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
}
public virtual IDbSet<Product> Products { get; set; }
public virtual IDbSet<Company> Companies { get; set; }
public virtual IDbSet<CompanyProduct> CompanyProducts { get; set; }
}
This is classic many to many relation that uses an entity in the middle to store addtional data.
Company -|---------|< CompanyProduct >|---------|- Product
How in this case I can rewrite the non-eager query to eager query, that will do everything in one SQL call? I tried to do it myself using the .Include() but I failed.
Any ideas?
I wouldn't consider it the best approach but you can have a .Include() statement in the two positions I have shown below. If you include it in the first query then I think you'd be fine to use your method for the second one but I havn't tested to not certain about that.
The reason why you can't use it in the positions in your example is becasue it can only be used on a IQueryable() object, of which Company and ICollection is not.
var _dbContext = new MyDbContext();
var company = _dbContext.Companies.Include("").SingleOrDefault(c => c.Id == companyId);
var products = _dbContext.Companies.Include("").SingleOrDefault(c => c.Id == companyId)
?.CompanyProducts
.Where(cp => cp.IsBuyable && cp.Product.IsPublished)
.OrderByDescending(c => c.Product.PublishDate)
.ThenBy(c => c.Product.Name)
.ToList();
if(company != null)
{
company.CompanyProducts = products;
}
return company;
var company = _dbContext.Companies.Include(x => x.CompanyProducts).Includex => x.CompanyProducts.Select(y => y.Company).Includex => x.CompanyProducts.Select(y => y.Product).FirstOrDefault(c => c.Id == companyId);
if (company != null)
{
company.CompanyProducts =
company.CompanyProducts
.Where(cp => cp.IsBuyable && cp.Product.IsPublished)
.OrderByDescending(c => c.Product.PublishDate)
.ThenBy(c => c.Product.Name)
.ToList();
}
return company;
To get the lambda include make sure to add reference to System.Data.Entity.
https://msdn.microsoft.com/en-us/library/dn176380(v=vs.113).aspx

Equivalent of 'WHERE columnname IN' SQL syntax in Entity Framework

I have this scenario
public class TimeSheet
{
[Key]
public int TimeSheetID { get; set; }
public string Username { get; set; }
}
public class Approval
{
[Key]
public int ApprovalID { get; set; }
[Index(IsUnique = true)]
[StringLength(450)]
public string Approver { get; set; }
public virtual ICollection<ApprovalDetail> Details { get; set; }
}
public class ApprovalDetail
{
[Key]
public int ApprovalDetailID { get; set; }
[StringLength(450)]
public string Username { get; set; }
}
I want to the following syntax in EF.
SELECT
*
FROM
TimeSheet
WHERE
UserName IN (SELECT
[AD].Username
FROM
Approval [A]
INNER JOIN
ApprovalDetail [AD] ON [A].ApprovalID = [AD].ApprovalID
WHERE
[A].Approver = 'warheat1990')
How to achieve this?
UPDATE :
My Repo
public IEnumerable<TimeSheet> List()
{
return _timeSheet.AsEnumerable().ToList();
}
public IEnumerable<TimeSheet> ListByUsername(string username)
{
return _timeSheet.Where(w => w.Username == username).ToList();
}
This should do it:
var usernamesByApprover = approvals
.Where(a => a.Approver == "warheat1990")
.SelectMany(a => a.Details.Select(d => d.Username));
var timesheetsByApprover = timesheets
.Where(t => usernamesByApprover.Contains(t.Username));
Note that even if the query is split into two expressions, Entity Framework will convert it into a single SQL query once you evaluate the timesheetsByApprover variable because of deferred execution.

Categories