Linq count trouble - c#

I have two tables:
tblBadge
-------
ID
Name
tblBadgeUsers
-------
BadgeID
UserID
A user can have many badges, all the relationships are set up properly in the SQL database.
I'm trying to return a list of all the badges a user has, along with the total number of those badges. This is about as far as I can get, I'm getting pretty confused. I want to return the tblBadge data along with an extra column showing the total of that badge awarded.
This doesn't work but is my attempt:
var q = db.tblBadgeUsers
.Where(c => c.UserID == UserID)
.GroupBy(c => c.BadgeID)
.Select(c => new { BadgeCount = c.Count(), Record = c.tblBadge });

Given that a badge only really has a badge ID and a user, it sounds like you just need to get the badge ID and the count of that badge for the use - which means just getting the key for the group:
var q = db.tblBadgeUsers
.Where(c => c.UserID == UserID)
.GroupBy(c => c.BadgeID)
.Select(c => new { BadgeCount = c.Count(), BadgeId = c.Key });
If there's more information on each badge, you might want to do:
var q = db.tblBadgeUsers
.Where(c => c.UserID == UserID)
.GroupBy(c => c.BadgeID)
.Select(c => new { BadgeCount = c.Count(), BadgeId = c.Key, Badges = c });
Then you could do:
foreach (var badgeType in q)
{
Console.WriteLine("{0}: {1}", badgeType.BadgeId, badgeType.BadgeCount);
foreach (var badge in q.Badges)
{
// Deal with the badge information
}
}

Related

Is there a way to query a temporal table to see if any history exists?

My EF Core 6 application is using SQL Temporal tables.
I have an existing query that returns a number of rows based on a filter:
var results = Context.MyTable
.Where(cn => cn.Name == "Fred")
.Skip(5)
.Take(10)
.Select(x=>new { FirstName = x=>x.Name, Dob = x=>x.Dob });
What I want to do, is add a property in the response that shows if there is any history for each row in the query, so I can return it to the UI, which will show paging controls for stepping through the history.
var results = Context.MyTable
.Where(cn => cn.Name == "Fred")
.Skip(5)
.Take(10)
.Select(x=>new { FirstName = x=>x.Name, Dob = x=>x.Dob, HasHistory = x.???? });
Try the following:
var results = Context.MyTable
.Where(cn => cn.Name == "Fred")
.Skip(5)
.Take(10)
.Select(x => new
{
FirstName = x.Name,
Dob = x.Dob,
HasHistory = Context.MyTable.TemporalAll().Count(h => h.Id == x.Id) > 1
});

Include count = 0 in linq results

I have a table having TeamName and CurrentStatus fields. I am making a linq query to get for each team and for each status the count of records:
var teamStatusCounts = models.GroupBy(x => new { x.CurrentStatus, x.TeamName })
.Select(g => new { g.Key, Count = g.Count() });
The results of this query returns all the counts except where count is 0. I need to get the rows where there is no record for a specific team and a specific status (where count = 0).
You could have a separate collection for team name and statuses you are expecting and add the missing ones to the result set
//assuming allTeamNamesAndStatuses is a cross joing of all 'CurrentStatus' and 'TeamNames'
var teamStatusCounts = models.GroupBy(x => new { x.CurrentStatus, x.TeamName })
.Select(g => new { g.Key, Count = g.Count() })
.ToList();
var missingTeamsAndStatuses = allTeamNamesAndStatuses
.Where(a=>
!teamStatusCounts.Any(b=>
b.Key.CurrentStatus == a.CurrentStatus
&& b.Key.TeamName == a.TeamName))
.Select(a=>new {
Key = new { a.CurrentStatus, a.TeamName },
Count = 0
});
teamStatusCounts.AddRange(emptyGroups);
I've created a fiddle demonstrating the answer as well
I would select the team names and status first:
var teams = models.Select(x => x.TeamName).Distinct().ToList();
var status = models.Select(x => x.CurrentStatus).Distinct().ToList();
You can skip this if you know the list entries already.
Then you can select for each team and each state the number of models:
var teamStatusCounts = teams.SelectMany(team => states.Select(state =>
new
{
TeamName = team,
CurrentStatus = state,
Count = models.Count(model =>
model.TeamName == team && model.CurrentStatus == state)
}));

Reducing the verbosity of Linq Joins?

I'm currently working through figuring out how to replace all of our cross-database joins with Entity Framework and Linq, I've managed to get a section of my code working, but what really makes it a bit annoying is the verbosity and complexity of the joins. Is there any method to simplify the code some, or am I stuck with long, verbose, messy code?
An example:
using (var context = new CustomerContext(CustomerID))
using (var e = new eContext())
{
var globalUserList = e.GlobalLoginCustomerBridges
.Join(e.GlobalLogins,
glcb => glcb.glcbr_gl_id,
gl => gl.gl_id,
(glcb, gl) => new { glcb, gl })
.Where(n => n.glcb.glcbr_customer_id == CustomerID)
.Select(n => new User2
{
ID = (int)n.glcb.glcbr_user_id,
GlobalLogin = n.gl.gl_login_name,
GUID = n.gl.gl_GUID
}).ToList();
var customer = e.Customers
.Join(e.DatabaseConnectionStrings,
c => c.DatabaseConnectionID,
d => d.DatabaseConnectionID,
(c, d) => new { c, d })
.Select(n => new Customer2
{
ID = n.c.CustomerID,
Name = n.c.CustomerName,
DatabaseConnectionName = n.d.DatabaseConnectionName,
DatabaseConnectionString = n.d.DatabaseConnectionString1,
GUID = n.c.cust_guid,
}).ToList().FirstOrDefault(n => n.ID == CustomerID);
var orgs = context.Organizations
.Select(o => new Organization2
{
ID = o.org_id,
Name = o.org_name,
}).ToList();
var users = context.Users
.Select(n => new User2
{
ID = n.UserID,
FirstName = n.UserFirstName,
}).ToList();
var userList = users
.Join(globalUserList,
u => u.ID,
gl => gl.ID,
(u, gl) => new { u, gl })
.Join(context.OrganizationObjectBridges,
u => u.u.ID,
oob => oob.oob_object_id,
(u, oob) => new { u, oob })
.Where(o => o.oob.oob_object_type_id == 9)
.Select(n => new User2
{
ID = n.u.u.ID,
GlobalLogin = n.u.gl.GlobalLogin,
FirstName = n.u.u.FirstName,
GUID = n.u.gl.GUID,
Customer = customer,
Organization = orgs.FirstOrDefault(o => o.ID == n.oob.oob_org_id)
}).Where(n => !isDisabled != null && n.Disabled == isDisabled).ToList();
return userList;
}
In the snippet above, I've removed ~80% of the code as most of it is simply field mappings, but it's considerably longer than what's displayed.
It seems that the first 2 queries don't need to be joined, because they belong to the same database. Couldn't you use navigation properties instead? Remember to only use join between objects which don't have a physical (navigation properties) relation.
About the last query, you could use linq queries instead of chain methods( which makes the code more readable, in my opinion). It would be something like this:
var userList = from user in users
join gul in globalUserList on user.ID = gul.ID
join oob in context.OrganizationObjectBridges on user.ID = oob.oob_object.id
where oob.oob_object_type_id == 9
select new User2
{
ID = user.ID,
GlobalLogin = gul.GlobalLogin,
FirstName = user.FirstName,
GUID = gul.GUID,
Customer = customer,
Organization = orgs.FirstOrDefault(o => o.ID == n.oob.oob_org_id)
};
Untested code, I'm sure it won't work. I'm just giving you some ideas.

Linq GroupJoin add default entries

var storeIds = repository.Get<Store>()
.Select(s => s.Id)
.ToList();
var storeReceipts = repository.Get<Receipt>()
.Where(r => DbFunctions.TruncateTime(r.LogDate) == today)
.GroupBy(r => r.StoreId)
.Select(g => new { Id = g.Key, Sales = g.Sum(r => r.TotalPrice) })
.GroupJoin(storeIds, x => x.Id, s => s, (x, s) => x ?? new { Id = s, Sales = 0 });
Basically I want the GroupJoin to add an entry to the sequence for any Store that doesn't have Receipt records.
My syntax above with the ?? doesn't compile (even if it did I am not sure its correct).
If you want to join the two tables, you may want to try this.
var storeSales = from s in repository.Get<Store>()
join r in repository.Get<Receipt>() on s.Id equals r.StoreId into g
select new {
StoreId = s.Id,
Sales = g.Sum(x => (decimal?)x.TotalPrice) ?? 0
};
It selects from the Stores first, so that you will get an entry for each store. It will next join the Receipts by matching store id into a group g that joins the two.
That allows the selection of your output shape, one item per store. In this case, the Id of the store as StoreId, and the sum of the TotalPrice values for each receipt as the Sales are selected.
If there were no receipts for a store, this sum will end up being 0.

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;
}

Categories