LINQ query for joining multiple tables and get comma separated values in single row - entity-framework

I have below tables with the values.
Account:
Id
Name
Email
101
Nasir Uddin
nasir#email.com
Role:
Id
Title
101
Admin
102
Operator
AccountRole:
AccountId
RoleId
101
101
101
102
Now I want to write a linq to have the result like below:
UserAccount
AccountId
Name
Email
Roles
101
Nasir Uddin
nasir#email.com
Admin, Operator
To get the above result I have written the below query in LINQ. But it does not get the expected result.
var userAccount1 = (from account in _db.Accounts
join accountRole in _db.AccountRoles on account.Id equals accountRole.AccountId
join role in _db.Roles on accountRole.RoleId equals role.Id
select new UserAccountInfo
{
AccountId = account.Id,
Name = account.UserFullName,
Email = account.Email,
Roles = string.Join(",", role.Title)
});

At last I found my answer. The results can be achieved in different ways. Examples are given below:
var answer1 = (from account in userAccounts
join accountRole in accountRoles on account.Id equals accountRole.AccountId
join role in roles on accountRole.RoleId equals role.Id
select new UserAccount
{
AccountId = account.Id,
Name = account.Name,
Email = account.Email,
Roles = role.Title
}).ToList().GroupBy(x => new { x.AccountId, x.Name, x.Email }).Select(y => new UserAccount
{
AccountId = y.Key.AccountId,
Name = y.Key.Name,
Email = y.Key.Email,
Roles = string.Join(", ", y.Select(a => a.Roles))
}).ToList();
----------------------------------------------------------------------------
var answer2 = (from account in userAccounts
join accountRole in accountRoles on account.Id equals accountRole.AccountId
join role in roles on accountRole.RoleId equals role.Id
group new { account, role } by new { account.Id, account.Name, account.Email } into ag
select new UserAccount
{
AccountId = ag.Key.Id,
Name = ag.Key.Name,
Email = ag.Key.Email,
Roles = string.Join(", ", ag.Select(x=> x.role.Title))
}).ToList();
----------------------------------------------------------------------------
var answer3 = (from account in userAccounts
let roles1 = from accountRole in accountRoles
join role in roles on accountRole.RoleId equals role.Id
where accountRole.AccountId == account.Id
select role
select new UserAccount
{
AccountId = account.Id,
Name = account.Name,
Email = account.Email,
Roles = string.Join(", ", roles1.Select(x => x.Title))
}).ToList();

The below gives you the expected result using Lambda Expression (not Query Expression) based on the information provided in your post (and some assumptions since I could not find e.g. UserFullName in any of your tables)
Note: I'm also convinced there is a more efficient way to do this, but it is a starting point if nothing else.
(Here is working .NET Fiddle of the below: https://dotnetfiddle.net/aGra15):
// Join the AccountRoles and Roles together and group all Titles for
// a given AccountId together
var groupedAccountRoles = AccountRoles.GroupJoin(Roles, i => i.RoleId, o => o.Id, (o, i) => new {o, i})
.Select(x => new {AccountId = x.o.AccountId, Titles = string.Join(",", x.i.Select(y => y.Title))});
// Perform another GroupJoin to group by AccountId and Join to groupedAccountRoles table. Then `string.Join()`
var userAccount1 = Accounts.GroupJoin(AccountRoles, acc => acc.Id, accrol => accrol.AccountId,
(o, i) => new {o, UserAccountRoles = i})
.GroupJoin(groupedAccountRoles, ii => ii.o.Id, oo => oo.AccountId,
(ii, oo) => new UserAccountInfo
{
AccountId = ii.o.Id,
Email = ii.o.Email,
Name = ii.o.Name,
Roles = string.Join(",", oo.Select(x => x.Titles))
});
This will give the following output:

Related

Linq query select property list with join

I'm trying to get a list in a linq with joins and get all events for specific user.
This is the query I have so far:
var runnerObject = from r in _context.Runners
join re in _context.RunnerEvents
on r.RunnerId equals re.RunnerId
join e in _context.Events
on re.EventId equals e.EventId
where r.RunnerId == runnerId
select new RunnerVM
{
RunnerId = r.RunnerId,
FirstName = r.FirstName,
LastName = r.LastName,
UserId = r.UserId,
Events = //get all events in Events table for the runnerId
};
Events should be all entries from Events table for that runner, based on their id which is joined in the RunnerEvents table. How can I get that?
Something like this?
var runnerObject = from r in _context.Runners
join re in _context.RunnerEvents
on r.RunnerId equals re.RunnerId
join e in _context.Events
on re.EventId equals e.EventId
where r.RunnerId == runnerId
select new RunnerVM
{
RunnerId = r.RunnerId,
FirstName = r.FirstName,
LastName = r.LastName,
UserId = r.UserId,
Events = r.Events.Select(e => new Event { }).ToList()
};

How to Include a count(*) as additional result field of a query in LINQ

Given a table of players (users) with several fields. One of this is their rating with respect other players.
I'd like to implement via LINQ following SQL query:
SELECT p.*,
(select COUNT(*) from Users where (Rating > p.Rating)) as Rank
FROM Users as p
ORDER BY p.Rating DESC
In other words, last field (RANK) should give the rank of each user with respect the others:
Id Username ... Rating Rank
43 player41 ... 1002,333 0
99 player97 ... 1002 1
202 player200 ... 1002 1
53 player51 ... 1000,667 2
168 player166 ... 1000,667 2
56 player54 ... 1000 3
32 player30 ... 999,342 4
This attempt does not work:
var q = from u in Users
orderby u.Rating descending
group u by u.Id into g
select new
{
MyKey = g.Key,
User = g.First(),
cnt = Users.Count(uu => uu.Rating > g.First().Rating) + 1
};
Just for your knowledge, note that the table Users is mapped to a EF entity named User with a 'NotMapped' int? field named Rank where I'd like to manually copy the rank:
class User {
...
[NotMapped]
public virtual int? Rank { get; internal set; }
}
You'll want something like:
var rankedUsers = db.Users
.Select(user => new
{
User = user,
Rank = db.Users.Count(innerQueryUser => innerQueryUser.Rating > user.Rating)
})
.ToList();
Which will give you a list of users and their Rank as an anonymous type.
You'll then be able to do something like:
List<User> users = rankedUsers.Select(rankedUser=>
{
rankedUser.User.Rank = rankedUser.Rank;
return rankedUser.User;
})
.ToList();
Try this:
var q = (from u in Users
select new
{
UserId = u.Id,
UserObj = u,
Rank = (from u1 in Users where u1.Rating>u.Rating select u1).Count()
}).ToList();

Can't add/sum string values in IEnumerable

The ultimate goal is to display a GrandTotal column using Highcharts. The GrandTotal should be the sum of TotalAmount for a given Offer id. TotalAmount is a string and the values are like $10.00 or 10.00. GrandTotal is an int, but could easily be changed. Here is what I have done so far.
Step 1) Convert the two IEnumerable lists into their ViewModel counterparts. I set GrandTotal to 0 here because I don't know the amount.
var offersConvert = offers
.Select(o => new OfferSummaryViewModel
{
Id = o.Id,
Name = o.Name,
Created = o.Created,
Shares = o.Shares,
Redemptions = o.Redemptions,
GrandTotal = 0
})
.ToList();
var sharedOffersConvert = sharedOffers
.Select(s => new SharedOfferViewModel
{
OfferId = s.OfferId,
//TotalAmount = s.TotalAmount.Replace("$", string.Empty).Replace(",", string.Empty).Trim()
TotalAmount = s.TotalAmount
})
//.Where(i => i.TotalAmount != null)
.ToList();
Step 2) Join the two lists on the Id of the Offer.
var data = offersConvert
.Join(sharedOffersConvert,
o => o.Id,
s => s.OfferId,
(o, s) => new { offersConvert = o, sharedOffersConvert = s })
.Select(o => new
{
Id = o.offersConvert.Id,
Created = o.offersConvert.Created,
Shares = o.offersConvert.Shares,
Redemptions = o.offersConvert.Redemptions,
Name = o.offersConvert.Name,
OfferId = o.sharedOffersConvert.OfferId,
TotalAmount = o.sharedOffersConvert.TotalAmount,
//GrandTotal = Convert.ToInt32(o.sharedOffersConvert.TotalAmount.Replace("$", string.Empty).Replace(",", string.Empty).Trim())
//GrandTotal = Convert.ToInt32(Convert.ToDouble(o.sharedOffersConvert.TotalAmount, CultureInfo.InvariantCulture))
})
//.Where(o => o.Id == o.OfferId)
.OrderBy(o => o.Created.Add(offset))
.ToList();
As you can tell, I've tried to remove any dollar signs and commas. I've even tried to trim white space in order to get clean data. I am then trying to convert the strings to int values, so I can sum them. Nothing seems to work. I've even tried .GroupBy and other methods (see below). At least with .GroupBy I can get to the .Sum operator. With the other method I run into issues when I can't convert int into ToList(), so I have to try and convert ToString().
.Where(o => o.Id == o.OfferId)
.GroupBy(g => g.Id)
.Select(x => new { GrandTotal = x.Sum(o => o.TotalAmount) })
ERROR in above: can't convert TotalAmount to decimal
.Where(i => i.Id == i.OfferId)
.Sum(i => Convert.ToInt32(Convert.ToDouble(i.TotalAmount, CultureInfo.InvariantCulture)
)).ToString()
Does anyone know how I can add/sum the string values in TotalAmount to get a GrandTotal per Offer id?
Any help us much appreciated. Thanks!
UPDATE: This works, but I really don't understand why and I don't think it is very clean. I really couldn't find many examples where people were joining two lists together and summing one of the columns. This seems pretty common to me, but perhaps it is not.
var data = (from o in offersConvert
join s in sharedOffersConvert on o.Id equals s.OfferId
orderby o.Created.Add(offset)
let k = new
{
Id = o.Id,
Name = o.Name,
Created = o.Created,
Shares = o.Shares,
Redemptions = o.Redemptions
}
group s by k into totals
select new
{
OfferId = totals.Key.Id,
Name = totals.Key.Name,
Created = totals.Key.Created,
Shares = totals.Key.Shares,
Id = totals.Key.Id,
Redemptions = totals.Key.Redemptions,
GrandTotal = totals.Sum((s => s.TotalAmount == null ? Decimal.Zero : Decimal.Parse(s.TotalAmount, NumberStyles.Currency)))
})
.ToList();
You can use
Decimal.TryParse("$10.00", NumberStyles.Currency, CultureInfo.CurrentCulture, out var res);
Or in the context of your LINQ,
GrandTotal = Decimal.Parse(o.sharedOffersConvert.TotalAmount, NumberStyles.Currency)
if o.sahredOffersConvert.TotalAmount may be null,
GrandTotal = (o.sharedOffersConvert.TotalAmount ==null) ? Decimal.Zero : Decimal.Parse(o.sharedOffersConvert.TotalAmount, NumberStyles.Currency)
I'm still puzzled why everyone lately has this odd fascination with the Lambda syntax. And that seems to have been only one of several means you've used to make this more complicated than necessary:
var data = (from o in offers
join s in sharedOffers on o.Id equals s.OfferId
orderby o.Created.Add(offset)
select new
{
Id = o.Id,
Created = o.Created,
Shares = o.Shares,
Redemptions = o.Redemptions,
Name = o.Name,
OfferId = o.OfferId,
TotalAmount = Decimal.Parse(o.TotalAmount, NumberStyles.Currency)
})
.ToList();
Further, as offset seems to be a constant (for the life of this query), adding it to Created isn't going to affect the ordering, and that bit can be removed.
And, since it appears your final output is just the grand totals, it can be reduced further to:
var data = (from o in offers
join s in sharedOffers on o.Id equals s.OfferId
orderby o.Created
group Decimal.Parse(o.TotalAmount, NumberStyles.Currency) by o.id into totals
select new
{
Id = totals.Key,
GrandAmount = totals.Sum()
})
.ToList();
UPDATE: Putting back what I took out... This should work (I don't have your tables, so I can't test it)
var data = (from o in offers
join s in sharedOffers on o.Id equals s.OfferId
orderby o.Created.Add
group o by o.Id into totals
let item = totals.First()
select new
{
Id = item.Id,
Created = item.Created,
Shares = item.Shares,
Redemptions = o.Redemptions,
Name = item.Name,
OfferId = item.OfferId,
GrandTotal = totals.Sum(t=>Decimal.Parse(t.TotalAmount, NumberStyles.Currency))
})
.ToList();

How can I fix linq query to select count of ids with group by?

I want to create this SQL query to linq:
SELECT
COUNT(m.FromUserId) AS Messages,
m.FromUserId AS UserId
FROM
dbo.ChatMessages m
INNER JOIN
dbo.ChatMessagesRead mr ON mr.ChatMessageId = m.ChatMessageId
WHERE
m.ToUserId = #toUserId
GROUP BY
m.FromUserId
I have tried create following linq query:
var messages = from m in _dbContext.ChatMessages
join mread in _dbContext.ChatMessagesRead on m.ChatMessageId equals mread.ChatMessageId
where m.ToUserId == userId
group m by m.FromUserId into g
select new
{
UserId = g.Key,
Messages = g.Count()
};
var messagesList = messages.ToList();
But this doesn't work.
How can I fix this linq query?
I get this exception:
Expression of type 'System.Func2[Microsoft.Data.Entity.Query.EntityQueryModelVisitor+TransparentIdentifier2[Project.BL.ChatMessages.ChatMessages,Project.BL.ChatMessages.ChatMessagesRead],System.Int32]' cannot be used for parameter of type 'System.Func2[<>f__AnonymousType12[Project.BL.ChatMessages.ChatMessages,Project.BL.ChatMessages.ChatMessagesRead],System.Int32]' of method 'System.Collections.Generic.IEnumerable1[System.Linq.IGrouping2[System.Int32,Project.BL.ChatMessages.ChatMessages]] _GroupBy[<>f__AnonymousType12,Int32,ChatMessages](System.Collections.Generic.IEnumerable1[<>f__AnonymousType12[Project.BL.ChatMessages.ChatMessages,Project.BL.ChatMessages.ChatMessagesRead]], System.Func2[<>f__AnonymousType12[Project.BL.ChatMessages.ChatMessages,Project.BL.ChatMessages.ChatMessagesRead],System.Int32], System.Func2[<>f__AnonymousType1`2[Project.BL.ChatMessages.ChatMessages,Project.BL.ChatMessages.ChatMessagesRead],Project.BL.ChatMessages.ChatMessages])'"
I'm facing the same issue and I've found that there is an opened issue on the Entity Framework Core bugtracker
The only workaround for now seems to split the request in two.
var filtered = (from m in _dbContext.ChatMessages
join mread in _dbContext.ChatMessagesRead on m.ChatMessageId equals mread.ChatMessageId
where m.ToUserId == userId
select m).ToList();
var messages = from m in filtered
group m by m.FromUserId into g
select new
{
UserId = g.Key,
Messages = g.Count()
};
you can try this
var res = ctx.MyTable // Start with your table
.GroupBy(r => r.id) / Group by the key of your choice
.Select( g => new {Id = g.Key, Count = g.Count()}) // Create an anonymous type w/results
.ToList(); // Convert the results to List
Your code should work. However I created another version of your query using extension methods.
var messages =
_dbContext
.ChatMessages
.Where(message => message.ToUserId == userId)
.Join(
_dbContext.ChatMessageRead,
message => message.ChatMessageId,
readMessage => readMessage.ChatMessageId,
(m, mr) => m.FromUserId
)
.GroupBy(id => id)
.Select(group =>
new
{
UserId = group.Key,
Messages = group.Count()
}
);
Could you please try it if it also throws the same exception or not?

LINQ query to fetch entities that include custom formatted field of associated entities?

A bit rusty on LINQ
I want to get a single result from related tables for a given user. See schema below.
Each user has one or more roles. I want a list of usernames and a custom string that is a list of their roles in a format such as "Role1 - Role2 - Role3", where the values are the RoleNames associated with the UserRole/Role for that user.
Role
=====
RoleId
RoleCode
RoleName
UserRole
========
UserRoleId
RoleId
UserId
Users
======
UserId
UserName
Testing it out in LINQpad, I can get a list of usernames and their roles, but instead of the RoleName, I want a single field in the result to be a formatted string of ALL the users roles, as mentioned above.
Here is what I have now. How can I construct a list of the roles for each user?
from u in Users
join ur in UserRoles on u.UserId equals ur.UserKey
join r in Roles on ur.RoleKey equals r.RoleId
select new {
u.UserId,
u.UserName,
r.RoleName
}
Add group by UserName to your LINQ query, and use string.Join to format the roles separated by "-".
You can test this in LINQPad -
var Roles = new [] {new{RoleId=1,RoleCode="SU",RoleName="Super User"},new{RoleId=2,RoleCode="PU",RoleName="Power User"}};
var Users = new [] {new{UserId=1,UserName="Bit Shift"},new{UserId=2,UserName="Edward"}};
var UserRoles = new [] {new{UserRoleId=1,RoleId=1,UserId=1},new{UserRoleId=2,RoleId=2,UserId=1},new{UserRoleId=3,RoleId=2,UserId=2}};
var userRoles = from u in Users
join ur in UserRoles on u.UserId equals ur.UserId
join r in Roles on ur.RoleId equals r.RoleId
select new
{
u.UserId,
u.UserName,
r.RoleName
}
into userRole
group userRole by userRole.UserName into userGroups
select new{ UserName=userGroups.Key, Roles = string.Join(" - ", userGroups.Select(ug => ug.RoleName))};
userRoles.Dump();
More succinct version, which includes UserId+UserName in result -
var userRoles = from u in Users
join ur in UserRoles on u.UserId equals ur.UserId
join r in Roles on ur.RoleId equals r.RoleId
group r by u into userGroups
select new{ User=userGroups.Key, Roles = string.Join(" - ", userGroups.Select(r => r.RoleName))};
userRoles.Dump();
When executing against SQL database, you need to the query split into two parts, as String.Join is not supported by LINQ to SQL, like this -
var userRoleGroups = (from u in Users
join ur in UserRoles on u.UserId equals ur.UserId
join r in Roles on ur.RoleId equals r.RoleId
group r by u into userGroups select userGroups)
.ToList(); // This causes SQL to be generated and executed
var userRoles = from userGroups in userRoleGroups select(new{ User=userGroups.Key, Roles = string.Join(" - ", userGroups.Select(r => r.RoleName))});
userRoles.Dump();
Or try using Aggregate instead of String.Join, as you suggested, like this -
var userRoles = from u in Users
join ur in UserRoles on u.UserId equals ur.UserId
join r in Roles on ur.RoleId equals r.RoleId
group r by u into userGroups
select(new{
User=userGroups.Key,
Roles = userGroups.Select(s => s.RoleName).Aggregate((current, next) => current + " - " + next)});
userRoles.Dump();
Group the RoleNames over the UserNames and use String.Join to get the desired result.
from u in Users
join ur in UserRoles on u.UserId equals ur.UserKey
join r in Roles on ur.RoleKey equals r.RoleId
group r.RoleName by u.UserName into grp
select new {
UserName = grp.Key,
Roles = String.Join(" - ", grp)
};
This will not return the UserIds. If they're important for the result, you need to change the code to
.... join as above
group r.RoleName by new {u.UserId, u.UserName} into grp
select new {
grp.Key.UserName,
grp.Key.UserId,
Roles = String.Join(" - ", grp)
};
This will create a grouping key containing both UserId and UserName, so you have that available in your select.