I've seen this topic BETWEEN EQUIVALENT in LINQ
My Original Query in SQL:
SELECT ISNULL(Tcar.name, '') FROM dbo.models model
LEFT JOIN cars Tcar on Tcar.model = model.id AND
Tcar.year between model.Start and model.End
I need to implement between inside a "left join", I tried this:
My Classes:
public class car
{
public string name { get; set; }
public int model { get; set; }
public DateTime year { get; set; }
}
public class model
{
public int id { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
}
My Implementation:
var theModel = from model in models
join Tcar in cars
on new
{
ID = (int)model.id,
DateStart = (DateTime)model.Start,
DateEnd = (DateTime)model.End
}
equals new
{
ID = (int)Tcar.model,
DateStart = (DateTime)Tcar.year,
DateEnd = (DateTime)Tcar.year
} into tempCar
from finalCar in tempCar
select new
{
CAR = (finalCar == null ? String.Empty : finalCar.name)
};
WorkAround:
var theModel = from model in models
join Tcar in cars
on model.id equals Tcar.model
where model.Start <= Tcar.year && model.End >= Tcar.year
select new
{
CAR = Tcar.name
};
If I use a workaround Linq translate to this query:
SELECT Tcar.name FROM dbo.models model
LEFT JOIN cars Tcar on Tcar.model == model.id
WHERE model.Start <= Tcar.year and model.End >= Tcar.year
I can put a simple where before "select new", but I have to implement by this way, with "between" inside the left join, How can I do this ?
Edit - Added DefaultOrEmpty() in order for it to be a Left Join
Modify your query like so, this will force the where clause into the join on clause. It wont give you the Between clause in the Join, but at least there wont be a where clause
var theModel = from model in models
from Tcar in cars.Where(x => model.id == x.model)
.Where(x => model.Start <= x.year && model.End >= x.year)
.DefaultOrEmpty()
select new
{
CAR = Tcar.name
};
SQL Server should consider your original query and the example produced by LINQ to be identical, because WHERE model.Start <= Tcar.year and model.End >= Tcar.year and ON Tcar.year between model.Start and model.End both specify join conditions.
It's generally preferred to use ON because it keeps your join conditions separate from your other search criteria, but that's for readability rather than performance. Testing similar queries on a couple of tables I had lying around produces identical query plans, and I'd be surprised if you see different plans for your tables.
Related
So the equivalent of this query:
select * from car
left join parts ON car.Id = parts.carId
where parts.MemberId = 1
is this, in EntityFrameworkCore LINQ , using an IQueryable which has already selected car.Include(x => x.parts):
queryable = queryable.Where(x =>
x.parts.Select(y => y.MemberId).Contains(1);
But how can I convert the following SQL to LINQ, so that it includes rows from the left car table that have no respective MemberId entries in the parts table?
select * from car
left join parts ON car.Id = parts.CarId and parts.MemberId = 1
Models:
public class Car
{
public int Id { get; set; }
public virtual ICollection<Part> Parts { get; set; }
}
public class Parts
{
public int Id { get; set; }
public int CarId { get; set; }
public virtual Car { get; set; }
public int MemberId { get; set; }
}
A filtered Include does exactly what you want:
var cars = context.Cars
.Include(c => c.Parts.Where(p => p.MemberId == 1));
This doesn't generate the shorter join statement with a composite condition, but an outer join to a filtered subquery on Parts, to the same effect.
queryable = queryable
.Where(x => x.parts.Select(y => y.MemberId).Contains(1) || !x.parts.Any());
Try it like that:
queryable = queryable.Include(x => x.parts).Where(x =>
x.parts.Any(y => y.MemberId == 1).ToList();
I'm trying to replicate a SQL statement in EF Core but cant seem to find a way to do it, to set the scene I have the following table structure
Slot -> SlotInstance -> SlotInstanceUser
(a Slot can have many SlotInstances, a SlotInstance can have many SlotInstanceUsers)
When a user registers for a SlotInstance a record is created in SlotInstanceUsers storing the SlotInstanceId and UserId - all good there.
I'm able to write SQL to get a list of slot instances which the user has not registered for e.g.
SELECT
S.StartDate, S.EndDate, S.StartTime, S.EndTime, S.DayOfWeek,
SI.Date
FROM
Slot S WITH (NOLOCK)
INNER JOIN
SlotInstance SI WITH (NOLOCK) ON S.Id = SI.SlotId
WHERE
SI.ID not in (
SELECT
SlotInstanceId
FROM
SlotInstanceUser SIU WITH (NOLOCK)
WHERE
SIU.UserId = #UserID
)
ORDER BY
SI.Date
But I just cant seem to replicate this in EF core - what am I missing?
You can write the LINQ query pretty much the same way as the SQL query. Just remember that in LINQ select is last, variables (aliases) are mandatory, and the equivalent of SQL NOT IN is !Contains. e.g.
var query =
from s in db.Slots
join si in db.SlotInstances on s.Id equals si.SlotId
where !(from siu in db.SlotInstanceUsers
where siu.UserId == userId)
select siu.SlotInstanceId).Contains(si.Id)
orderby si.Date
select new
{
s.StartDate, s.EndDate, s.StartTime, s.EndTime, s.DayOfWeek,
si.Date
};
But in EF Core you have more options, especially for joins, since normally the relationships (and associated joins) are encapsulated with navigation properties. So the model you are describing with words in EF Core/C# terms is something like
public class Slot
{
public int Id { get; set; }
// Other properties...
public ICollection<SlotInstance> SlotInstances { get; set; }
}
public class SlotInstance
{
public int Id { get; set; }
// Other properties...
public Slot Slot { get; set; }
public ICollection<SlotInstanceUser> SlotInstanceUsers { get; set; }
}
public class SlotInstanceUser
{
public int Id { get; set; }
// Other properties...
public SlotInstance SlotInstance { get; set; }
}
and the query would be like
var query =
from s in db.Slots
from si in s.SlotInstances
where !si.SlotInstanceUsers.Any(siu => siu.UserId == userId)
orderby si.Date
select new
{
s.StartDate, s.EndDate, s.StartTime, s.EndTime, s.DayOfWeek,
si.Date
};
(this actually translates to SQL NOT EXISTS, but that's not essential).
And if you don't need projection, but simply slot instances (with slot info) which the user has not registered for, then it would be simply
var query = db.SlotInstances
.Include(si => si.Slot)
.Where(si => !si.SlotInstanceUsers.Any(siu => siu.UserId == userId))
Hi good day I am new with Entity Framework. I just wanna to know if there is a way I could improve my implementation. Here are the codes.
public async Task<List<Record>> GetRecordsByBatchId(string batchId, string source)
{
List<string> idList = new List<string>();
//[1] Get all parent ID from table 1 with a filter of source and batchId
var parentIds= await _context.Set<FirstTable>()
.Where(a => a.IsActive
&& a.BatchId.Equals(batchId)
&& a.Source.Equals(source)).Select(b => b.ParentId).ToListAsync();
if (parentIds.Count() == 0)
{
return new List<Record>();
}
//[2] Query idNumber of each parentId from [1] to SecondTable
List<long> idNumber = await _context.Set<SecondTable>()
.Where(a => parentIds.Contains(a.Id))
.Select(b => b.IdNumber).ToListAsync();
//[3] Query Record/s that contains idNumber from previous query [2]. it is possible that 1 or
//more records has same idNumber
List<Risk> recordByIdNumber = await _context.Set<SecondTable>()
.Where(a => idNumber.Contains(a.IdNumber)).ToListAsync();
//[4] In this part I just want to group the records in [3] by Id number and sort each group
//by its endorsementNumber in descending order and return the record with highest endorsement
//number for each group
return (from record in recordByIdNumber
group record by record.IdNumber into g
orderby g.Key
select g.OrderByDescending(risk =>risk.EndorsementNumber).FirstOrDefault()).ToList();
}
}
The model for the FirstTable
public class FirstTable
{
public Guid? ParentId{ get; set; }
public string BatchId { get; set; }
public string Source { get; set; }
public bool IsActive { get; set; }
}
The model for the SecondTable
public class SecondTable
{
public Guid Id{ get; set; }
public int EndorsementNumber { get; set; }
public long IdNumber { get; set; }
}
Note: I just include the necessary properties in the model.
This approach is working as expected. I just wanna know if there is a possibility that these queries could be optimized that there is only 1 query for the SecondTable table.
Any help would be greatly appreciated, thanks in advance.
Yes, queries 1-3 can and should be combined. In order to do that you need, to have navigation properties in your model. It seems that there is one-to-many relationship between FirstTable and SecondTable. Let's use Customer and Order instead.
class Customer {
int CustomerId
string BatchId
ICollection<Order> Orders
}
class Order {
int OrderId
int CustomerId
Customer Customer
Risk Risk
}
in which case you just write third query as
List<Risk> = await _context.Orders.Where(o => o.Customer.BatchId == batchId)
.Select(o => o.Risk).ToListAsync();
Obviously, I am only guessing the structure and the relationship. But hopefully, this can get you started. For me Contains() is "code smell". There is a high chance that there will be large list out of your first query, and contains() will produce a huge IN clause in the database, that can easily crash the system
var parentIds = _context.Set<FirstTable>()
.Where(a => a.IsActive
&& a.BatchId.Equals(batchId)
&& a.Source.Equals(source)).Select(b => new { b.parentId });
var risks = await (from s in _context.Set<SecondTable>()
join p in parentIds on s.Id equals p.parentId
join r in _context.Set<SecondTable>() on s.IdNumber equals r.IdNumber
select r).GroupBy(r=>r.IdNumber)
.Select(r=> r.OrderByDescending(risk =>risk.EndorsementNumber).FirstOrDefault())
.ToArrayAsync();
return risks;
You can have 1 query instead of 3. It will perform better as the number of the rows from the first query grows.
EDIT: As #SvyatoslavDanyliv mentioned in the comments, group-take operations may not work depending on the version of the EF and the provider you use. You may need to separate the query and the group by operation like below :
var result = await (from s in _context.Set<SecondTable>()
join p in parentIds on s.Id equals p.parentId
join r in _context.Set<SecondTable>() on s.IdNumber equals r.IdNumber
select r).ToArrayAsync();
var risks = result.GroupBy(r=>r.IdNumber)
.Select(r=> r.OrderByDescending(
risk =>risk.EndorsementNumber).FirstOrDefault())
.ToArray();
return risks;
I have the following 3 tables structured in the way as shown below. I am trying to write an entity framework join query among the 3 tables
to
Select joblink, usersubmitted, runstatus, submitted_time, changelist
orderby submitted time
I was able to join and retrieve data from 2 tables but cant figure out
on how to join 3 tables, can anyone provide guidance on how to do this?
lookahead_run (Table#1)
+-------------------+--------+----------------+-------------------+----------+
lookahead_run_id(PK)|joblink | usersubmitted |submitted_time |runstatus
+-------------------+--------+----------------+-------------------+----------+
15963---------------+link1---+---username1----+2017-03-17 22:28:53--Fail-----
lookahead_run_change_list (Table#2)
+---------------+----------------+-----------------+
changelistid(PK)|lookahead_run_id|change_list_id
+---------------+----------------+-----------------+
38591-----------+15963-----------+34022
38590-----------+15963-----------+34021
38589-----------+15963-----------+34020
change_lists (Table#3)
+-------------+-----------+
change_list_id|changelist
+-------------+-----------+
34022-------- 1823900
34021-------- 1819483
34020-------- 1818572
UPDATED CODE;-
namespace Dashboard.Model.ApiModels
{
public class LookaheadRunInfo
{
public string ECJobLink { get; set; }
public List<String> gerrits { get; set; }
public string UserSubmitted { get; set; }
public string SubmittedTime { get; set; }
public string RunStatus { get; set; }
}
}
public IEnumerable<LookaheadRunInfo> GetLookaheadRunInfoSearch(LookaheadRunsFilterCriteria filterCriteria)
{
List<LookaheadRunInfo> lookaheadRunsInfo = new List<LookaheadRunInfo>();
var lookaheadRunData = bitDB.lookahead_run.OrderBy(x => x.lookahead_run_id).Skip(filterCriteria.PageNumber * filterCriteria.PageSize).Take(filterCriteria.PageSize).ToList();
foreach (var lookaheadRunRow in lookaheadRunData)
{
var lookaheadRunId = lookaheadRunRow.lookahead_run_id;
lookaheadRunsInfo = (from lrcl in bitDB.lookahead_run_change_list
join cl in bitDB.change_lists on lrcl.change_list_id equals cl.change_list_id
join lr in bitDB.lookahead_run on lrcl.lookahead_run_id equals lr.lookahead_run_id
where lrcl.lookahead_run_id == lookaheadRunId
orderby lr.submission_time
select new LookaheadRunInfo
{
lr.ec_job_link,
cl.change_requests,
lr.submitted_by,
lr.submission_time,
lr.lookahead_run_status,
}).ToList();
}
return lookaheadRunsInfo;
}
Error:-
Error 1 Cannot initialize type 'Dashboard.Model.ApiModels.LookaheadRunInfo' with a collection initializer because it does not implement 'System.Collections.IEnumerable'
You can put as many joins as you like in a query. if you want to select fields from multiple entities, you need to select a new object. I would suggest you to create a holder class to contain the joined contents, but i'm gonna show you how to do it with an anonymous type:
var lookaheadRunChangeListIds = (from lrcl in bitDB.lookahead_run_change_list
join cl in bitDB.change_lists on lrcl.change_list_id equals cl.change_list_id
join lr in bitDB.lookahead_run on lrcl.lookahead_run_id equals lr.lookahead_run_id
where lrcl.lookahead_run_id == lookaheadRunId
orderby lr.submitted_time
select new LookaheadRunInfo {
ECJobLink = lr.joblink,
UserSubmitted = lr.usersubmitted,
RunStatus = lr.runstatus,
SubmittedTime = lr.submitted_time,
gerrits = cl.changelist
}).ToList();
Note that the new { ... } can be replaced by a new NewClass { ... } where the NewClass contains all the selected fields.
Edit above: name your properties as you instanciate your LookAheadRun since the names are different, you should map them.
I have the following query:
List<Meeting> meetings =
(from m in crdb.tl_cr_Meetings
join p in crdb.tl_cr_Participants on m.MeetingID equals p.MeetingID
where p.TimeOut == null
select new Meeting
{
MeetingID = m.MeetingID,
MeetingName = m.MeetingName,
HostUserName = m.HostUsername,
BlueJeansMeetingID = m.tl_cr_BlueJeansAccount.MeetingID,
StartTime = m.StartTime,
Participants = (from pa in crdb.tl_cr_Participants
where pa.MeetingID == m.MeetingID
select pa.tl_cr_BlueJeansAccount.DisplayName).ToList()
}).Distinct().ToList();
And I want it to bring back a list of unique meetings. For some reason it brings back an entry for every participant, even though the data is identical:
Am I missing a grouping somewhere?
EDIT
The meeting class is currently very basic:
public class Meeting
{
public int MeetingID { get; set; }
public string MeetingName { get; set; }
public string HostUserName { get; set; }
public DateTime StartTime { get; set; }
public List<string> Participants { get; set; }
public string BlueJeansMeetingID { get; set; }
}
I believe the reason you get an entry for every participant is that you perform two joins.
You need to do a groupjoin.
var meetings = crdb.tl_cr_Meetings.GroupJoin(crdb.tl_cr_Participants,
k => k.MeetingID,
k => k.MeetingID,
(o,i) => new Meeting
{
MeetingID = o.MeetingID,
MeetingName = o.MeetingName,
HostUserName = o.HostUsername,
BlueJeansMeetingID = o.tl_cr_BlueJeansAccount.MeetingID,
StartTime = o.StartTime,
Participants = i.Select(s => s.DisplayName)
}).ToList();
You can use the way that juharr advise you, or you can implement comparer as separate class that implements IEqualityComparer interface and pass this comparer to distinct.
public class MeetingComparer : IEqualityComparer<Meeting>
{
public bool Equals (Meeting x, Meeting y)
{
return x.smth.Equals (y.smth);
}
public int GetHashCode (Meeting obj)
{
return obj.smth.GetHashCode ();
}
}
I think you just have to delete the 3rd line of your code (join ...).
Is this Linq to Entities? Regardless, I would remove the Distinct and add a group by.
List<Meeting> meetings =
(from m in crdb.tl_cr_Meetings
join p in crdb.tl_cr_Participants on m.MeetingID equals p.MeetingID
where p.TimeOut == null
group m by new { m.MeetingID, m.MeetingName, m.HostUsername, MeetingID2 = m.tl_cr_BlueJeansAccount.MeetingID, m.StartTime } into m
select new Meeting
{
MeetingID = m.Key.MeetingID,
MeetingName = m.Key.MeetingName,
HostUserName = m.Key.HostUsername,
BlueJeansMeetingID = m.Key.MeetingID2,
StartTime = m.Key.StartTime,
Participants = (from pa in crdb.tl_cr_Participants
where pa.MeetingID == m.Key.MeetingID
select pa.tl_cr_BlueJeansAccount.DisplayName).ToList()
}).ToList();
gabba explained why your code is failing. Here's a way you can re-work your query to not even need a Distinct() and make it a little cleaner looking, using a group join:
from m in crdb.tl_cr_Meetings
join p in crdb.tl_cr_Participants on new { m.MeetingID, null }
equals new { p.MeetingID, p.TimeOut } into meetingParticipants
select new Meeting
{
MeetingID = m.MeetingID,
MeetingName = m.MeetingName,
HostUserName = m.HostUsername,
BlueJeansMeetingID = m.tl_cr_BlueJeansAccount.MeetingID,
StartTime = m.StartTime,
Participants = meetingParticipants
.Select(x => x.tl_cr_BlueJeansAccount.DisplayName)
.ToList()
}