How to left join two queries in linq - c#

I want to left join two queries. In that first query will contains 4 records and second query will contain 0 or more records up to 4. I want to join both these and want to get 4 records. If there are any records in second query then i want to the permission from the second table. I have written code like.
var finalquery = values.GroupJoin(records, i => i.typeid, j => j.typeid, (i, j) => new { i, j }).SelectMany(i => i.j.DefaultIfEmpty(), (i, j) => new {
id = i.i.typeid,
Permission = (j.premission ==null)?null:j.premission
}).ToArray();
But it is throwing an exception

You have NullReferenceException on this condition:
j.premission ==null
That will throw when j is null (your default case).
var finalquery = values.GroupJoin(records,
v => v.typeid,
r => r.typeid,
(v, g) => new { v, g })
.SelectMany(x => x.g.DefaultIfEmpty(),
(x, r) => new {
id = x.v.typeid,
Permission = r == null ? null : r.permission
})
.ToArray();
Or with query syntax (converting to array omitted for beauty):
var finalquery =
from v in values
join r in records on v.typeid equals r.typeid into g
from r in g.DefaultIfEmpty()
select new {
id = v.typeid,
Permission = r == null ? null : r.permission
};

Related

LINQ Query Multiple Group and count of latest record - Oracle DB

I tried to divided Linq queries into 3 (total, success, fail) but so far "Total" Linq query is working fine. Please help me to get "Success", "Fail" columns (it has mulitple statuses and we have to check the last column of each transaction and destination)
Note: you need to group by ProcessTime, TransactionId, Destination and check last column whether it is success or Fail then apply count (we are using oracle as backend)
LINQ for Total count
var query = (from filetrans in context.FILE_TRANSACTION
join route in context.FILE_ROUTE on filetrans.FILE_TRANID equals route.FILE_TRANID
where
filetrans.PROCESS_STRT_TIME >= fromDateFilter && filetrans.PROCESS_STRT_TIME <= toDateFilter
select new { PROCESS_STRT_TIME = DbFunctions.TruncateTime((DateTime)filetrans.PROCESS_STRT_TIME), filetrans.FILE_TRANID, route.DESTINATION }).
GroupBy(p => new { p.PROCESS_STRT_TIME, p.FILE_TRANID, p.DESTINATION });
var result = query.GroupBy(x => x.Key.PROCESS_STRT_TIME).Select(x => new { x.Key, Count = x.Count() }).ToDictionary(a => a.Key, a => a.Count);
Check this solution. If it gives wrong result, then I need more details.
var fileTransQuery =
from filetrans in context.AFRS_FILE_TRANSACTION
where accountIds.Contains(filetrans.ACNT_ID) &&
filetrans.PROCESS_STRT_TIME >= fromDateFilter && filetrans.PROCESS_STRT_TIME <= toDateFilter
select filetrans;
var routesQuery =
from filetrans in fileTransQuery
join route in context.AFRS_FILE_ROUTE on filetrans.FILE_TRANID equals route.FILE_TRANID
select route;
var lastRouteQuery =
from d in routesQuery.GroupBy(route => new { route.FILE_TRANID, route.DESTINATION })
.Select(g => new
{
g.Key.FILE_TRANID,
g.Key.DESTINATION,
ROUTE_ID = g.Max(x => x.ROUTE_ID)
})
from route in routesQuery
.Where(route => d.FILE_TRANID == route.FILE_TRANID && d.DESTINATION == route.DESTINATION && d.ROUTE_ID == route.ROUTE_ID)
select route;
var recordsQuery =
from filetrans in fileTransQuery
join route in lastRouteQuery on filetrans.FILE_TRANID equals route.FILE_TRANID
select new { filetrans.PROCESS_STRT_TIME, route.CRNT_ROUTE_FILE_STATUS_ID };
var result = recordsQuery
.GroupBy(p => DbFunctions.TruncateTime((DateTime)p.PROCESS_STRT_TIME))
.Select(g => new TrendData
{
TotalCount = g.Sum(x => x.CRNT_ROUTE_FILE_STATUS_ID != 7 && x.CRNT_ROUTE_FILE_STATUS_ID != 8 ? 1 : 0)
SucccessCount = g.Sum(x => x.CRNT_ROUTE_FILE_STATUS_ID == 7 ? 1 : 0),
FailCount = g.Sum(x => failureStatus.Contains(x.CRNT_ROUTE_FILE_STATUS_ID) ? 1 : 0),
Date = g.Min(x => x.PROCESS_STRT_TIME)
})
.OrderBy(x => x.Date)
.ToList();

Lambda syntax. How join by 1 to many keys

i'm trying rewrite query from native sql to linq lambda syntax (not linq query syntax)
LINQ (not work)
var result = _uow.Repository<TableA>().Get().AsNoTracking()
.GroupJoin(
_uow.Repository<TableB>().Get().AsNoTracking(),
a => new { a.TabNotesCodeId, a.TabLabelCodeId },
b => b.ElementNameId
(b, a) => new SubSection
{
SubSectionName = b.CustomValue ?? a.TabLabelCodeId,
SubSectionNote = b.CustomValue ?? a.TabLabelCodeId,
})
.Where(a => a.ResourceId == 1);
SQL
SELECT [SubSectionName] = ISNULL(B.CUSTOMVALUE,A.TABLABELCODEID),
[SubSectionNote] = ISNULL(B.CUSTOMVALUE,A.TABNOTESCODEID)
FROM TableA as A LEFT JOIN
(SELECT CUSTOMVALUE, ELEMENTNAMEID FROM TableB WHERE DISPLAYSETTINGID = 1) as B
ON B.ELEMENTNAMEID IN ( A.TABNOTESCODEID, A.TABLABELCODEID)
WHERE A.RESOURCEID = 1
Q How to rewrite sql ON B.ELEMENTNAMEID IN ( A.TABNOTESCODEID, A.TABLABELCODEID) to lambda syntax
...
a => new { a.TabNotesCodeId, a.TabLabelCodeId },
b => b.ElementNameId
....
(doesn't work)
Normally I would suggest following my SQL conversion rules, but this is sufficiently complex I don't think it would help.
To use query comprehension syntax on a non-equijoin that is a left join, it seems easiest to use lambda syntax to express the join conditions, so I just combined the sub-query with the join conditions:
var ans = from A in TableA
where A.ResourceID == 1
from B in TableB.Where(b => b.DisplaySettingID == 1).Where(b => b.ElementNameID == A.TabNotesCodeID || b.ElementNameID == A.TabLabelCodeID).DefaultIfEmpty()
select new {
SubSectionName = (B.CustomValue ?? A.TabLabelCodeID),
SubSectionNote = (B.CustomValue ?? A.TabNotesCodeID)
};
The lambda equivalent of multiple from clauses to generate a cross join is SelectMany, so converting into lambda syntax:
var ans2 = TableA.Where(a => a.ResourceID == 1)
.SelectMany(a => TableB.Where(b => b.DisplaySettingID == 1).Where(b => b.ElementNameID == a.TabNotesCodeID || b.ElementNameID == a.TabLabelCodeID)
.DefaultIfEmpty(),
(a, b) => new {
SubSectionName = (b.CustomValue ?? a.TabLabelCodeID),
SubSectionNote = (b.CustomValue ?? a.TabNotesCodeID)
}
);
After countless experiments i've found out solution:
_uow.Repository<TableA>().Get().AsNoTracking()
.GroupJoin(
_uow.Repository<TableB>().Get().AsNoTracking().Where(b => b.DisplaySettingId == 1),
a => new { note = a.TabNotesCodeId, label = a.TabLabelCodeId },
b => new { note = b.ElementNameId, label = b.ElementNameId },
(a, b) => new { a,b })
.Where(joinTables => joinTables.a.ResourceId == 1)
.SelectMany(
joinTables => joinTables.b.DefaultIfEmpty(),
(joinTables, b) => new SubSection()
{
LayoutTab = joinTables.a.LayoutTab,
SubSectionName = b.CustomValue ?? joinTables.a.TabLabelCodeId,
SubSectionNote = b.CustomValue ?? joinTables.a.TabNotesCodeId
});

SQL query to LINQ conversion with nested select statements

I want to convert the following query to LINQ:
SELECT LV.* FROM LowerVehicles LV
INNER JOIN (Select VSerial,MAX(updatedOn) MaxUpdatedOn from LowerVehicles group by vserial) LVG
ON LV.VSerial = LVG.VSerial AND LV.updatedOn = LVG.MaxUpdatedOn
Not knowing your entities classes, here is an approximation. You can use query syntax or fluent syntax. Sometimes one is preferable over the other, and in the case of joins and grouping I prefer to use query syntax.
QUERY SYNTAX
var query = from LV in LowerVehicles
join LVG in (
from r in LowerVehicles
group r by r.vserial into g
select new {VSerial = g.Key, MaxUpdatedOn = g.Max(t => t.updatedOn)})
on LV.VSerial equals LVG.Vserial
and LV.updatedOn equals LVG.MaxUpdatedOn
select LV;
FLUENT SYNTAX
var lvg = LowerVehicles.GroupBy(t => t.vserial)
.Select(g => new {
VSerial = g.Key,
MaxUpdatedOn = g.Max(t => t.updatedOn)
});
var query = LowerVehicles.Join(
lvg,
a => new { a.VSerial, a.updatedOn },
b => new { b.VSerial, b.MaxUpdatedOn },
(a, b) => new { LV = a, LVG = b}
)
.Select(t=> t.LV);
Something like this?
Something.LowerVehicles
.Join(something.LowerVehicles.Select(y => new { y.VSerial, updatedOn = y.updatedOn.Max() }).GroupBy(z => z.VSerial),
x => new { x.VSerial, x.updatedOn },
lvg => new { lvg.VSerial, lvg.updatedOn },
(x, y) => x)

Query Users who have all requested records in two tables

Data structure looks like:
User(id)
UserApp(user_id, app_id)
UserSkill(user_id, skill_id)
Using linq-to-sql or EF, how would I construct a query to elegantly return only users who possess every requested app and skill?
In addition, how would I adjust the query to return any user who possesses at least one of the requested apps or skills? Essentially an OR vs AND (above).
UPDATE 1:
So I think we're close. Basically I want to only return users who have ALL the requested apps and skills. If we have two arrays of requested ids for skills and apps:
int[] requestedAppIDs // [1, 2, 3]
int[] requestedSkillIDs // [4, 5, 6]
I would only want to return a user if they have apps 1,2,3 AND skills 4,5,6.
var usersWithAllSelectedAppsAndSkills =
context.Users
.GroupJoin(context.UserApp,
k => k.id,
k => k.user_id,
(o, i) => new { User = o, UserApps = i })
.GroupJoin(context.UserSkill,
k => k.User.id,
k => k.user_id,
(o, i) => new { User = o.User, o.UserApps, UserSkills = i })
.Where(w => !requestedAppIDs.Except(w.UserApps.Select(x => x.app_id).ToArray()).Any() && !requestedSkillIDs.Except(w.UserSkills.Select(x => x.skill_id).ToArray()).Any())
.Select(s => s.User)
.ToList();
Obviously, LINQ does not know how to translate the UserSkills.Select().ToArray()'s in my Where() to SQL. How can I accomplish this?
And, secondarily the OR solution as well (user has any one of the requested apps or skills).
This will do the job as long as the user_id – app_id and user_id – skill_id values in the UserApp and UserSkill tables are unique.
var requestedSkillIDs = new[] { 4, 5, 6 };
var skillCount = requestedSkillIDs.Length;
var requestedAppIDs = new[] { 1, 2, 3 };
var appCount = requestedAppIDs.Length;
using (var context = new TestContext()) {
context.Database.CreateIfNotExists();
var appQuery = context.UserApp.Where(p => requestedAppIDs.Contains(p.AppId))
.GroupBy(p => p.UserId)
.Where(p => p.Count() == appCount);
var skillQuery = context.UserSkill.Where(p => requestedSkillIDs.Contains(p.SkillId))
.GroupBy(p => p.UserId)
.Where(p => p.Count() == skillCount);
var result = from a in appQuery
join s in skillQuery on a.Key equals s.Key
join u in context.Users on s.Key equals u.Id
select u;
var users = result.ToList();
}
Here's one way to do it, I hope i got all the syntax right :)
using (var context = new YourContext())
{
var usersWithAllSkills = context.User
.Where(w => w.id == yourId)
.Join(context.UserApp,
k => k.id,
k => k.user_id,
(o,i) => o)
.Join(context.UserSkill,
k => k.id,
k => k.user_id,
(o,i) => o)
.ToList();
var usersWithAnySkill = context.User
.Where(w => w.id == yourId)
.GroupJoin(context.UserSkill,
k => k.id,
k => k.user_id,
(o,i) => new { User = o, UserSkills = i })
.GroupJoin(context.UserApp,
k => k.User.id,
k => k.user_id,
(o,i) => new { User = o.User, o.UserSkills ,UserApps = i })
.Where(w => w.UserSkills != null || w.UserApps != null)
.Select(s => s.User)
.ToList();
}
For the first case (AND) You just need to make inner join like below:
from t1 in db.UserApp
join t2 in db.UserSkill on t1.user_id equals t2.user_id
where t1.app_id == "someID" && t2.skill_id == "someID"
select new { t1.user_id,t1.user_app_id, t2.user_skill}
For the second case just swap &&(AND) with ||(OR).
There is a more direct way to write the required queries using L2E. To write these queries you have to forget thinking in SQL and start thinking in LINQ.
For the first case, look for users which have all the skills:
var usersWithAll = ctx.Users2.Where(u =>
appIds.All(aid => u.Apps.Any(a => a.AppId == aid))
&& skillIds.All(sid => u.Skills.Any(s => s.SkillId == sid))
);
Translated as: get the users where, for all the appIds the user has al leat an app with that application id and for all skillIds the user has at least one skill with that id
And, for the second case, users which have any of the apps and any of the skills:
var usersWithAny = ctx.Users2.Where(u =>
appIds.Any(aid => u.Apps.Any(a => a.AppId == aid))
&& skillIds.Any(sid => u.Skills.Any(s => s.SkillId == sid))
).ToList();
Translated as: get the users where, for at least one appId the user has an app with that application id and for any skillIds the user has at least one skill with that id
If you run this test class, you'll also see the executed query (please, note that, to do so, I'm using the Log property of Database. I think it's only available from EF6 on).
namespace Tests
{
[TestClass]
public class CheckSeveralRelationsAtOnce
{
[TestMethod]
public void HasAllAppsAndSkills()
{
int[] appIds = {1, 2, 3};
int[] skillIds = {6, 7, 8};
using (var ctx = new MyDbContext())
{
ctx.Database.Log = Console.Write;
var usersWithAll = ctx.Users2.Where(u =>
appIds.All(aid => u.Apps.Any(a => a.AppId == aid))
&& skillIds.All(sid => u.Skills.Any(s => s.SkillId == sid))
).ToList();
Assert.IsNotNull(usersWithAll);
}
}
[TestMethod]
public void HasAnyAppsOrSkill()
{
int[] appIds = { 1, 2, 3 };
int[] skillIds = { 6, 7, 8 };
using (var ctx = new MyDbContext())
{
ctx.Database.Log = Console.Write;
var usersWithAny = ctx.Users2.Where(u =>
appIds.Any(aid => u.Apps.Any(a => a.AppId == aid))
&& skillIds.Any(sid => u.Skills.Any(s => s.SkillId == sid))
).ToList();
Assert.IsNotNull(usersWithAny);
}
}
}
}
I believe the answer by codeworx is correct for getting users with all the skills / apps
As an aside - I answered pretty much the same question recently with a pure SQL solution (SQL Server) - that could be turned into a stored procedure (with a table valued parameter) - See here if interested. This will perform better for a large number of values. Entity framework will turn every skill/app in the list into its own SQL parameter, which is much slower.
Unfortunately Entity framework doesn't really support table valued parameters yet - although you can use the entity framework connection to directly call a stored procedure with a table valued parameter (as per this article
Back to the question at hand...
I'll add the (easier) query to select a user with ANY of the skills & ANY of the apps:
var result = from u in context.Users
join _a in (
from a in context.UserApp
where requestedAppIDs.Contains(a.AppId)
select a.UserId;
) on u.Id equals _a
into aGrp
join _s in (
from s in context.UserSkill
where requestedSkillIDs.Contains(s.SkillId)
select s.UserId;
) on u.Id equals _s
into sGrp
where aGrp.Any()
&& sGrp.Any()
select u;
And just for completeness - the ALL solution again:
var skillCount = requestedSkillIDs.Length;
var appCount = requestedAppIDs.Length;
var result = from u in context.Users
join _a in (
from a in context.UserApp
where requestedAppIDs.Contains(a.AppId)
select a.UserId;
) on u.Id equals _a
into aGrp
join _s in (
from s in context.UserSkill
where requestedSkillIDs.Contains(s.SkillId)
select s.UserId;
) on u.Id equals _s
into sGrp
where aGrp.Count() == appCount
&& sGrp.Count() == skillCount
select u;
and finally - an example where the main query body is fixed, but you can add differing where clauses depending upon the AND/OR requirement
bool onlyReturnWhereAllAreMatched = false;
var skillCount = requestedSkillIDs.Length;
var appCount = requestedAppIDs.Length;
IQueryable<User> result;
var query = from u in context.Users
join _a in (
from a in context.UserApp
where requestedAppIDs.Contains(a.AppId)
select a.UserId;
) on u.Id equals _a
into aGrp
join _s in (
from s in context.UserSkill
where requestedSkillIDs.Contains(s.SkillId)
select s.UserId;
) on u.Id equals _s
into sGrp
select new {u, aCount = aGrp.Count(), sCount = sGrp.Count()};
if (onlyReturnWhereAllAreMatched)
{
result = from x in query
where x.aCount == appCount
&& x.sCount == skillCount
select x.u;
} else {
result = from x in query
where x.aCount > 0
&& x.sCount > 0
select x.u;
}

LINq query throws exception

I am running the following query
var allroles = from l in metaData.Role select l.RoleId;
var personroles = from k in metaData.PersonRole
where k.PersonId == new Guid(Session["user_id"].ToString())
select k.RoleId;
Dictionary<Guid, string> allroleswithnames =
(from l in metaData.Role
select new { l.RoleId, l.Description })
.ToDictionary(u => u.RoleId, u => u.Description);
var avl_roles = from j in allroles.Except(personroles)
select new
{
RoleId = j,
Description = allroleswithnames[new Guid(j.ToString())]
};
clist_avl_roles.DataSource = avl_roles;
clist_avl_roles.DataBind();
The code at code for avl_roles throwing error
Subquery returned more than 1 value. This is not permitted when the subquery
follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
Actually there are multiple rows for roleid with same person id. How do I rewrite the query to handle this situation?
var personId = new Guid(Session["user_id"].ToString());
var personRoles = metaData.PersonRole
.Where(pr => pr.PersonId == personId)
.Select(pr => pr.RoleId);
var avl_roles = from r in metaData.Role
where !personRoles.Contains(r.RoleId)
select new { r.RoleId, r.Description };
Or in single query
var avl_roles = from r in metaData.Role
join pr in metaData.PersonRole.Where(x => x.PersonId == personId)
on r.RoleId equals pr.RoleId into g
where !g.Any()
select new { r.RoleId, r.Description };

Categories