I have two entities - Clients and Jobs. Clients have 0 to many Jobs associated with them.
Client is as follows:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using JobsLedger.INTERFACES;
namespace JobsLedger.DATA.ENTITIES
{
#nullable enable
public class Client : IEntityBase, IAuditedEntityBase
{
public Client()
{
ClientNotes = new List<Note>();
Jobs = new List<Job>();
}
[Key]
public int Id { get; set; }
public string ClientNo { get; set; } = default!;
public bool Company { get; set; }
public string? CompanyName { get; set; }
public string? Abn { get; set; }
public bool IsWarrantyCompany { set; get; }
public bool RequiresPartsPayment { set; get; }
public string? ClientFirstName { get; set; }
public string ClientLastName { get; set; } = default!;
public string? Email { get; set; }
public string? MobilePhone { get; set; }
public string? Phone { get; set; }
public string? Address1 { get; set; }
public string? Address2 { get; set; }
public string? BankName { get; set; }
public string? BankBSB { get; set; }
public string? BankAccount { get; set; }
public bool Active { get; set; }
public DateTime? DateDeActivated { get; set; }
public bool Activity { get; set; }
// One warranty company client to a job.
public int? WarrantyCompanyId { get; set; }
public virtual Job? WarrantyCompany { get; set; }
// One suburb to a client.
public int? SuburbId { get; set; }
public virtual Suburb? Suburb { get; set; }
// If its a warranty company then we simply link it one to one to the brand id.
public virtual Brand? Brand { get; set; }
// Multiple notes for each client.
public virtual ICollection<Note> ClientNotes { get; set; }
// Multiple jobs for each client.
public virtual ICollection<Job> Jobs { get; set; }
public virtual ICollection<Job> WarrantyCompanyJobs { get; } = default!;
}
#nullable disable
}
Job is as follows:
using System.Collections.Generic;
using JobsLedger.INTERFACES;
namespace JobsLedger.DATA.ENTITIES
{
public class Job : IEntityBase, IAuditedEntityBase
{
public Job()
{
JobNotes = new List<Note>();
Visits = new List<Visit>();
}
public string? JobNo { get; set; }
public string? AgentJobNo { get; set; }
public int ClientId { get; set; } = default!;
public virtual Client Client { get; set; } = default!;
public int? BrandId { get; set; }
public virtual Brand? Brand { get; set; }
public int? TypeId { get; set; }
public virtual JobType? Type { get; set; }
public int? StatusId { get; set; }
public virtual Status? Status { get; set; }
public int? WarrantyCompanyId { get; set; }
public virtual Client? WarrantyCompany { get; set; }
public string? Model { get; set; }
public string? Serial { get; set; }
public string? ProblemDetails { get; set; }
public string? SolutionDetails { get; set; }
public virtual ICollection<Note> JobNotes { get; set; }
public virtual ICollection<Visit> Visits { get; }
public int Id { get; set; }
}
#nullable disable
}
This Linq Join works and I get back a list of ClientIndexDtos.
public IQueryable<ClientIndexDto> GetClients()
{
var result = this._context.Clients.Join(this._context.Jobs, c => c.Id, j => j.Id, (c, j) =>
new ClientIndexDto
{
Id = c.Id,
ClientNo = c.ClientNo,
Active = c.Active,
ClientFirstName = c.ClientFirstName,
ClientLastName = c.ClientLastName,
Company = c.Company,
CompanyName = c.CompanyName,
MobilePhone = c.MobilePhone,
IsWarrantyCompany = c.IsWarrantyCompany,
//JobsCount = j.Count().ToString(CultureInfo.CurrentCulture)
});
return result;
}
But.. I wanted the number of jobs (if any) for each client... so I asked this question on SO and it this was suggested:
public IQueryable<ClientIndexDto> GetClients()
{
var result = this._context.Clients.GroupJoin(this._context.Jobs, c => c.Id, j => j.Id, (c, j) =>
new ClientIndexDto
{
Id = c.Id,
ClientNo = c.ClientNo,
Active = c.Active,
ClientFirstName = c.ClientFirstName,
ClientLastName = c.ClientLastName,
Company = c.Company,
CompanyName = c.CompanyName,
MobilePhone = c.MobilePhone,
IsWarrantyCompany = c.IsWarrantyCompany,
JobsCount = j.Count().ToString(CultureInfo.CurrentCulture)
});
return result;
}
Whilst it works for the join version I am getting the following error when run with groupJoin..
The LINQ expression 'DbSet<Client>()
.GroupJoin(
inner: DbSet<Job>(),
outerKeySelector: c => c.Id,
innerKeySelector: j => j.Id,
resultSelector: (c, j) => new ClientIndexDto{
Id = c.Id,
ClientNo = c.ClientNo,
Active = c.Active,
ClientFirstName = c.ClientFirstName,
ClientLastName = c.ClientLastName,
Company = c.Company,
CompanyName = c.CompanyName,
MobilePhone = c.MobilePhone,
IsWarrantyCompany = c.IsWarrantyCompany
}
)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
I note that the URL - https://go.microsoft.com/fwlink/?linkid=2101038 discusses client - server evaluation... well I naturally want this to occur on the database but am perplexed as to why one (Join) works swimmingly and the other (GroupJoin) falters.
Can someone answer why its not working firstly and then tell me what I need to do to fix it. I would use Join except I need to know how many jobs exist on each client. GroupJoin will give me that if I can get it working...
I'm aware that I would like it to be performed at the database end ie obtain an IQueryable etc so it doesnt drag more records back to the client than necessary..
Any help appreciated.