EF Core .ThenInclude as left join - ef-core-2.0

New to EF Core...
I have the following EF Core class setup, but I am running into a problem loading the data into a view. When all data is populated for each table everything loads as is, however if for example I have an order and order line that has no orders received only the order loads. The orderline will not load.
Classes:
public class Order {
public Guid OrderID { get; set;}
public ICollection<OrderLine> OrderLines { get; set; }
}
public class OrderLine {
public Guid OrderLineId { get; set; }
public int OrderQty { get; set; }
public Order Order { get; set; }
public Guid OrderID { get; set; }
public ICollection<OrderReceived> OrdersReceived { get; set; }
}
public class OrderReceived {
public Guid OrderReceivedID { get; set; }
public int QuantityReceived { get; set; }
public OrderLine OrderLine { get; set; }
public Guid OrderLineId { get; set; }
}
My controller looks like this:
public async Task<IActionResult> Details(Guid? id) {
if (id == null) {
return NotFound();
}
var orderDetail = await _context.Orders
.Include(ol => ol.OrderLines)
.ThenInclude(rec => rec.OrdersReceived)
.SingleOrDefaultAsync(o => o.OrderId = id);
}
Ex: Order A has 1 order line and 2 OrdersReceived. This loads perfectly.
Ex: Order B has 1 order line and no orders received. Only the Order detail loads everything below that does not load (orderline or ordersreceived). I guess I'm looking for something more like a left join for the ordersreceived. I'm just not that familiar with EF Core.
Hopefully I explained this correcly. Any help is greatly appreciated. I did find this post with a similar question: ef core linq filtered child entities. Is best practice to implement a viewmodel for this type of situation?
Thank you,
E

Related

How to make a ViewModel for an anonymous type from a linq query

I have joined and grouped 2 databases using linq in my controller, and now I need to parse it to the view. I know, I gonna need a ViewModel to do it, but I have no clue what to put in it, to make it work.
My Linq query in the controller looks like this:
var energyUsageViewModel =
from e in applicationDbContext
join u in _context.UsagesPerMonth on e.Id equals u.EnergyUsageId into u2
group u2 by new { Year = e.Year }
into u3
select u3.ToList();
return View(energyUsageViewModel);
In short what the linq query does is taking the "year" from table "e" joining it with table "u" which contains the energy usage per month, which I am gonna use to make a table in my view, which displays the year and usage per month in one row.
And currently my ViewModel looks like this (obviously not working):
public class EnergyUsageViewModel
{
public IEnumerable<UsagePerMonth> UsagePerMonthVm { get; set; }
}
The view takes a model of:
#model IEnumerable<Jullerup.Models.EnergyUsageViewModel>
I have tried to modify the ViewModel to take u3, but haven't succeeded.
I get the following invalid operation exception:
InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'System.Linq.Enumerable+SelectEnumerableIterator2[System.Linq.IGrouping2[<>f__AnonymousType101[System.String],System.Collections.Generic.IEnumerable1[Jullerup.Models.UsagePerMonth]],System.Collections.Generic.List1[System.Collections.Generic.IEnumerable1[Jullerup.Models.UsagePerMonth]]]', but this ViewDataDictionary instance requires a model item of type 'System.Collections.Generic.IEnumerable`1[Jullerup.Models.EnergyUsageViewModel]'.
How do I edit my ViewModel to handle u3?
Update:
I'm working with 3 classes.
Customer class:
public class Customer
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
//Navigation Prop
public ICollection<EnergyUsage>? energyUsage { get; set; }
}
EnergyUsage class:
public class EnergyUsage
{
public int Id { get; set; }
public int CustomerID { get; set; }
public DateTime Date { get; set; }
public int YearlyHeatUsage { get; private set; }
public List<UsagePerMonth> UsagePerYear { get; set; }
//Navigation Prop
public Customer? Customer { get; set; }
}
UsagePerMonth class:
public class UsagePerMonth
{
public int Id { get; set; }
public MonthEnum Month { get; set; }
public int Usage { get; set; }
public int HeatUsage { get; private set; }
public string EnergyType { get; set; }
private EnergyMeasurement energyType { get; set; }
public int EnergyUsageId { get; set; }
}
In the database Customer.Id (PK) has a one to many relationship to EnergyUsage.CustomerId (FK) and EnergyUsage.Id (PK) has a one to many relationship to UsagePerMonth.EnergyUsageId (FK).
What I am trying to do, is to get an object/list something I can use in the view to display all instances of UsagePerMonth grouped by Customer.Year for a certain Customer.Id.

List in Model in DB keeps on changing its order

my db looks like this:
public class BrContext:DbContext
{
public DbSet<Conversation> AllConversations { get; set; }
public DbSet<ChatReference> ChatReferences { get; set; }
public DbSet<Product> Products { get; set; }
}
My Conversation Model looks like this:
public class Conversation
{
[Key]
public int ConversationId { get; set; }
public string ConverserName { get; set; }
public List<ChatReference> AllReferences { get; set; }
public ChatReference CurrentChatReference { get; set; }
public bool IsDealtWith { get; set; }
}
My ChatReference Model looks like this:
public class ChatReference
{
public int ChatReferenceId { get; set; }
public string ChatReferenceTime { get; set; }
public string ChatReferenceContent { get; set; }
public bool IsR { get; set; }
}
as you see - I have a list {"AllReferences"} of 'CurrentChatReference' as a property in a model that is saved in the DB.
I have times in the course of debugging the project , that when i look at the values in the DB - i see that the order of the ChatReferences in the list 'AllReferences' in the latest conversation in the db has been switched around.
Does anyone have an idea of why this is happening?
When backend query is returning the results, those results are populated in the List. If you are not sending the query using OrderBy, the results are not guaranteed to always come in the same order, so the items in the List are not always in the same order. Either you retrieve the results using OrderBy clause or Sort the results after you populate the results into your model.
Entity Framework Ordering Includes
How to Sort a List<T> by a property in the object

MVC / EF Code First ICollection Objects

I am using MVC5 Code First and have a couple of classes that look like;
public class Asset
{
public int Id { get; set; }
public string FileName { get; set; }
public virtual ICollection<Category> Categories { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public int AssetId { get; set; }
public string CategoryName { get; set; }
public string Description { get; set; }
}
I would like to return a view that lists all Assets that have a particular Category. I was hoping for something along the lines of
public ActionResult ListByCategory(string categoryName)
{
var model =
from r in _db.Assets
.Where(r => r.Categories.CategoryName == categoryName)
select r;
return View(model);
}
I know I have some assets from my seed method that have categories that exist. But the compiler is saying "System.Collection.Generic.ICollection Does not contain a definition for CategoryName and no extension method could be found, am I missing a reference?" This is on my .Where line.
I don't fully understand what it's trying to tell me. I do have a reference to my Models as I can reference them elsewhere within the controller. I know that a single Asset might be in several Categories, hence I created the ICollection at the class level.
Here is my context class;
public class AssetsDb : DbContext
{
public AssetsDb() : base("DefaultConnection")
{
}
public DbSet<Asset> Assets { get; set; }
public DbSet<Category> Categories { get; set; }
}
Could somebody help my understanding on how I can get to my underlying data? I'm trying to learn EF / MVC so appreciate any help.
Thanks.
You cannot get a CategoryName from a collection of categories, you need to check the name of each category within the collection.
Try using this query instead:
var model =
from r in _db.Assets
.Where(r => r.Categories.Any(c => c.CategoryName == categoryName))
select r;

Update many-to-many relationship efficiently

I've 2 model classes.
public class Candidate
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Skill> Skills { get; set; }
}
public class Skill
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Candidate> Candidates { get; set; }
}
This creates me 3 tables.
Candidates
Id Name
1 Tom
2 John
Skills
Id Name
1 C#
2 MVC
3 SQL
4 nHibernate
5 Java
I've made the association in the AUTOGENERATED table :
CandidateSkills
CandidateId SkillId
1 2
1 3
Now I've want to update the skills for candidateId 1 i.e. Tom who wants to remove his SkillId : 2 i.e. MVC and add new SkillId : 4 & 5 i.e. nHibernate & Java respectively.
I get the set of Ids from the form i.e. 3, 4 & 5 & and the candidate Id : 1 , Since 2 is removed by the user.
My API is like this :
public Candidate Add(int candidateId, int[] skillIds)
{
var model = dbContext.candidate.Find(candidateId);
}
How do I update the candidate record in an efficient way, making the least database calls?
With many-to-many associations in Entity Framework you can only work with objects, not with primitive Id values.
public void Add(int candidateId, int[] skillIds)
{
var model = dbContext.candidate.Include(c => c.Skills)
.Single(candidateId => c.candidateId == candidateId);
var skills = dbContext.Skills.Where(s => skillIds.Contains(s.Id)).ToArray();
foreach (var skill in skills)
{
model.Skills.Add(skill);
}
dbContext.SaveChanges();
}
You must fetch the candidate with its skills loaded, hence the Include. The skills must be loaded for EF to be able to track changes to the collection.
If it's really a great deal for you to optimize performance you must create a junction class CandidateSkill class in your model, so you can add/delete associations without loading candidate or skill objects.
public class CandidateToSkill
{
public Candidate Candidate{ get; set; }
[Key,Column(Order = 0)]
public int CandidateID { get; set; }
[Key,Column(Order = 1)]
public int SkillID{ get; set; }
public Skill Skill { get; set; }
}
You need also add this class as DbSet in your dbContext class
public class Candidate
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<CandidateToSkill> CandidateSkills{ get; set; }
}
public class Skill
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<CandidateToSkill> SkillCandidates{ get; set; }
}
and the controller code
public void Add(int candidateId, int[] skillIds)
{
using (var db = new YourdbContext())
{
foreach (int skillID in skillIds )
{
db.CandidateToSkill.Add(new CandidateToSkill(){skillID,candidateId});
}
db.SaveChanges();
}
}
in this sample u have only marking a candidates to add to our context (in your Link-table) and Adding em to db with db.SaveChanges()
You could just mark the entity state as modified and save the context.
public void UpdateCandidate(Candidate candidate)
{
Context.Entry(candidate).State = EntityState.Modified;
}

LINQ to entities against EF in many to many relationship

I'm using ASP.NET MVC4 EF CodeFirst.
Need help to write LINQ (to entities) code in Index action to get collection of Courses which are attended by selected student. The relationship is many to many with join table with payload.
//StudentController
//-----------------------
public ActionResult Index(int? id)
{
var viewModel = new StudentIndexViewModel();
viewModel.Students = db.Students;
if (id != null)
{
ViewBag.StudentId = id.Value;
// *************PROBLEM IN LINE DOWN. HOW TO MAKE COURSES COLLECTION?
viewModel.Courses = db.Courses
.Include(i => i.StudentsToCourses.Where(t => t.ObjStudent.FkStudentId == id.Value));
}
return View(viewModel);
}
The error I got is:
The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
I have modeles (the third one is for join table with payload):
//MODEL CLASSES
//-------------
public class Student
{
public int StudentId { get; set; }
public string Name { get; set; }
public virtual ICollection<StudentToCourse> StudentsToCourses { get; set; }
}
public class Course
{
public int CourseId { get; set; }
public string Title { get; set; }
public virtual ICollection<StudentToCourse> StudentsToCourses { get; set; }
}
public class StudentToCourse
{
public int StudentToCourseId { get; set; }
public int FkStudentId { get; set; }
public int FkCourseId { get; set; }
public string Classroom { get; set; }
public virtual Student ObjStudent { get; set; }
public virtual Course ObjCourse { get; set; }
}
Then, here is modelview I need to pass to view
//VIEWMODEL CLASS
//---------------
public class StudentIndexViewModel
{
public IEnumerable<Student> Students { get; set; }
public IEnumerable<Course> Courses { get; set; }
public IEnumerable<StudentToCourse> StudentsToCourses { get; set; }
}
EF does not support conditional include's. You'll need to include all or nothing (ie no Whereinside the Include)
If you need to get the data for just certain relations, you can select it into an anonymous type, something like (the obviously untested);
var intermediary = (from course in db.Courses
from stc in course.StudentsToCourses
where stc.ObjStudent.FkStudentId == id.Value
select new {item, stc}).AsEnumerable();
Obviously, this will require some code changes, since it's no longer a straight forward Course with a StudentsToCourses collection.

Resources