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();
Related
I've got some data that I need to return some of its related data and put it all in a model. I have all the appropriate fields setup in my model, and looks like this:
public class ComputerModel
{
public int MachineId { get; set; }
public int GroupId { get; set; }
public int SoftwareVersionId { get; set; }
public string GroupName { get; set; }
public string SoftwareVersion { get; set; }
public string IPAddress { get; set; }
public string HostName { get; set; }
public string MACAddress { get; set; }
public string Title { get; set; }
public bool IsIGMonitor { get; set; }
public string UpTime { get; set; }
public DateTime DateEntered { get; set; }
public string EnteredBy { get; set; }
public Nullable<DateTime> DateUpdated { get; set; }
public string UpdatedBy { get; set; }
public ICollection<MachineRole> MachineRoles { get; set; }
public ICollection<Role> Roles { get; set; }
}
Here's the linq statement I'm trying to use:
var query = (from m in unitOfWork.Context.Machines
join u in unitOfWork.Context.Users
on m.EnteredBy equals u.UserId into EntByUser
from EnteredByUser in EntByUser.DefaultIfEmpty()
join u2 in unitOfWork.Context.Users
on m.UpdatedBy equals u2.UserId into UpdByUser
from UpdatedByUser in UpdByUser.DefaultIfEmpty()
join g in unitOfWork.Context.Groups
on m.GroupId equals g.GroupId into Grp
from Groups in Grp.DefaultIfEmpty()
join s in unitOfWork.Context.SoftwareVersions
on m.SoftwareVersionId equals s.SoftwareVersionId into SW
from SoftwareVersions in SW.DefaultIfEmpty()
join mr in unitOfWork.Context.MachineRoles
on m.MachineId equals mr.MachineId into MachRoles
from MachineRoles in MachRoles.DefaultIfEmpty()
join r in unitOfWork.Context.Roles
on MachineRoles.RoleId equals r.RoleId into Rolz
from Rolz2 in Rolz.DefaultIfEmpty()
select new ComputerModel()
{
MachineId = m.MachineId,
GroupId = m.GroupId,
SoftwareVersionId = m.SoftwareVersionId,
GroupName = Groups.GroupName,
SoftwareVersion = SoftwareVersions.Version,
IPAddress = m.IPAddress,
HostName = m.HostName,
MACAddress = m.MACAddress,
Title = m.Title,
IsIGMonitor = m.IsIGMonitor,
UpTime = m.UpTime,
DateEntered = m.DateEntered,
DateUpdated = m.DateUpdated,
EnteredBy = EnteredByUser.FirstName + " " + EnteredByUser.LastName,
UpdatedBy = UpdatedByUser.FirstName + " " + UpdatedByUser.LastName,
MachineRoles = m.MachineRoles,
Roles = ?????
}).ToList();
I can get MachineRoles to populate but I cannot get Roles to populate. I've tried Roles = Rolz2 but Rolz returns a single instance of Role, not a collection.
How can I get this query to return Machines and the related data for both MachineRoles and Roles?
I've looked at the following articles but haven't had much luck:
This SO Article
Loading Related Data - MSDN
Using Include with Entity Framework
UPDATE
I notice if I remove my model and use an anonymous type, then I don't get any errors:
select new ()
{
GroupName = Groups.GroupName,
......
}).ToList();
But this doesn't help me in my project because I need to use a Model for the data.
If all the above tables are related via PK-FK relationship you can use linq lambda functions .Include() to include related tables and then use Navigation properties to access data.
If the tables are not related you can use LINQ left joins as shown in http://www.devcurry.com/2011/01/linq-left-join-example-in-c.html.
It looks like you need a mix of inner and left joins. The above example and only achieve inner joins.
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):
Good evening everyone.
I have a LINQ to SQL query result, and I want to convert this result to a list of Custom Model class
down here the Class Model:
public class Beneficiary
{
public int BeneficiaryId { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public DateTime? CreatedAt { get; set; }
[Column(TypeName = "date")]
public DateTime? BithDate { get; set; }
public int? CommunityId { get; set; }
public virtual Community Community { get; set; }
[Required]
public string Gender { get; set; }
public string IdNumber { get; set; }
[Required]
[StringLength(50)]
public string AppName { get; set; }
[StringLength(50)]
public string BeneficiaryNumber { get; set; }
public int CompanyId { get; set; }
public virtual Company Company { get; set; }
public virtual ICollection<BeneficiaryProject> BeneficiaryProjects { get; set; }
public virtual ICollection<Card> Cards { get; set; }
public virtual ICollection<Invoice> Invoices { get; set; }
}
and here is the query that I used :
var list = (from B in db.Beneficiaries
join ben in db.BeneficiaryProjects on B.BeneficiaryId equals ben.BeneficiaryId
where (ben.CardNeeded == true && ben.ProjectId == temp.ProjectId)
select new Beneficiary()
{
BeneficiaryId = B.BeneficiaryId,
FirstName = B.FirstName,
LastName = B.LastName,
IdNumber = B.IdNumber,
Gender = B.Gender
});
the above query is successfully executed, but when I convert the var list to a list of Beneficiary as following:
List<Beneficiary> list1 = list.ToList<Beneficiary>();
but I got the below exception :
An exception of type 'System.NotSupportedException' occurred in EntityFramework.SqlServer.dll but was not handled in user code
Additional information: The entity or complex type 'E_Voucher.Models.Beneficiary' cannot be constructed in a LINQ to Entities query.
Make conversion to custom class after retrieving the results from EF:
var list = (
from B in db.Beneficiaries
join ben in db.BeneficiaryProjects on B.BeneficiaryId equals ben.BeneficiaryId
where (ben.CardNeeded == true && ben.ProjectId == temp.ProjectId)
select new {
B.BeneficiaryId,
B.FirstName,
B.LastName,
B.IdNumber,
B.Gender
}
).AsEnumerable()
.Select(B => new Beneficiary() {
BeneficiaryId = B.BeneficiaryId,
FirstName = B.FirstName,
LastName = B.LastName,
IdNumber = B.IdNumber,
Gender = B.Gender
}).ToList();
Note: The reason you get the error on running ToList is that EF queries defer execution. Your query may be incorrect, but the only way to find out is to try getting its results.
The reason why you are getting this error
(in this line:)
List<Beneficiary> list1 = list.ToList<Beneficiary>();
is that you are trying to convert a Linq Query variable to a List.
All you really need to do is the following:
List<Beneficiary> list1 = list.ToList();
the query you have for temp.ProjectID:
var projects = db.Projects.Where(p => p.ContractId == id); foreach (var temp in projects)
should be something like:
int ProjectId = (from P in db.Projects where P.ContractId == id select P.ProjectId).FirstOrDefault();
and then take that value and put into your next query:
... where (ben.CardNeeded == true && ben.ProjectId == ProjectId)
I believe you can't project your object of type Beneficiary from within LINQ to Entities to a mapped entity object.
Try this:
db.Beneficiaries.Join(db.BeneficiaryProjects, B => B.BeneficiaryId, ben => ben.BeneficiaryId, (B, ben) => new { B, ben })
.Where(x => x.ben.CardNeeded == true && x.ben.ProjectId == temp.ProjectId)
.Select(x => x.B)
.ToList();
It's a lamda expression that should do the same thing as your LINQ query but will select your Beneficiary model object and map it. The ToList() can be replaced with an AsQueryable() or AsEnumerable() based on your need.
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'm using Entity Framework 6.0 in a C# WPF app. I have this query to search the Employee entity:
var query = Context.Employees.AsQueryable();
switch (selectedColumnValue)
{
case "lastName":
query = query.Where(q => q.lastName == SearchValue);
break;
case "employeeID":
query = query.Where(q => q.personnelID == SearchValue);
break;
case "securityID":
query = query.Where(q => q.securityID == SearchValue);
break;
default:
return;
}
The parent entity Employee may have some child rows in the EmployeeStatus entity to indicate if the employee is inactive, on leave of absence, or archived. Employee entity's primary key is employeeID and is a foreign key on EmployeeStatus.
Here is the Employee entity's columns:
public int employeeID { get; set; }
public string personnelID { get; set; }
public string securityID { get; set; }
public string firstName { get; set; }
public string middleName { get; set; }
public string lastName { get; set; }
public string suffix { get; set; }
public string job { get; set; }
public string organizationalUnit { get; set; }
public string costCenter { get; set; }
public string notes { get; set; }
public System.DateTime createdDate { get; set; }
And here's the EmployeeStatus entity's columns
public int employeeStatusID { get; set; }
public int employeeID { get; set; }
public int validEmployeeStatusID { get; set; }
public Nullable<System.DateTime> exitDate { get; set; }
public System.DateTime createdDate { get; set; }
If the user clicks the checkbox "Include archived employees?" during the search, we need to do the join to the EmployeeStatus and add the condition that the EmployeeStatus column validEmployeeStatusID equals 5 (Archived). How do you do this in LINQ?
Update: I need to add the Linq join after the above switch statement in an IF statement, such as this:
if (IncludeArchived)
{
// Add to Linq query here using method syntax
}
And so the Linq statement needs to use the method syntax.
What about this query?
query = (from empl in query
join status in Context.Status on empl.employeeID equals status.employeeID
where status.employeeStatusID == 5
select empl).Distinct();
Or you can perform chain:
query = query.Join(Context.Status, x => x.employeeID, x => x.employeeID, (a, b) => new { a, b })
.Where(x => x.b.employeeStatusID == 5).Select(x => x.a).Distinct();
Actually you do not need to add it after the switch statement.
In your current implementation you are including ALL employees (even the archived ones). So you actually need to make it so that you exclude the archived ones if the checkbox is not checked.
Note: I am assuming you have the navigation properties for your entities.
var query = Context.Employees.AsQueryable();
if (!IncludeArchived)
{
query = query.Where(e => e.Status == null || e.Status.employeeStatusId != 5);
}
switch (selectedColumnValue)
{
// ...
}