I want to list the most purchased products by users by user ID.
My table looks like this
ID UserId ProductId
1 10 Apple
2 10 Computer
3 10 Computer
4 11 Apple
5 11 Apple
6 11 Computer
6 11 Phone
6 11 Phone
6 11 Phone
6 12 Fax
6 12 Fax
6 12 Phone
the output i wanted:
UserId: 10, MostPurchased: Computer
UserId: 11, MostPurchased: Phone
UserId: 12, MostPurchased: Fax
var mostRequestUsers = await dbContext.UserProducts.Include(x => x.Products)
.GroupBy(x => new { UserId = x.UserId, ProductName = x.Product.Name)
.OrderByDescending(gp => gp.Count())
.Select(g => new { Key = g.Key.UserId, RequestType = g.Key.ProductName }).ToListAsync();
Here is another approach:
var mostRequestUsers = await dbContext.UserProducts
.Include(x => x.Products)
.GroupBy(x => new { x.UserId, x.ProductId })
.Select(g => new
{
MostPurchased = g.Key.ProductId,
g.Key.UserId,
Count = g.Count()
})
.GroupBy(x => x.UserId)
.Select(g => g.OrderByDescending(t => t.Count).First());
Result:
UserId: 10, MostPurchased: Computer, Count: 2
UserId: 11, MostPurchased: Phone, Count: 3
UserId: 12, MostPurchased: Fax, Count: 2
Its kind of a hard one but here is what I came up with
var mostRequestedUsers = await dbContext.UserProducts.Include(x => x.Products).
GroupBy(x => x.UserId). // groupBy users
Select(x =>
{
var mostPurchased = x.GroupBy(y => y.Product.Name). // groupBy ProductID
Select(z => new { Product = z.Key, Count = z.Count() }). // create Product/Count list for each user
OrderBy(x => x.Count).First().Product; // pick the top item in the Product/Count list
return $"Userid: {x.Key}, MostPurchased:{mostPurchased}";
}).ToList();
Related
I am trying to figure out a way to rank items in a list that has duplicated values.
For example:
QTECDE
RANK
40
1
30
2
24
3
18
4
4
5
4
5
3
6
But my code always skips a number when I have a duplicated rank. This what I get:
QTECDE
RANK
40
1
30
2
24
3
18
4
4
5
4
5
3
7 (7 insted of 6)
Here's my code:
var rankedList = oList.OrderByDescending(p => p.QTECDE)
.Select((p, i) => new { Order = 1 + i, lst = p })
.GroupBy(p => new { p.lst.QTECDE })
.SelectMany(g => g.Select(p => new
{
RANK = g.Min(x => x.Order),
NO_ART = p.lst.NO_ART,
QTECDE = p.lst.QTECDE,
LIB_INDEX_FR_SUP = p.lst.LIB_NIVEAU_SUP_FR,
LIB_IMAGE = p.LIB_IMAGE,
}));
Any solutions?
You just need the index of the group not the items:
var rankedList = oList
.OrderByDescending(p => p.QTECDE)
.GroupBy(p => new { p.QTECDE })
.SelectMany((g, groupIndex) => g
.Select(p => new
{
RANK = groupIndex + 1,
NO_ART = p.NO_ART,
QTECDE = p.QTECDE,
LIB_INDEX_FR_SUP = p.LIB_NIVEAU_SUP_FR,
LIB_IMAGE = p.LIB_IMAGE,
}));
You're determining the rank/order on your source items. You want to apply the (item, index) to your SelectMany() instead of your Select().
I have this code:
var lotResults = lotTypes.OrderByDescending(x => x.LotTypeId == 14 || x.LotTypeId == 9 || x.LotTypeId == 15).ThenBy(x => x.Position).ToList();
What I am trying to do is have the results return in the order of LotTypeId (14, then 9, then 15) after that give me the rest of the data in the order by position.
My problem is my results always display 15, 9, then 14 no matter what order I have those or conditions...how do I get it to be 14, then 9, then 14?
You can try like this
var lotResults = lotTypes
.OrderByDescending(x => x.LotTypeId == 14)
.ThenByDescending(x => x.LotTypeId == 9)
.ThenByDescending(x => x.LotTypeId == 15)
.ThenBy(x => x.Position)
.ToList();
A couple of C# 8 alternatives to the accepted answer using remapping of LotTypeId.
With remapping of LotTypeId like this:
14 -> 1
9 -> 2
15 -> 3
others -> 4
we can use a single OrderBy for LotTypeId.
Inline remapping of LotTypeId (least amount of code lines option)
var lotResults = lotTypes
.OrderBy(x => x.LotTypeId switch { 14 => 1, 9 => 2, 15 => 3, _ => 4 })
.ThenBy(x => x.Position)
.ToList();
Using a method for remapping (for reuse across multiple LINQ-expressions)
var lotResults = lotTypes
.OrderBy(x => RemapLotTypeId(x.LotTypeId))
.ThenBy(x => x.Position)
.ToList();
private static int RemapLotTypeId(int lotTypeId) => lotTypeId switch { 14 => 1, 9 => 2, 15 => 3, _ => 4 };
When you have like 5+ remappings, you may not want to maintain the mapped values, only the actual keys to look for, which is 14, 9, 15 in this case. This can be solved using a List. This mapping will be zero-based: 14 -> 0 ... others -> 3
var lotResults = lotTypes
.OrderBy(x => RemapLotTypeIdUsingList(x.LotTypeId))
.ThenBy(x => x.Position)
.ToList();
private static readonly IList<int> LotTypeIds = new List<int> { 14, 9, 15 }.AsReadOnly();
private static int RemapLotTypeIdUsingList(int keyToFind)
{
var sortKey = LotTypeIds.IndexOf(keyToFind);
return sortKey < 0 ? LotTypeIds.Count : sortKey;
}
All approaches are tested and verified on my computer.
The C# / Entity Framework problem:
I have object
Account
{
public string AccountId { get; set; }
public string UserId { get; set; }
public string CurrencyId { get; set; }
}
then I need to return all accounts on "user A" which have same currencyId as accounts for "user B"
This is simple SQL query, but I stuck with EF. This is what I tried
public IQueryable<Account> Test(string userA, string userB)
{
var accountsA = GetAccounts().Where(x => x.UserId == userA);
var accountsB = GetAccounts().Where(x => x.UserId == userB);
return accountsA.Join(
accountsB,
acc1 => acc1.CurrencyId,
acc2 => acc2.CurrencyId,
(acc1, acc2) => acc1
);
}
this query works but return a lot of duplicate accounts for userA.
I tried
public IQueryable<Account> Test(string userA, string userB)
{
var accountsA = GetAccounts().Where(x => x.UserId == userA);
var accountsB = GetAccounts().Where(x => x.UserId == userB);
return accountsA.GroupJoin(
accountsB,
acc1 => acc1.CurrencyId,
acc2 => acc2.CurrencyId,
(acc1, acc2) => acc1
);
}
but it crash with
System.InvalidOperationException
HResult=0x80131509
Message=Processing of the LINQ expression 'DbSet<Account>
.Where(x => x.UserId == "userA").GroupJoin(
outer: DbSet<Account>
.Where(x => x.UserId == "userB"),
inner: acc1 => acc1.CurrencyId,
outerKeySelector: acc2 => acc2.CurrencyId,
innerKeySelector: (acc1, acc2) => acc1)' by 'NavigationExpandingExpressionVisitor' failed. This may indicate either a bug or a limitation in EF Core. See https://go.microsoft.com/fwlink/?linkid=2101433 for more detailed information.
how can I do DISTINCT on EF queries?
public IQueryable<Account> Test(string userA, string userB)
{
var accountsA = GetAccounts().Where(x => x.UserId == userA);
var accountsB = GetAccounts().Where(x => x.UserId == userB);
return accountsA.Where(accountA =>
accountsB.Any(accountB => accountB.CurrencyId == accountA.CurrencyId)
);
}
So user B has zero or more Accounts, where every Account has a CurrencyId.
"I need to return all accounts on "user A" which have same currencyId as the currencyIds of user B"
Apparently we need the CurrencyIds` of user B:
int idUserB = ...
var currencyIdsOfUserB = GetAccounts()
.Where(account => account.Id == idUserB)
.Select(account => account.CurrencyId)
.Distinct(); // only if you expect duplicates
All accounts of user A that have at least one of these currencyIds:
int idUserA:
var result = GetAccounts.Where(account => account.Id == idUserB
&& currencyIdsOfUserB.Contains(account.CurrencyId);
Accounts
Id UserId CurrencyId
01 09 18
02 10 50
03 11 19
04 20 49
05 10 51
06 10 52
07 20 52
08 20 51
09 10 50
10 20 52
User [10] has Accounts 2, 5, 6, 9 with CurrencyIds 50, 51, 52, 50
User [20] has Accounts 4, 7, 8, 10 with CurrencyIds 49, 52, 51, 52
currenCyIdsOfUserB = {50, 51, 52}
Give me all Accounts with UserId equal to [10] and CurrencyId in {50, 51, 52}.
The result will be the Accounts with Id 7, 8, 10
Simple comme bonjour!
ok, I found it. it is
.Distinct()
so the answer will be
var a1 = accountContext.Account.Where(x => x.UserId == "userA");
var a2 = accountContext.Account.Where(x => x.UserId == "userB");
var result = a1.Join(
a2,
acc1 => acc1.CurrencyId,
acc2 => acc2.CurrencyId,
(acc1, acc2) => acc1
)
.Distinct();
I have this query in SQL
select FileName, UploadDate, Status
from MyTable group by FileName, UploadDate, Status
this give me the correct output
FileName UploadDate Status
fuel 1.xls 2020-04-10 17:43:04.857 1
fuel 1.xls 2020-04-10 17:43:04.857 4
fuel 2.xls 2020-04-10 17:43:17.193 4
I can translate this query to LINQ
context.MyTable
.GroupBy(x => new { x.FileName, x.UploadDate, x.Status })
.Select(x => new { x.Key.FileName, x.Key.UploadDate, x.Key.Status });
Now i wanna the same query but an additional column with the count of the 'Status' column
i Accomplish this in SQL with this query
select FileName, UploadDate, Status, count(Status) as 'StatusCount'
from MyTable group by FileName, UploadDate, Status
This give me the correct output
FileName UploadDate Status StatusCount
fuel 1.xls 2020-04-10 17:43:04.857 1 19
fuel 1.xls 2020-04-10 17:43:04.857 4 1
fuel 2.xls 2020-04-10 17:43:17.193 4 20
How to translate this additional "column count" into LINQ?i've tried several times different solutions but without success. Can someone help me please?
If you really mean count:
context.MyTable
.GroupBy(x => new { x.FileName, x.UploadDate, x.Status })
.Select(x => new { x.Key.FileName, x.Key.UploadDate, x.Key.Status,
Count = x.Count() });
If you actually meant Sum:
context.MyTable
.GroupBy(x => new { x.FileName, x.UploadDate, x.Status })
.Select(x => new { x.Key.FileName, x.Key.UploadDate, x.Key.Status,
Sum = x.Sum(e => e.Status) });
You just need to call the Count method on each IGrouping<> object in the Select method :
context.MyTable
.GroupBy(x => new { x.FileName, x.UploadDate, x.Status })
.Select(x => new
{
FileName = x.Key.FileName,
UploadDate = x.Key.UploadDate,
Status = x.Key.Status,
Count = x.Count()
});
This question already has an answer here:
Grouping a generic list via LINQ in VB.NET
(1 answer)
Closed 7 years ago.
I have the following model:
public class Result
{
public int Id { get; set; }
public string Company { get; set; }
}
And I have a List with data similar to the following:
Id Company
=================
21 Microsoft
22 Apple
22 IBM
23 Microsoft
How can I use Linq to give me the distinct ID's, concatenating the Company column with a delimiter?
My output should be:
Id Company
=================
21 Microsoft
22 Apple, IBM
23 Microsoft
You can use GroupBy and String.Join:
IEnumerable<Result> query = results.GroupBy(r => r.Id)
.Select(g => new Result
{
Id = g.Key,
Company = String.Join(", ", g.Select(r => r.Company))
});
A slightly different take on Tim's excellent answer if there are duplicate records in your source and you don't want Company names repeated in the same Field:
var data = new List<Result>
{
new Result {Id = 21, Company = "Microsoft"},
new Result {Id = 22, Company = "Apple"},
new Result {Id = 22, Company = "IBM"},
new Result {Id = 23, Company = "Microsoft"},
new Result {Id = 23, Company = "Microsoft"}
};
var x = data.GroupBy(d => d.Id)
.Select(d => new Result { Id = d.Key,
Company =
string.Join(",", d.Select(s => s.Company).Distinct())});
var groupesList = result.GroupBy(x => x.Id,
(key, val) => new { Key = key, Value = string.Join(",", val.Select(r => r.Company)} ).ToList();
then you can call Key(unuque) or Value by key for all inform for example all ID
Simply use GroupBy and String.Join methods:-
List<Result> result = Results.GroupBy(x => x.id)
.Select(x => new Result
{
Id = x.Key,
Company = String.Join(",",x.Select(z => z.Company)
}).ToList();