Unable to populate an ICollection with related data in Entity Framework - c#

I have tables with created using below models in Entity Framework
public class User
{
public int Id{ get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
public ICollection<AssigneeMonth> AssigneeMonths { get; set; }
}
public class AssigneeMonth
{
public int Id { get; set; }
public int AssigneeId { get; set; }
public Month Month { get; set; }
public User Assignee { get; set; }
}
public class ProjectAssignee
{
public int Id { get; set; }
public int ProjectId { get; set; }
public int AssigneeId { get; set; }
public bool IsActive { get; set; }
public AutomationProject Project { get; set; }
[ForeignKey("AssigneeId")]
public User User { get; set; }
}
I am trying to get data into the collection AssigneeMonths from AssigneeMonth using this code:
var assn = dataContext.ProjectAssignees
.Where(r => r.Project.Name == project.Name && r.IsActive)
.Include(u => u.User)
.ToList();
But AssigneeMonths collection in the above assn is always null even if I have data in AssigneeMonth for the user
May I know what's wrong with the above code?

Since you're using eager loading, you're only loading the information for user, not its navigational properties.
You can use the code in this answer, which applied to your case will look like this:
var assn = dataContext.ProjectAssignees
.Where(r => r.Project.Name == project.Name && r.IsActive)
.Include(u => u.User.SelectMany(u => u.AssigneeMonths))
.ToList();
Since AssigneeMonths is a collection, you need to use SelectMany instead of Select.
Other option would be to do it like this (as posted in other answers in that link):
var assn = dataContext.ProjectAssignees
.Where(r => r.Project.Name == project.Name && r.IsActive)
.Include(u => u.User)
.Include("User.AssigneeMonths")
.ToList();
But I personally don't like the second method because its easy to make mistakes with the string and it will hide the errors until runtime.

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.

How to use Linq in C# to select a specific string from multiple nested columns?

I have a small problem here. I have the following tables with their relations :
Building
Batteries
Columns
Elevators
A building can have many batteries, batteries can have many columns, columns can have many elevators.
A battery has one building, a column has one battery, an elevator has one column.
If I were to do this var myintervention = _context.buildings.Where(b => b.batteries.Any(ba => ba.status == "Intervention")).ToList(); it would work perfectly fine in my query to return a list of the buildings that have batteries with the intervention status (status is a column).
The problem is that I can't do something like _context.buildings.Where(c => c.columns.Any...) because the building model doesn't have access to the column class but the battery does... Same goes for elevators, buildings don't have access to elevators, columns do.
Here's how I defined my modelBuilder :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Battery>()
.HasOne(p => p.buildings)
.WithMany(b => b.batteries)
.HasForeignKey(p => p.building_id);
modelBuilder.Entity<Column>()
.HasOne(p => p.batteries)
.WithMany(b => b.columns)
.HasForeignKey(p => p.battery_id);
modelBuilder.Entity<Elevator>()
.HasOne(p => p.columns)
.WithMany(b => b.elevators)
.HasForeignKey(p => p.column_id);
}
Here's how my relations look in my models :
building.cs
public List<Battery> batteries { get; set; }
battery.cs
public long building_id { get; set; }
public Building buildings { get; set; }
public List<Column> columns { get; set; }
column.cs
public long battery_id { get; set; }
public Battery batteries { get; set; }
public List<Elevator> elevators { get; set; }
elevator.cs
public long column_id { get; set; }
public Column columns { get; set; }
tl;dr; I want to do a linq query to list all the buildings that have either a battery, column or elevator whose's status column is at "Intervention".
UPDATE: apparently this somehow works, but not efficiently :
var myintervention = _context.buildings.Where(a => a.batteries.SelectMany(b => b.columns.SelectMany(c => c.elevators)).Any(c => c.status == "Intervention")).ToList();
It seems as though it's not accurate? Sometimes there are batteries in intervention and the building associated with those batteries doesn't appear in the building list. Same goes for elevators or columns.. I'm a bit lost!
UPDATE 2 : here's my request :
[HttpGet("intervention")]
public List<Building> Getintervention(string status)
{
var myintervention = _context.buildings.Where(c => c.batteries.SelectMany(z => z.columns).Any(z => z.status == "Intervention")).ToList();
return myintervention;
}
First up, please fix the model so collections have plural names and objects have single, otherwise your code will become very confused:
building.cs
public List<Battery> Batteries { get; set; }
battery.cs
public long BuildingId { get; set; }
public Building Building { get; set; }
public List<Column> Columns { get; set; }
column.cs
public long BatteryId { get; set; }
public Battery Battery { get; set; }
public List<Elevator> Elevators { get; set; }
elevator.cs
public long ColumnId { get; set; }
public Column Columns { get; set; }
Now let's add some more properties to the model so it can tell us about interventions:
building.cs
public List<Battery> Batteries { get; set; }
[NotMapped]
public bool IsInIntervention => this.Status == "Intervention" || Batteries.Any(b => b.IsInIntervention);
battery.cs
public long BuildingId { get; set; }
public Building Building { get; set; }
public List<Column> Columns { get; set; }
[NotMapped]
public bool IsInIntervention => this.Status == "Intervention" || Columns.Any(c => c.IsInIntervention);
column.cs
public long BatteryId { get; set; }
public Battery Battery { get; set; }
public List<Elevator> Elevators { get; set; }
[NotMapped]
public bool IsInIntervention => this.Status == "Intervention" || Elevators.Any(e => e.IsInIntervention);
elevator.cs
public long ColumnId { get; set; }
public Column Column { get; set; }
[NotMapped]
public bool IsInIntervention => this.Status == "Intervention";
Now you can just ask a building if it IsInIntervention and it will say yes if it is or if anything it owns is
Note: if the model hasn't been loaded with entities then you might need to employ a trick like this: EF Core linq and conditional include and theninclude problem to conditionally load them
var vara = _context.maintable.Where(a => a.tablerelatedtomain.SelectMany(b => b.tablerelatedtoprevious).SelectMany(c => c.tablerelatedtoprevious).Any(c => c.status == "mystring")).ToList();
var varb = _context.maintable.Where(a => a.tablerelatedtomain.SelectMany(b => b.tablerelatedtoprevious).Any(b => b.status == "mystring")).ToList();
var varc = _context.maintable.Where(a => a.tablerelatedtomain.Any(a => a.status == "mystring")).ToList();
var result = vara.Union(varb).Union(varc).OrderBy(z => z.id).ToList();
return result;

Querying complex data types in EF Core with LINQ

I'm using EF Core and ASP.NET Core API, and I have entities that are more or less defined as follows:
public class MyUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Circle
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public ICollection<MyUser> Members { get; set; } = new List<MyUser>();
}
public class Tender
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public TenderVisibility Visibility { get; set; }
public ICollection<TenderCircle> TenderCircles { get; set; } = new List<TenderCircle>();
}
public enum TenderVisibility
{
Public,
Circles,
}
public class TenderCircle
{
public int TenderId { get; set; }
[ForeignKey("TenderId")]
public Tender Tender { get; set; }
public int CircleId { get; set; }
[ForeignKey("CircleId")]
public Circle Circle { get; set; }
}
Then, I want to get all my Tenders where a certain user is member of the Circle if the visibility is set to Circles, so I have something like this:
public IEnumerable<Tender> GetTenders(string userId)
{
var user = _context.Users.Where(u => u.Id == userId);
return _context.Tenders
.Include("TenderCircles.Circle.Members")
.Where(t => t.Visibility == TenderVisibility.Public ||
(t.Visibility == TenderVisibility.Circles && t.TenderCircles.Select(tc => tc.Circle.Members).ToList().Contains(user.ToList()))).ToList()
.ToList();
}
But that seems to me way too complicated, and it actually doesn't work as it throws error saying it is not allowed for the second part where I select circle members and check whether user is contained. Any idea how to achieve what I want more efficiently?
You can't call ToList in Where condition. You can use Any instead;
public IEnumerable<Tender> GetTenders(string userId)
{
var user = _context.Users.Where(u => u.Id == userId);
return _context.Tenders
.Include("TenderCircles.Circle.Members")
.Where(t => t.Visibility == TenderVisibility.Public ||
(t.Visibility == TenderVisibility.Circles && t.TenderCircles.Any(tc => tc.Circle.Members.Any(m=>m.Id == userId)))
.ToList();
}
I couldn't try it because it is a bit complicated to reproduce. But please let me know if there is any syntax error

Error iterating the query results

I have a context called companyContext. There are Three tables Reports,Logs and Employees.I am given a case id and I need to get a list of all the employees who belong to a certain case (and a log if there is one, but i dont need to worry about that yet). So I made a query get all the employees where EmployeeID is equal to Employee.ID, where the Reports CaseID is eaual to case.id. However, its not reutning a list, its returning a Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable}]
Am I making the query correctly ? Thanks guys.
var employees = await context.Reports.Include(s => s.Employee)
.ThenInclude(e => e.ID)
.AsNoTracking()
.Where(r => r.CaseID == Case.Id);
Models
public class Log
{
public int ID { get; set; }
public string Input { get; set; }
public string Tag { get; set; }
public DateTime LogDate { get; set; }
public ICollection<Report> Reports { get; set; }
}
public class Employee
{
public int ID { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public ICollection<Report> Reports { get; set; }
}
public class Report
{
public int ID { get; set; }
public string CaseID { get; set; }
public int EmployeeD { get; set; }
public int? LogID { get; set; }
public Employee Employee { get; set; }
public Log Log { get; set; }
}
SingleOrDefaultAsync
throws an exception if there is more than one element in the sequence.
So you should not be using SingleOrDefaultAsync if you expect multiple records.
If I'm interpreting your question properly and you want employees themselves, you could start with them and then narrow via the Employee class's .Reports navigation property.
var employees = await context.Employees.AsNoTracking()
.Where(e => e.Reports.Any(r => r.CaseID == case.Id))
.ToListAsync();
So entity is actually like using a real database. I had to do a where and select clause, but also a toList to convert it into something readable.
var employees = await context.Reports.Where(r => r.CaseID == Case.Id);
.Select(r => r.Employee)
.ToList();

How can I user LINQ to get a IList<string> from a multi-table join?

I have a SQL Server 2012 database and I am using Entity Framework 6.1 to access data.
I have the following entities that are joined to each other with Primary Key and Foreign Key relationships. What I would like to do is to get a simple collection of values of the Quid parameter in the Questions table where the taskId matches a value that is chosen outside of this code.
Task contains Objective contains ObjectiveDetail contains SubTopic contains Problems contations
Questions
I created the following LINQ statement. It seems not to work and I need some advice on what I am doing wrong and how the statement could be made to get what I need. In particular I am not sure about the way I do the join with all the many .Selects and also the select to give me the output.
var quids = db.Tasks
.Include(e => e.Objectives
.Select(o => o.ObjectiveDetails
.Select(od => od.SubTopics
.Select(s => s.Problems
.Select(p => p.Questions)))))
.Where(t => t.TaskId == taskId)
.Select(e => e.Objectives
.Select(o => o.ObjectiveDetails
.Select(od => od.SubTopics
.Select(s => s.Problems
.Select(p => p.Questions
.Select(q => q.QuestionUId))))))
.ToList();
However this gives me a very confusing output and certainly not the simple IList of Quids
that I would like. Here are my classes for reference. I removed additional fields and hopefully just left the important ones.
public class Task
{
public Task()
{
this.Objectives = new HashSet<Objective>();
}
public int TaskId { get; set; }
public virtual ICollection<Objective> Objectives { get; set; }
}
public class Objective
{
public Objective()
{
this.ObjectiveDetails = new HashSet<ObjectiveDetail>();
}
public int ObjectiveId { get; set; }
public int TaskId { get; set; }
public virtual Task Task { get; set; }
public virtual ICollection<ObjectiveDetail> ObjectiveDetails { get; set; }
}
public partial class ObjectiveDetail
{
public ObjectiveDetail()
{
this.SubTopics = new HashSet<SubTopic>();
}
public int ObjectiveDetailId { get; set; }
public int ObjectiveId { get; set; }
public virtual Objective Objective { get; set; }
public virtual ICollection<SubTopic> SubTopics { get; set; }
}
public class SubTopic
{
public SubTopic()
{
this.Problems = new HashSet<Problem>();
}
public int SubTopicId { get; set; }
public int Number { get; set; }
public int TopicId { get; set; }
public virtual ICollection<ObjectiveDetail> ObjectiveDetails { get; set; }
public virtual ICollection<Problem> Problems { get; set; }
}
public class Problem
{
public Problem()
{
this.Questions = new HashSet<Question>();
}
public int ProblemId { get; set; }
public int SubTopicId { get; set; }
public virtual SubTopic SubTopic { get; set; }
public virtual ICollection<Question> Questions { get; set; }
}
public class Question
{
public int QuestionId { get; set; }
public string QuestionUId { get; set; }
public virtual Problem Problem { get; set; }
}
Please note the many-many relationship between SubTopic and ObjectiveDetail. This I think makes it more difficult.
From what I understand of your need, you can simply start from the Question DbSet instead of the task:
var uids = db.Questions.Where(q => q.Problem.SubTopic
.ObjectiveDetails
.Any(od => od.Objective.TaskId == taskId))
.Select(q => q.QuestionUId)
.ToList();
This will take all Question which has at least one objective with the TaskId equal to taskId then project the QuestionUid and put it in a list.
Each of your outer Select calls produces a sequence for each item. This gives you nested sequences and probably a confusing ToString debug output.
What you probably meant was to use SelectMany to flatten the nested sequences.
The Include in your query cannot possibly help because you are just returning a list of strings. There is nothing to include in a string. Better remove it. Who knows what code EF generates from it.

Categories