Linq - Join where ID's !=, select new + distinct? - c#

I have the following classes.
Course;
public class Course
{
//pk
public int Id{ get; set; }
public int SourceCourseId { get; set; }
public string Name { get; set; }
}
Registration
public class Registration
{
//primary key
public int Id { get; set; }
//...more fields
public int CourseId { get; set; }
}
I want to obtain a collection of annonymous objects with the two fields below for all Courses that are Distinct in the registrations table that are not in the Courses table.
var distinctCourses = (from registration in db.Registrations
join courses in db.Courses on registration.CourseId equals courses.SourceCourseId
where registration.CourseId != courses.SourceCourseId
select new
{
SourceCourseId = registration.CourseId,
Name = registration.CourseName,
}).Distinct().ToList();
For some reason the above is returning 0... Any suggestions?

try a left join:
var query = from r in registrations
join c in courses on r.CourseId equals c.id into newCourses
from nullCourse in newCourses.DefaultIfEmpty()
where nullCourse == null
select new { }
Edit - per comment from Alex :
Also, your where clause needs to change to
where nullCourse == null
Edit - changed join columns and added correct where clause.
Edit - group registrations on CourseID so they will be distinct
var distinctCourses =
(from registration in db.Registrations
group registration by registration.CourseId into grp
from reg in grp
join courses in db.Courses on reg.CourseId equals courses.SourceCourseId into newCourses
from nullCourse in newCourses.DefaultIfEmpty()
where nullCourse == null
select new
{
SourceCourseId = reg.CourseId,
Name = reg.CourseName,
}).ToList();

Try this
var result = Registrations.GroupJoin(Courses,r=>r.CourseId,c=>c.SourceCourseId,
(k,g) => new {k,g})
.Where(x=>x.g.Count()==0)
.Select(s=> new {id=s.k.CourseId,name=s.k.CourseName});

Related

LINQ uses extra inner join

Here are my simplified models:
public class Resource
{
public int id { get; set; }
public string Name { get; set; }
}
public class Upgrade
{
public int Id { get; set; }
public virtual Resource Res { get; set; }
public int Lvl { get; set; }
public int Amount { get; set; }
}
Basically, I need to group by Resource and get [Name] from "Resource" and sum([Amount]) from "Upgrade".
Here is the LINQ:
from u in _db.Upgrades
join r in _db.Resources on u.Res equals r
where u.Lvl > levelFrom
&& u.Lvl <= levelTo
group u by new { r.id, r.Name } into grp
select new UpgradeCost()
{
resName = grp.Key.Name,
resAmount = grp.Sum(k => k.Amount),
};
And here is the SQL I get (Sqlite):
SELECT "r0"."Name" AS "resName", COALESCE(SUM("u"."Amount"), 0) AS "resAmount"
FROM "Upgrades" AS "u"
LEFT JOIN "Resources" AS "r" ON "u"."Resid" = "r"."id"
INNER JOIN "Resources" AS "r0" ON "r"."id" = "r0"."id"
WHERE ("u"."Lvl" > #__levelFrom_0) AND ("u"."Lvl" <= #__levelTo_1)
GROUP BY "r0"."id", "r0"."Name"
LINQ uses extra INNER JOIN to group by.
I want it to be made like this:
SELECT "r"."Name" AS "resName", COALESCE(SUM("u"."Amount"), 0) AS "resAmount"
FROM "Upgrades" AS "u"
LEFT JOIN "Resources" AS "r" ON "u"."Resid" = "r"."id"
WHERE ("u"."Lvl" > #__levelFrom_0) AND ("u"."Lvl" <= #__levelTo_1)
GROUP BY "r"."id", "r"."Name"
Additional join generated when used u.Res navigation property. Actually you don't need explicit joins here.
from u in _db.Upgrades
where u.Lvl > levelFrom
&& u.Lvl <= levelTo
group u by new { u.Res.id, u.Res.Name } into grp
select new UpgradeCost()
{
resName = grp.Key.Name,
resAmount = grp.Sum(k => k.Amount),
};

SQL TO Linq, How to return an object and fill properties

i have a question, i want to create a linq query that returns a list of object.
This is the model
public class Test
{
[Key]
public int ID { get; set; }
[Required]
[StringLength(5)]
public string Code { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
[NotMapped]
public string Reference { get; set; }
}
The query that i want to do is simple: context.Test.ToList();
this returns the database mapping Reference is null since is not part of the table.
Now if i create a linq query i know that i can do select new { all fields here }
i want to avoid this:
select new Test
{
Reference = r,
ID = t.ID,
Code = t.Code,
Name = t.Name
}).ToList();
is it possible to do something like this
(from t in context.Test
join r in context.Reference on f.ID equals r.ID
select new
{
t.Reference = r.Reference,
t
}).ToList();
i want to set the Reference value inside the same query, is that possible?
What are you asking is not directly supported in LINQ to Entities - neither projection to entity type, nor expression block which is the only way to assign properties of an existing object.
As usual, the typical workaround is to split the query on two parts - one being LINQ to Entities query selecting the necessary data (usually into intermediate anonymous type), then switch to LINQ to Objects with AsEnumerable() and do the rest - in this case using block inside Select:
var result =
(from t in context.Test
join r in context.Reference on f.ID equals r.ID
select new { t, r.Reference }
).AsEnumerable()
.Select(x =>
{
x.t.Reference = x.Reference;
return x.t;
}).ToList();
Don't select an anonymous object, just create a new T from the one you have.
(from t in context.Test
join r in context.Reference on t.ID equals r.ID
select new Test
{
Reference = r,
ID = t.ID,
Code = t.Code,
Name = t.Name
}).ToList();
EDIT:
To avoid having to manually copy over all the properties
public class Test
{
public int ID { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public string Reference { get; set; }
public Test CopyWithReference(string reference)
{
var copy = (Test)this.MemberwiseClone();
copy.Reference = reference;
return copy;
}
}
Then...
(from t in context.Test
join r in context.Reference on t.ID equals r.ID
select t.CopyWithReference(r)).ToList();
Try following :
(from t in context.Test
join r in context.Reference on f.ID equals r.ID
select new Test()
{
ID = t.ID,
Code = t.Code,
Name = t.Name,
Reference = r.Reference
}).ToList();
Try:
var result = context.Test.Include("Reference").ToList();
or:
var result = context.Test.Include(t => t.Reference).ToList();
or Try Lambda Expressions:
var result = context.Test.Select(t => new {
t,
t.Reference = t.Reference.Select(r => new {
r.Reference })
}).AsEnumerable().Select(x => x.r).ToList();

LINQ to a model including List

I would like to get your advice on Linq.
I have 3 tables users ( listing all the users), another one userEtablishments (one user can have many places), and the table Establishment (to list the place)
i have a models which is:
public class UserVM {
public Int32 id { get; set; }
public string name { get; set; }
public List<Etablishment> Etablishments { get; set; }
}
and the Etablishment:
public class EtablishmentVM {
public Int32 id { get; set; }
public string name { get; set; }
}
I query first the users, then establishments of the user, but its very slow:
UserVM obj = new UserVM();
using (context db = new context(ConnectionString))
{
userlist = (from u in db.users
join p in db.establishments on u.id equals p.id
join pe in db.userEtablishments on p.id equals pe.id
select new
{
u,
}).FirstOrDefault();
if (entity != null)
{
obj.id = entity.u.id);
obj.name = entity.u.name);
// Then get the establishment list then update the establishmentVM
List<EstablishmentVM> userplaces = new List<EstablishmentVM>();
var userplacesList = (from u in db.users join p in db.establishments on
u.id equals p.id join pe in db.userEtablishments on p.id equals pe.id
select new { u, pe }).ToList();
if (userplacesList != null && userplacesList.Count > 0)
{
foreach (var item in userplacesList)
{ }.....
is there a better way to do to get it execute faster ?
Any advices would helps, the purpose is to get betterperformances.
Thanks for your helps

how to write an EF query to join three tables and order by a field?

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.

Use a filter in an outer join in linq

I have the following entities:
public class Company
{
public string CompanyName { get; set; }
public int ID { get; set; }
}
public class CompanyCurrency
{
public int Id { get; set; }
public int CompanyId { get; set; }
public decimal Rate { get; set; }
public int CurrencyId { get; set; }
}
public class Currency
{
public int ID { get; set; }
public string Name { get; set; }
}
I need to get the list of currencies for a country. If a country does not have an entry for a currency I need a line for that missing entry too.
The statement I have right now is:
var currencies =
from c in Currencies
join cc in CompanyCurrency
on c.ID equals cc.CurrencyId
into jointable
from resultiten in jointable.DefaultIfEmpty()
select new {c.Name ,
HasEntry = resultiten == null ? 0:1,
rate = resultiten != null ? resultiten.Rate:0 ,
} ;
This is not filtered by a countryID . I tried to add a filter by
from c in Currencies
join cc in CompanyCurrency
on c.ID equals cc.CurrencyId
into jointable
from resultiten in jointable.DefaultIfEmpty()
where resultiten.CompanyId == 1 || resultiten == null
select new {c.Name ,
HasEntry = resultiten == null ? 0:1,
rate = resultiten != null ? resultiten.Rate:0
But that does not have a result for a currency that has en entry for a company other then companyID 1.
The cooresponding SQL query would be
select *
from [dbo].[Currency] c
left outer join [dbo].[CompanyCurrency] cc
on c.id = cc.Currencyid
and cc.[Companyid] = 1
You need to either apply the filter before the join:
join cc in CompanyCurrency.Where(e => e.CompanyId == 1)
or as part of the join
on new { CurrencyId = c.ID, CompanyId = 1 } equals new { cc.CurrencyId, cc.CompanyId }
For inner joins it doesn't really matter, but for outer join it's important (the same btw applies to SQL queries).

Categories