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.
Related
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):
I have a invoice DTO that I want to return that contains a list of invoice items where InvoiceLines is the junction table.
My WebApi controller code:
var screenset =
from inv in context.Invoices where inv.InvoiceId == invoiceID
join line in context.InvoiceLines on inv.InvoiceId equals line.InvoiceId
join track in context.Tracks on line.TrackId equals track.TrackId into T
select new InvoiceDTO
{
InvoiceId = inv.InvoiceId,
InvoiceDate = inv.InvoiceDate,
CustomerId = inv.CustomerId,
CustomerFullName = inv.Customer.LastName + ", " + inv.Customer.FirstName,
CustomerPhoneNumber = inv.Customer.Phone,
BillingAddress = inv.BillingAddress,
BillingCity = inv.BillingCity,
BillingState = inv.BillingState,
BillingCountry = inv.BillingCountry,
BillingPostalCode = inv.BillingPostalCode,
Tracks = T.Select(t => new InvoiceTrackDTO
{
InvoiceLineId = line.InvoiceLineId,
TrackId = t.TrackId,
TrackName = t.Name,
Artist = t.Album.Artist.Name,
UnitPrice = line.UnitPrice,
Quantity = line.Quantity
})
};
var result = screenset.SingleOrDefault();
var response = Request.CreateResponse(HttpStatusCode.OK, result);
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return response;
My DTO's are defined as follows:
public class InvoiceDTO
{
public int InvoiceId { get; set; }
public DateTime InvoiceDate { get; set; }
public int CustomerId { get; set; }
public string CustomerFullName { get; set; }
public string CustomerPhoneNumber { get; set; }
public string BillingAddress { get; set; }
public string BillingCity { get; set; }
public string BillingState { get; set; }
public string BillingCountry { get; set; }
public string BillingPostalCode { get; set; }
public IEnumerable<InvoiceTrackDTO> Tracks { get; set; }
}
public class InvoiceTrackDTO
{
public int InvoiceLineId { get; set; }
public int TrackId { get; set; }
public string TrackName { get; set; }
public string Artist { get; set; }
public decimal UnitPrice { get; set; }
public int Quantity { get; set; }
}
My consumer code is:
message = new HttpClient().GetMessage(host, path, q).Result;
invoice = message.GetObjectFromMessage<InvoiceDTO>().Result;
tracks = invoice.Tracks as IEnumerable<InvoiceTrackDTO>;
My problem is if the ApiController is called with .ToList() it will give an error:
var result = screenset.ToList();
If the ApiController returns with .SingleOrDefault() it sucessfully returns the invoice but the invoice items cannot be more than one.
var result = screenset.SingleOrDefault();
I am trying to get a single invoice based on its ID but the invoice contains a list of invoice items.
So is this possible?
Thanks in advance.
EDIT:
If I do this in the consumer:
message = new HttpClient().GetMessage(host, path, q).Result;
invoice = message.GetObjectFromMessage<InvoiceDTO>().Result;
Debug.WriteLine("Invoice: " + invoice.ToJsonString());
tracks = invoice.Tracks as IEnumerable<InvoiceTrackDTO>;
Debug.WriteLine("Tracks: " + tracks.ToJsonString());
where ToJsonString is my extension method to convert any object to Json string. I can get invoice but not tracks where tracks are the invoice items.
The output and error I'm getting is as follows:
Invoice: {"InvoiceId":0,"InvoiceDate":"0001-01-01T00:00:00","CustomerId":0,"CustomerFullName":null,"CustomerPhoneNumber":null,"BillingAddress":null,"BillingCity":null,"BillingState":null,"BillingCountry":null,"BillingPostalCode":null,"Tracks":null}
Tracks: null
Exception thrown: 'System.ArgumentOutOfRangeException' in System.Windows.Forms.dll
EDIT 2:
Ok, just to clear things up a little.
I am comparing inner join with group join as illustrated below:
Parent
Id Value
1 A
2 B
3 C
Child
Id ChildValue
1 a1
1 a2
1 a3
2 b1
2 b2
Inner Join
from p in Parent
join c in Child on p.Id equals c.Id
select new { p.Value, c.ChildValue }
Result
Value ChildValue
A a1
A a2
A a3
B b1
B b2
GroupJoin
from p in Parent
join c in Child on p.Id equals c.Id into g
select new { Parent = p, Children = g }
Result
Value ChildValues
A [a1, a2, a3]
B [b1, b2]
C []
When returning from Entity Framework using LINQ, inner join works but not the group join.
Group join will be returned like inner join.
Hope that clarifies my question :)
Ok, I realized the problem. Entity Framework ultimately generates SQL based on Linq. In SQL, there is no notion of a parent object containing a list of child objects. Number of records returned will be the number of child objects with the parent columns repeated.
I was in a rush initially and forgot that a simple browser or Fiddler output will show that the original ApiController method will return this:
So modify your DTO like this:
public class InvoiceAndItemsDTO
{
// Invoice
public int InvoiceId { get; set; }
public DateTime InvoiceDate { get; set; }
public int CustomerId { get; set; }
public string CustomerFullName { get; set; }
public string CustomerPhoneNumber { get; set; }
public string BillingAddress { get; set; }
public string BillingCity { get; set; }
public string BillingState { get; set; }
public string BillingCountry { get; set; }
public string BillingPostalCode { get; set; }
// Track
public int InvoiceLineId { get; set; }
public int TrackId { get; set; }
public string TrackName { get; set; }
public string Artist { get; set; }
public decimal UnitPrice { get; set; }
public int Quantity { get; set; }
}
And modify your ApiController method like this:
var screenset =
from inv in context.Invoices where inv.InvoiceId == invoiceID
join line in context.InvoiceLines on inv.InvoiceId equals line.InvoiceId
join track in context.Tracks on line.TrackId equals track.TrackId
select new InvoiceAndItemsDTO
{
InvoiceId = inv.InvoiceId,
InvoiceDate = inv.InvoiceDate,
CustomerId = inv.CustomerId,
CustomerFullName = inv.Customer.LastName + ", " + inv.Customer.FirstName,
CustomerPhoneNumber = inv.Customer.Phone,
BillingAddress = inv.BillingAddress,
BillingCity = inv.BillingCity,
BillingState = inv.BillingState,
BillingCountry = inv.BillingCountry,
BillingPostalCode = inv.BillingPostalCode,
InvoiceLineId = line.InvoiceLineId,
TrackId = track.TrackId,
TrackName = track.Name,
Artist = track.Album.Artist.Name,
UnitPrice = line.UnitPrice,
Quantity = line.Quantity
};
and your consumer like this:
message = new HttpClient().GetMessage(host, path, q).Result;
tracks = message.GetObjectFromMessage<List<InvoiceAndItemsDTO>>().Result;
if (tracks != null)
{
invoice = tracks.First();
}
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; }
}
I have a database with two tables (UserData) and (UserDetails). I use the LINQtoEntities.
The issue here is that in the LINQ query is not accepted:
Details = c.ToList<UserDetails>()
Error is:
Instance argument: cannot convert from 'System.Collections.Generic.IEnumerable<mynamespace.UserDetails>' to 'System.Collections.Generic.IEnumerable<System.Collections.IEnumerable>'
public class UserData
{
public int Index{ get; set; }
public string Name{ get; set; }
public string PersonalNo { get; set; }
public <List<UserDetails>> Details { get; set; }
}
public struct UserDetails
{
public int Age;
public string profession;
public string gender;
}
public IEnumerable<Userdata> GetUserData()
{
var context = new DatabaseEntities();
var Results =
from a in context.UserData
join b in context.UserDetails on a.Index equals b.Index into c
select new UserData{ Index = a.Index, Name = a.Name, PersonalNo = a.PersonalNo,
Details= c.ToList<UserDetails>() };
return Results;
}
Is there anybody who can help me out. In case more infos needed, please ask.
Thanks.
The following example is working. To get it working you can use object. See below the example:
public class UserData
{
public int Index{ get; set; }
public string Name{ get; set; }
public string PersonalNo { get; set; }
public List<object> Details { get; set; }
}
public class UserDetails
{
public int Age;
public string profession;
public string gender;
}
public IEnumerable<Userdata> GetUserData()
{
var context = new DatabaseEntities();
var Results =
from a in context.UserData
join b in context.UserDetails on a.Index equals b.Index into c
select new UserData{ Index = a.Index, Name = a.Name, PersonalNo = a.PersonalNo,
Details= c.ToList<object>() };
return Results;
}
The Type in the Watch windows is names as "object {MyTestApp.UserDetails}"
So basically it is working as I did it. But I need to cast it appropriate. Possibly now it is easier to help me.
Any suggestions?
I'm starting to use Entity Framework Code First.
Suppose to have such POCO's (ultimately simplified):
public class BortStructure
{
public Guid Id { get; set; }
public String Name { get; set; }
}
public class Slot
{
public Guid Id { get; set; }
public String Name { get; set; }
public BortStructure { get; set; }
}
public class SystemType
{
public Guid Id { get; set; }
public String Name {get; set; }
}
public class SlotSystemType
{
public Guid Id { get; set; }
public Slot Slot { get; set; }
public SystemType SystemType {get; set; }
}
and a context
public MyContext : DbContext
{
public DbSet<BortStructure> BortStructures { get; set; }
public DbSet<Slot> Slots{ get; set; }
public DbSet<SystemType> SystemTypes { get; set; }
public DbSet<SlotSystemType> SlotSystemTypes { get; set; }
}
I have a task to get BortStructure by Id with list of attached Slots, each one with list of systemTypes attached.
Using SQL allowed me to do that with some JOIN's:
SELECT BortStructures.Id, BortStructures.Name, Slots.Id,
Slots.Name, SystemType.Id, SystemType.Name FROM
((BortStructures LEFT JOIN Slots ON BortStructures.Id = Slots.BortStructureId)
LEFT JOIN SlotSystemTypes ON SlotSystemTypes.SlotId = Slots.Id)
LEFT JOIN SystemTypes ON SystemTypes.Id = SlotSystemTypes.SystemTypeId
WHERE BortStructures.Id='XXXXXX' ORDER BY Slots.Id, SystemType.Id
But with Entity Framework Code First I don't have any idea howto do that.
If I use
var slotSystemTypes = from sl in MyContext.SlotSystemTypes
where sl.Slot.BortStructure.Id = XXXXXX
orderby sl.Slot.Id, sl.SystemType.Id
select sl;
i, of course, will receive nothing if BortStructure consists of no Slots/Slots without any SystemTypes attached.
Instead of getting BortStructure with empty list of Slots/with Slots, each one with empty list of SystemTypes attached as I expect to get.
Is there any way to archive that with single LINQ query for my database configuration?
You can use join operator example:
string[] categories = new string[]{
"Beverages",
"Condiments",
"Vegetables",
"Dairy Products",
"Seafood" };
List<Product> products = GetProductList();
var q =
from c in categories
join p in products on c equals p.Category
select new { Category = c, p.ProductName };
foreach (var v in q)
{
Console.WriteLine(v.ProductName + ": " + v.Category);
}
more samples in: http://code.msdn.microsoft.com/LINQ-Join-Operators-dabef4e9