Linq Query Join to Subquery with In clause - c#

I have a query from an old application that I am attempting to convert to an Entity Framework Core application. The relationship I am working with is a simple one to many, where one Order can have many OrderEvents. The old query looks something like this:
select Order.Id, mostRecent.*
from Order
left join OrderEvent mostRecent
on mostRecent.Id in (select top(1) Id
from OrderEvent
where OrderEvent.OrdId = Order.Id
and OrderEvent.PostDateTime is not null
order by OrderEvent.PostDateTime desc)
where Order.SomeColumn = 'some value'
I am struggling with figuring out how to write this query in LINQ. It doesnt seem that one can use anything other than equals when doing using join, so I first attempted something like:
var test = (from order in _context.Ord.AsNoTracking()
join mostRecentQuery in _context.OrdEvt.AsNoTracking()
on (from orderEvent in _context.OrdEvt.AsNoTracking()
where orderEvent.PostDateTime != null && orderEvent.OrdId == order.Id
orderby orderEvent.PostDateTime descending
select orderEvent.Id).FirstOrDefault()
equals mostRecentQuery.Id
into mostRecentResults
from mostRecent in mostRecentResults.DefaultIfEmpty()
select new
{
OrderId = order.Id,
OrderEvent = mostRecent
}).ToList();
Whatever sql this spit out, it appears to be so slow that I cant even run it connected to Sql Server. I am however able to run this query when using Sqlite and it generates the following sql:
SELECT 'big list of fields....'
FROM "Ord" AS "order"
LEFT JOIN "OrdEvt" AS "mostRecentQuery" ON COALESCE((
SELECT "orderEvent0"."Id"
FROM "OrdEvt" AS "orderEvent0"
WHERE "orderEvent0"."PostDateTime" IS NOT NULL AND ("orderEvent0"."OrdId" = "order"."Id")
ORDER BY "orderEvent0"."PostDateTime" DESC
LIMIT 1
), X'00000000000000000000000000000000') = "mostRecentQuery"."Id"
This is close do what I am going for, but Im not sure why the COALESCE function is being used.
Is it possible to represent the query I am trying to convert, in Linq query syntax?

I'm not sure what your problem is, because you left out why it doesn't compile.
Bute here and simple example whith a join and choosing one event for an order:
public class Order
{
public int OrderId { get; set; }
public string OrderName { get; set; }
}
public class OrderEvent
{
public int OrderId { get; set; }
public string EventName { get; set; }
public DateTime Date { get; set; }
}
public static void Main(string[] args) // Here our Exe-Args are delivered to our program..
{
List<Order> orders = new List<Order>()
{
new Order() { OrderId = 1, OrderName = "One" }
};
List<OrderEvent> orderEvents = new List<OrderEvent>()
{
new OrderEvent() { OrderId = 1, EventName = "EventTwo", Date = DateTime.Now.AddHours(-1) },
new OrderEvent() { OrderId = 1, EventName = "EventOne", Date = DateTime.Now },
new OrderEvent() { OrderId = 1, EventName = "EventThree", Date = DateTime.Now.AddHours(-2) },
new OrderEvent() { OrderId = 2, EventName = "EventX", Date = DateTime.Now },
};
var tmp = orders.GroupJoin( // Join something to our orders
orderEvents, // join all events
o => o.OrderId, // key of our order
e => e.OrderId, // foreign-key to order in our event
(o, es) => // lambda-expression "selector". One order "o" and multiple events "es"
es.OrderByDescending(e => e.Date) // We order our events
.Select(s => new // we build a new object
{
OrderId = o.OrderId,
OrderName = o.OrderName,
RecentEvent = s.EventName
}).First()); // we choose the object with biggest date (first after orderDesc)
foreach (var item in tmp)
{
Console.WriteLine(item.OrderId + ", " + item.OrderName + ", " + item.RecentEvent);
}
tmp = from o in orders
join e in orderEvents on o.OrderId equals e.OrderId into es
select new
{
OrderId = o.OrderId,
OrderName = o.OrderName,
RecentEvent = (from x in es orderby x.Date descending select x.EventName).First()
};
foreach (var item in tmp)
{
Console.WriteLine(item.OrderId + ", " + item.OrderName + ", " + item.RecentEvent);
}
}

Related

Get the max date from linq with mutiple joins and with multiple select fields

I have been scratching my head with this and cannot find a solution. I have a dataset like this,
JobID DueDate VisitID
4024082 2020-07-31 5267157
4024082 2020-07-29 5263915
4024082 2020-08-04 5269154
4024082 2020-07-23 5258774
4024082 2020-07-22 5255307
I want to get the result set like this,
JobID Duedate VisitID
4024082 2020-08-04 5269154
I have tried several ways but cannot get it to work. my linq query is
var jobs = await (from j in _ctx.Jobs
join v in _ctx.Visits on j.JobID equals v.JobID
join p in _ctx.Products on j.ProductID equals p.ProductID
where j.IsProcessed== true
group v by new
{
v.JobID,
v.VisitID
}
into g
select new
{
JobId = g.Key,
DueDate = g.Max(x => x.DueDate),
VisitID = g.Key.VisitID
}).ToListAsync();
Any help would be highly appreciated. Thanks
You cannot use Max to get more than one value. You have to use a sorting mechanism here.
The following is an example of how to get the jobs grouped by the JobID and then every highest date with accompanying VisitID.
Here is the example. (I used LinqPad to test this):
void Main()
{
var jobs = new[]
{
new Job { JobID = 4024082, DueDate = DateTime.ParseExact("2020-07-31", "yyyy-MM-dd", CultureInfo.InvariantCulture), VisitID = 5267157 },
new Job { JobID = 4024082, DueDate = DateTime.ParseExact("2020-07-29", "yyyy-MM-dd", CultureInfo.InvariantCulture), VisitID = 5263915 },
new Job { JobID = 4024082, DueDate = DateTime.ParseExact("2020-08-04", "yyyy-MM-dd", CultureInfo.InvariantCulture), VisitID = 5269154 },
new Job { JobID = 4024082, DueDate = DateTime.ParseExact("2020-07-23", "yyyy-MM-dd", CultureInfo.InvariantCulture), VisitID = 5258774 },
new Job { JobID = 4024082, DueDate = DateTime.ParseExact("2020-07-22", "yyyy-MM-dd", CultureInfo.InvariantCulture), VisitID = 5255307 },
};
var query = from job in jobs
group job by job.JobID into grp
select grp.OrderByDescending(g => g.DueDate).FirstOrDefault();
query.Dump();
}
public class Job
{
public int JobID { get; set; }
public DateTime DueDate { get; set; }
public int VisitID { get; set; }
}
Output:
JobID DueDate VisitID
4024082 4-8-2020 00:00:00 5269154
I think the problem is that you're using VisitId as part of the GroupBy. Since all the VisitId values are unique, you'll get a single record per group (and therefore one group for each record).
Perhaps you should only join on JobId if you want one group from those sample records. Then to get the VisitID for the row that had the most recent DueDate, we can order the group by DueDate (descending) and take the VisitID from the First() record:
var jobs = await (from j in _ctx.Jobs
join v in _ctx.Visits on j.JobID equals v.JobID
join p in _ctx.Products on j.ProductID equals p.ProductID
where j.IsProcessed== true
group v by v.JobID
into g
select new
{
JobId = g.Key,
DueDate = g.Max(x => x.DueDate),
VisitID = g.OrderByDecending(x => x.DueDate).First().VisitID
}).ToListAsync();

Entity Framework Linq to Object Mapping

I have one problem.
Database Shema
==================
|parts |
==================
| partId |textId |
==================
========================
texts |
========================
|TextId|LanguageId|text|
========================
============================
languages |
============================
|LanguageId|LanguageIsoCode|
============================
I want to map this result to the following object
public long PartId { get; set; }
public Dictionary<string,string> Name { get; set; }
eg.
{
PartId: 32020
Name: {["en": "Blah", "es": "Blah2"]}
}
this is what I have tried, but Im running into TimeOut with this query.
var query = (from p in _context.epc_parts
select new //select first dynamic object
{
PartId = p.PartID,
Code = p.PartName,
Name = (from trans in _context.epc_texts
join lang in _context.epc_languages on trans.LanguageID equals lang.LanguageID
where p.TextID == trans.TextID
select new
{
LanguageId = lang.shortname.ToLower(),
Caption = trans.Caption
})
}).AsEnumerable().Select(x => new SearchPartModel //transform it here when we can use dictionary
{
Code = x.Code,
PartId = x.PartId,
Name = x.Name.ToDictionary(t => t.LanguageId, t => t.Caption)
});
The parts table has about 60k rows for every row there are 7 translations. Navigation properties cant be used because the Shema doesn't use foreign keys and the model is generated from db.
I have solved it with this query. For the whole query it took about 20s to load everything what is ok for this purpose. Im using group by first.
(from p in _context.epc_parts
join trans in _context.epc_texts on p.TextID equals trans.TextID
join lang in _context.epc_languages on trans.LanguageID equals lang.LanguageID
select new
{
PartId = p.PartID,
Code = p.PartName,
Caption = trans.Caption,
LanguageId = lang.shortname.ToLower()
}).AsEnumerable().GroupBy(x => x.PartId).Select(g => new SearchPartModel
{
Code = g.Select(x => x.Code).FirstOrDefault(),
PartId = g.Key,
Name = g.Select(x => new
{
x.LanguageId,
x.Caption
}).Distinct().ToDictionary(y => y.LanguageId, y => y.Caption)
});
from p in _context.epc_parts
join trans in _context.epc_texts on p.TextID equals trans.TextID
join lang in _context.epc_languages on trans.LanguageID equals lang.LanguageID
select new {
PartId = p.PartID,
Code = p.PartName,
Name = new
{
LanguageId = lang.shortname.ToLower(),
Caption = trans.Caption
}
}
That should be better, otherwise in your current implementation for each part you're querying the texts table

Populating a List in a Strongly Typed LINQ to Entities Query

I am relatively new to LINQ and I'm simply trying to populate a List in a LINQ to Entities query. Any assistance would be greatly appreciated.
The class "SearchCriteria" looks like this:
public class SearchCriteria
{
public IList<DTOEventType1> eventTypes { get; set; }
public IList<DTOLocation1> locs { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(ApplyFormatInEditMode = true,
DataFormatString="0:dd/MM/yyyy}")]
public DateTime? searchDate { get; set; }
public List<PortfolioLibraryMVC4V2.Domain.DTO.DTOCalendarEvents>
CalendarEvents;
}
and my query is shown below:
var query = from E in medRepo.evt
join L in medRepo.loc on E.LocationID equals L.LocationID
join ET in medRepo.evtType on E.EventTypeID equals
ET.EventTypeID
where IDsOfSelectedEventTypes.Contains(E.EventTypeID) &&
IDsOfSelectedLocations.Contains(L.LocationID) &&
E.EventStart > eventslocs.searchDate
select new SearchCriteria
{
CalendarEvents = query.Select(x => new DTOCalendarEvents
{
Name = ET.Name,
EventStart = E.EventStart,
EventEnd = E.EventEnd
}).ToList()
};
var datalist = query.ToList();
I'm trying to populate the list "CalendarEvents" in the Class SearchCriteria in the select in the above query. Currently, I get the error message "trying to use the local variable query before it is declared" so obviously my syntax isn't correct. Can someone show me the proper way to do this?
Thanks,
Pete
Give this is a try:
var query = from E in medRepo.evt
join L in medRepo.loc on E.LocationID equals L.LocationID
join ET in medRepo.evtType on E.EventTypeID equals
ET.EventTypeID
where IDsOfSelectedEventTypes.Contains(E.EventTypeID) &&
IDsOfSelectedLocations.Contains(L.LocationID) &&
E.EventStart > eventslocs.searchDate
select new DTOCalendarEvents
{
Name = ET.Name,
EventStart = E.EventStart,
EventEnd = E.EventEnd
};
var datalist = query.ToList();
var searchCriteria = new SearchCriteria();
searchCriteria.CalendarOfEvents = datalist;
You can take SearchCriteria outside of the linq query..
var searchCriteria = new SearchCriteria();
searchCriteria.CalendarEvents = (from E in medRepo.evt
join L in medRepo.loc
on E.LocationID equals L.LocationID
join ET in medRepo.evtType
on E.EventTypeID equals ET.EventTypeID
where
IDsOfSelectedEventTypes.Contains(E.EventTypeID)
&& IDsOfSelectedLocations.Contains(L.LocationID)
&& E.EventStart > eventslocs.searchDate)
.Select(x => new DTOCalendarEvents
{
Name = ET.Name,
EventStart = E.EventStart,
EventEnd = E.EventEnd
})
.ToList();
basically we only need the list of events from the LINQ query.

Linq to entities hard code list

This is my view model
public class ProcurementDisplayData
{
public string Key { get; set;}
public string Value { get; set;}
}
How can I hard code this list. Basically what will be the syntax inside the query
var query = (from u in context.Jobs
join q in context.Quotations on u.QuotationId equals q.QuotationId
join v in context.Vessels on q.VesselId equals v.VesselId
join c in context.Customers on q.CustomerId equals c.CustomerId
where u.JobNo == JobNo
select new List<ProcurementDisplayData>
{
new { Key = "a" ,Value=u.JobNo},
new { Key = "b" ,Value=u.Vessel}
}).ToList();
return query;
Get JobNo and Vessel from database. Then create lists in memory:
var query = from u in context.Jobs
join q in context.Quotations on u.QuotationId equals q.QuotationId
join c in context.Customers on q.CustomerId equals c.CustomerId
where u.JobNo == JobNo
select new { u.JobNo, u.Vessel };
return query.AsEnumerable() // moves further processing to memory
.Select(x => new List<ProcurementDisplayData> {
new { Key = "a", Value = x.JobNo },
new { Key = "b", Value = x.Vessel }
}).ToList();

Compare with list using linq

I have a list cids that I am fetching like this
var cids = _service.Employee.Where(i => i.EmpID == _empID).Select(j => j.ClientID).ToList();
I want to compare this list with Patient Entity and get all the records of patients that matches the clientId in the cid list
Patient Entity is like this
class Patient
{
Int PatientID{ get; set;}
Int ClientID{get; set;}
string PatientName{get; set;}
}
Right now I am doing it like this
foreach(var item in cids)
{
var pp = from p1 in _service.Patients
where p1.ClientId == item
select new PatientDTO
{
PatientID = p1.PatientID,
PatientName = p1.PatientName,
};
prec.Add(pp);
}
Is there a way to do it with Linq without using foreach
You can use Contains on your List (you don't need the ToList, by the way : this would avoid 2 queries on db).
var allPp = _service.Patients.Where(p1 => cids.Contains(p1.ClientId))
.Select(m >= new PatientDTO) {
PatientID = m.PatientID,
PatientName = m.PatientName
});
But the most performant way, in a db world, would be a join
from emp in _service.Employee.Where(i => i.EmpID == _empID)
join patient in _service.Patients on emp.ClientId equals patient.ClientId
select new PatientDTO {
PatientID = patient.PatientID,
PatientName = patient.PatientName,
}
Use Enumberable.Intersect to fetch common records.
var commonClients = cids.Intersect<int>(_service.Patients.Select(x => x.ClientID));
var person = _service.Patients.Where(x => commonClients.Contains(x.ClientID));

Categories