LINQ - Left Join Including ThenIncluding Children not working - c#

I'm using Entity Framework Core 2.0.2 in an ASP.NET Core WebAPI project.
I have the following entities :
NotificationReported.cs :
public class NotificationReported
{
public NotificationReported();
public int Id { get; set; }
public Guid Guid { get; set; }
public Guid PlantId { get; set; }
public ICollection<Photo> Photo { get; set; }
public ICollection<Response> Response { get; set; }
}
Photo.cs:
public class Photo
{
public Photo();
public int Id { get; set; }
public string Name { get; set; }
public string Status { get; set; }
public int NotificationReportedId { get; set; }
public NotificationReported NotificationReported { get; set; }
}
Response.cs:
public class Response
{
public Response();
public int Id { get; set; }
public int QuestionId { get; set; }
public int NotificationReportedId { get; set; }
public string Value { get; set; }
public NotificationReported NotificationReported { get; set; }
public Question Question { get; set; }
}
I want to do a LEFT JOIN and I want to include then children. Here's what I wrote:
var tableNotifReported = _myNotifContext.NotificationReported
.Include(nr => nr.Response)
.ThenInclude(res => res.Question)
.ThenInclude(question => question.QuestionTypeRef)
.Include(nr => nr.Photo);
And Here's the LINQ:
var query = (from notifReported in tableNotifReported
join response in _myNotifContext.Response
on notifReported.Id equals response.NotificationReportedId into joinResponse
from res in joinResponse.DefaultIfEmpty()
join question in _myNotifContext.Question
on res.QuestionId equals question.Id into joinQuestion
from ques in joinQuestion.DefaultIfEmpty()
join notifTemplate in _myNotifContext.NotificationTemplate
on notifReported.TemplateId equals notifTemplate.Guid
select new NotificationsReportedQuery
{
NotificationReported = notifReported,
Response = res,
Question = ques,
NotificationTemplate = notifTemplate
})
.Where(predicate)
.Select(nrq => nrq.NotificationReported)
.Distinct();
After calling ToListAsync(), my list of objects doesn't have his children => Response (Count = 0) and Photo (Count = 0)
But when I use the usual JOIN (Inner Join) my children are here.
What's missing ?
That's the code with INNER JOIN that's working but I need a LEFT JOIN because in this context a NotificationReported may not have a Response answered by the user, so the INNER JOIN would not report this kind of notifs:
var query = (from notifReported in tableNotifReported
join response in _myNotifContext.Response
on notifReported.Id equals response.NotificationReportedId
join question in _myNotifContext.Question
on response.QuestionId equals question.Id
join notifTemplate in _myNotifContext.NotificationTemplate
on notifReported.TemplateId equals notifTemplate.Guid
select new NotificationsReportedQuery
{
NotificationReported = notifReported,
Response = response,
Question = question,
NotificationTemplate = notifTemplate
})
.Where(predicate)
.Select(nrq => nrq.NotificationReported)
.Distinct();
EDIT:
That's what I have with LINQ LEFT JOIN (which does'not include its children):
And that's what I have with the classic INNER JOIN (for the same notif it includes the children):

Related

C# convert sql query into EF dbcontext linq to entities query

I have an sql server database and I use EF 6.0 to access it in my app.
I have the following SQL Query I need to convert to dbcontext linq to entities query and have some damn hard time figure it out.
This is the query:
select
PA.Number,
PA.Name,
PR.*
from MYDBNAME.dbo.Product PR
join MYDBNAME.dbo.Order OD on PR.Id = OD.Id
join MYDBNAME.dbo.Payment PA on OD.Id = PA.Id
where PR.Year = 2017
and PR.StatusId = (select CD.Id from Code CD where CodeId = (select ST.Id
from Status ST where ST.Value = 'Done')
and CD.State = 'Completed')
and PA.Created = '2018-12-10'
and PR.Amount <= 500
class Product
{
public string Id { get; set; }
public string Name { get; set; }
public decimal Amount { get; set; }
public string StatusId { get; set; }
public int Year {get; set;}
}
class Order
{
public string Id { get; set; }
}
class Payment
{
public string Id { get; set; }
public DateTime Created { get; set; }
public decimal Amount { get; set; }
public string Number { get; set; }
public string Name { get; set; }
}
class Status
{
public string Id { get; set; }
public string Value { get; set; }
}
class Code
{
public string Id { get; set; }
public string CodeId { get; set; }
public string State { get; set; }
}
As State and Code classes are not related with the rest, I guess that subquery should be run separately and then issue another dbcontext query for the main query
Your SQL equivalent LINQ query is,
string statusValue = "Done";
string codeState = "Completed";
DateTime paDate = DateTime.ParseExact("2018-12-10", "yyyy-MM-dd", new CultureInfo("en-US", true));
int year = 2017;
decimal amount = 500;
var result = (from PR in context.Products
join OD in context.Orders on PR.Id equals OD.Id
join PA in context.Payments on OD.Id equals PA.Id
let codeId = (from ST in context.Status where ST.Value == statusValue select ST.Id).FirstOrDefault()
let statusId = (from CD in context.Codes where CD.Id == codeId && CD.State == codeState select CD.Id).FirstOrDefault()
where PR.Year == year
&& PR.StatusId == statusId
&& PA.Created == paDate
&& PR.Amount <= amount
select new
{
Number = PA.Number,
Name = PA.Name,
PR = PR
}).GroupBy(x => x.Number).Select(x => x.First()).ToList();

LINQ data from view model using nested SQL [duplicate]

This question already has answers here:
LEFT JOIN in LINQ to entities?
(7 answers)
Closed 4 years ago.
I can access the data using the following TSQL:
select Sweets.*, Qty
from Sweets
left join (select SweetID, Qty from carts where CartID = '7125794e-38f4-4ec3-b016-cd8393346669' ) t
on Sweets.SweetID = t.SweetID
But I am not sure of how to achieve the same results on my web application. Does anyone know how this could be achievable using LINQ?
So far i have:
var viewModel = new SweetViewModel
{
Sweet = db.Sweets.Where(s => db.Carts.Any(c => c.SweetID == s.SweetID))
};
Edit: Sorry I should of specified that I am using a View model of the 2 classes:
View model:
public class SweetViewModel
{
public IEnumerable<Sweet> Sweet { get; set; }
public IEnumerable<Cart> Cart { get; set; }
//public Cart OrderQty { get; set; }
}
public class Sweet
{
public int SweetID { get; set; }
public int CategoryID { get; set; }
public Category Category { get; set; }
public string SweetName { get; set; }
public bool Singular { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public virtual ICollection<Cart> Carts { get; set; }
}
public class Cart
{
[Key]
public int RecordID { get; set; }
public string CartID { get; set; }
public int SweetID { get; set; }
public int PemixID { get; set; }
public int Qty { get; set; }
public System.DateTime DateCreated { get; set; }
public Sweet Sweet { get; set; }
public PreMix PreMix { get; set; }
}
The following will work
from sweet in db.Sweets
join cart in db.Carts
on sweet.SweetID equals cart.SweetID into swct
from sc in swct.DefaultIfEmpty()
select new { Sweet = sweet, Qty = sweet.Key == sc.Key ? sc.Qty : 0 }
var res=(from sweet in db.Sweets
join cart in db.Carts.Select(x=>new{x.SweetID,x.Qty})
on sweet.SweetID equals cart.SweetID
into r11
from r1 in r11.DefaultIfEmpty()
select new {sweet,r1})
.Select(x=>new
{
Sweet=x.sweet,
Qty=x.r1?.Qty
})
.ToList();
This will get you the equivalent result to your sql query.
res will be List<a> where a is anonymous class and it's structure will be
{Sweet,Qty}.
You should be using the LINQ join function.
For my example, I have also used an altered version of your SQL query which I believe to be identical:
SELECT sweets.*, carts.Qty
FROM sweets LEFT JOIN carts ON sweets.SweetID = carts.SweetID
WHERE carts.CartID = '7125794e-38f4-4ec3-b016-cd8393346669'
Then I translated this new query to LINQ with the JOIN function.
var cartId = '7125794e-38f4-4ec3-b016-cd8393346669'
var query = db.Sweets // table in the "from" statement
.GroupJoin(db.Carts, // all carts for that sweet will be joined into [sweets -> Cart[]]
cart => cart.SweetID, // the first part of the "on" clause in an sql "join" statement
sweet => sweet.SweetID, // the second part of the "on" clause)
(sweet, carts) => new { Sweet = sweet, Carts = cart }) // create new compound object
.SelectMany(
sweetsCarts => sweetsCart.Carts.DefaultIfEmpty(), //show the sweet even if there is no cart
(x,y) => new { Sweet = x.Sweet, Cart = y });
.Where(sweetsCart => sweetsCart.Cart.CartID == cartId); // restrict your cartID
Basically, the Join function makes a list of compound objects that contain a Sweet object and a Cart object with each list entry, hence why you can access sweetsCart.Cart.CartID or sweetsCart.Sweets.SweetID.
The name on the left side of the => can be anything you want by the way, it's just an identifier for LINQ. I chose to call it "sweetsCart".
The GroupJoin with the SelectMany makes it possible to do a Left Join.

LINQ - nested GroupJoin

I have looked at many LINQ examples on how to do GroupJoin. But I am not sure how to perform nested GroupJoin. Does somebody have any idea?
I have a following simple classes:
public class Subject
{
public int SubjectID { get; set;}
public string Name { get; set; }
}
public class SubjectStudent
{
public int SubjectStudentID { get; set; }
public int SubjectID { get; set; }
public int StudentID { get; set; }
}
public class StudentGrade
{
public int StudentGradeID { get; set;}
public int StudentID { get; set; }
public int GradeID { get; set; }
}
var subjects = (from s in Subject
join ss in SubjectStudent on s.SubjectID equals ss.SubjectID into SubjectStudents
select new
{
SubjectID = s.SubjectID,
Students = SubjectStudents
})
.ToList();
foreach (var subject in subjects)
{
foreach(var student in subject.Students)
{
//foreach(var grade in student.Grades)
//{
// I want to get grades for each subject.Students
//}
}
}
Can I have another GroupJoin after SubjectStudents, i.e. StudentGrades? I want to be able to iterate over StudentGrades in each subject.Students.
Thank you for any help.
Your data structure looks a bit confusing to me. Also, not sue if this is what you expect:-
var result = (from s in subjects
join ss in subjectStudents on s.SubjectID equals ss.SubjectID into SubjectStudents
select new
{
SubjectID = s.SubjectID,
Students = from ss in SubjectStudents
join g in studentsGrade on ss.StudentID equals g.StudentID
select new
{
StudentId = ss.StudentID,
GradeId = g.GradeID
}
})
.ToList();
Sample Fiddle

Entity Framework to json - grouping data

I have a class named Client which looks like this:
public class Client
{
[Key, ForeignKey("BaseAssignments")]
public int ClientId { get; set; }
public string Owner { get; set; }
public string CompanyName { get; set; }
public virtual ICollection<BaseAssignment> BaseAssignments { get; set; }
}
And a class named Base looking like this:
public class Base
{
[Key, ForeignKey("BaseAssignments")]
public int BaseId { get; set; }
public string BaseName { get; set; }
public DateTime BaseStart { get; set; }
public DateTime BaseEnd { get; set; }
public virtual ICollection<BaseAssignment> BaseAssignments { get; set; }
}
They are to be joined with another class called BaseAssignment:
public class BaseAssignment
{
[Key]
public int BaseAssignmentId { get; set; }
public int BaseId { get; set; }
public int ClientId { get; set; }
public virtual Base Base { get; set; }
public virtual Client Client { get; set; }
}
The idea is that a client can be assigned to many bases, and one base can contain many clients.
Moving forward, I am trying to serialize base entitites in such way that a json representation of a base would have a collection of all it's clients as a subobject. A Web Api method that I'm trying to achieve this is:
db.Configuration.ProxyCreationEnabled = false;
var query = from b in db.Bases
group b by b.BaseId into nb
join ba in db.BaseAssignments on nb.FirstOrDefault().BaseId equals ba.BaseId
join c in db.Clients on ba.ClientId equals c.ClientId
select new BaseDTO
{
BaseName = nb.FirstOrDefault().BaseName,
BaseStart = nb.FirstOrDefault().BaseStart,
BaseEnd = nb.FirstOrDefault().BaseEnd,
Clients = from c1 in db.Clients select new ClientDTO
{
ClientId = c1.ClientId,
CompanyName = c1.CompanyName,
Owner = c1.Owner
}
};
return query;
where a BaseDTO looks like:
public class BaseDTO
{
public String BaseName { get; set; }
public DateTime BaseStart { get; set; }
public DateTime BaseEnd { get; set; }
public IQueryable<ClientDTO> Clients { get; set; }
}
and ClientDTO looks like:
public class ClientDTO
{
public int ClientId { get; set; }
public string Owner { get; set; }
public string CompanyName { get; set; }
}
As of now I'm getting an error stating that ClientDTO is an unexpected type. What can I do to fix this, or maybe the way that I've chosen is completely wrong? Thanks in advance for any insight on this.
EDIT
I've made some changes to the Web Api controller method, so it looks like:
db.Configuration.ProxyCreationEnabled = false;
var query = from b in db.Bases
group b by b.BaseId into nb
join ba in db.BaseAssignments on nb.FirstOrDefault().BaseId equals ba.BaseId
join c in db.Clients on ba.ClientId equals c.ClientId
select new BaseDTO
{
BaseName = nb.FirstOrDefault().BaseName,
BaseStart = nb.FirstOrDefault().BaseStart,
BaseEnd = nb.FirstOrDefault().BaseEnd,
Clients = new ClientDTO
{
ClientId = c.ClientId,
CompanyName = c.CompanyName,
Owner = c.Owner
}
};
return query;
This makes the Api produce a JSON, but it still contains a single object for every client, not every base.
You shouldn't have to group or join anything by hand for this, just use a sub-select and have LINQ to the heavy lifting.
from b in db.Bases
select new BaseDTO
{
BaseName = b.BaseName,
BaseStart = b.BaseStart,
BaseEnd = b.BaseEnd,
Clients =
from ba in b.BaseAssignments
from c in ba.Client
select new ClientDTO
{
ClientId = c.ClientId,
CompanyName = c.CompanyName,
Owner = c.Owner
}
}
Based on StriplingWarrior's suggestion, mixing best of both worlds into:
from b in db.Bases
select new BaseDTO
{
BaseName = b.BaseName,
BaseStart = b.BaseStart,
BaseEnd = b.BaseEnd,
Clients =
from ba in b.BaseAssignments
join c in db.Clients on ba.ClientId equals c.ClientId
select new ClientDTO
{
ClientId = c.ClientId,
CompanyName = c.CompanyName,
Owner = c.Owner
}
};
got the JSON I wanted - thanks.

T-SQL to LINQ Query (Many to Many filter)

I have Enquiries that have a Many to 1 with Clients. Enquiries also have a Many To Many to DisabilityCodes. What I'd like to do is collect all the unique clients that have enquiries that contain a certain disability code. This is my T-SQL:
SELECT DISTINCT ClientId FROM (
SELECT ce.Id as EnquiryId, dc.Code, c.Id as ClientId
FROM ClientEnquiryToDisabilityCodes as etd
INNER JOIN DisabilityCodes as dc
ON etd.ClientEnquiryToDisabilityCode_DisabilityCode = dc.Id
INNER JOIN ClientEnquiries as ce
ON etd.ClientEnquiryToDisabilityCode_ClientEnquiry = ce.Id
INNER JOIN Clients as c
ON ce.ClientEnquiry_Client = c.Id
WHERE dc.Code = 'Ast')
AS data
Could someone even start to show me how to turn this into LINQ? I'm not even sure where to start. Thank you.
It wasn't as difficult as I first thought. I should probably drink MORE coffee before starting to work!
var distinctcients = (from etd in Context.ClientEnquiryToDisabilityCodes
join dc in Context.DisabilityCodes on etd.ClientEnquiryToDisabilityCode_DisabilityCode equals dc.Id
join ce in Context.ClientEnquiries on etd.ClientEnquiryToDisabilityCode_ClientEnquiry equals ce.Id
join c in Context.Clients on ce.ClientEnquiry_Client equals c.Id
where dc.Code == DisabilityCode
select c.Id)
.Distinct().ToList();
Given that you have the class structure attached below, the Example method will achieve what you're attempting
void Example()
{
var clients = new DisabilityCode[1].Where(dc => dc.Code == "Ast")
.SelectMany(dc => dc.Enquiries)
.Select(etd => etd.ClientEnquiry)
.Select(ce => ce.Client.ClientId);
// or
// .Select(ce => ce.Client)
// .Select(c => c.ClientId);
}
public class DisabilityCode
{
public string DisabilityId { get; set; }
public string Code { get; set; }
public virtual ICollection<ClientEnquiryToDisabilityCode> Enquiries { get; set; }
}
public class ClientEnquiry
{
public string ClientEnquiryId { get; set; }
public string ClientId { get; set; }
public virtual Client Client { get; set; }
public virtual ClientEnquiryToDisabilityCode DisabilityCode { get; set; }
}
public class ClientEnquiryToDisabilityCode
{
public string ClientEnquiryId { get; set; }
public string DisabilityCodeId { get; set; }
public virtual ClientEnquiry ClientEnquiry { get; set; }
public virtual DisabilityCode DisabilityCode { get; set; }
}
public class Client
{
public string ClientId { get; set; }
public virtual ICollection<ClientEnquiry> Enquiries { get; set; }
}

Categories