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

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();

Related

How to more efficiently materialize related items using EF and LINQ

A newbie asks...
Part 1
Suppose I have 3 classes (and their equivalent SQL tables) :
Product
{
int Id;
List<Keyword> Keywords;
List<Serial> Serials;
}
Keyword
{
int Id;
int ProductId; // FK to Product
string Name;
}
Serial
{
int Id;
int ProductId; // FK to Product
string SerialNumber;
}
When loading PRODUCT == 123, the we could do this:
item = db.Products.FirstOrDefault(p => p.Id == 123);
item.Keywords = db.Keywords.Where(p => p.ProductId == 123).ToList();
item.Serials = db.Serials.Where(p => p.ProductId == 123).ToList();
which is 3 SQL statements.
Or we could do this:
from product in db.Products.AsNoTracking()
join link1 in Db.Keywords.AsNoTracking()
on product.Id equals link1.ProductId into kwJoin
from keyword in kwJoin.DefaultIfEmpty()
join link2 in Db.Serials.AsNoTracking()
on product.Id equals link2.ProductId into serJoin
from serial in serJoin.DefaultIfEmpty()
where product.Id == 123
select new { product, keyword, serial };
which gives 1 SQL statement but produces far too many rows (number of keywords x number of serials) that need to be coalesced together
Both seem less than efficient. Is there a better way?
Part 2
As another question, but using the same example, when we have a join like so:
from product in db.Products.AsNoTracking()
join link1 in Db.Keywords.AsNoTracking()
on product.Id equals link1.ProductId into kwJoin
from keyword in kwJoin.DefaultIfEmpty()
select new { product, keyword };
Is there a way to assign the keywords directly in the product, in select statement?
select new { product, product.Keywords = keyword };
Thanks for any help!
If the FKs exist, depending on how you have setup your DB context, the properties will automatically be fetched. No joins required. Part 1 query is simple as it has a filter. Part 2 might have issues depending on how many records needs to be fetched from the database. You can map the fields to anonymous objects(or DTOs) after the fact that you have keyword objects for each product in the list.
Part 1
item = db.Products
.Include(p=>p.Keywords)
.Include(s=>s.Serials)
.Where(p => p.Id == 123)
.FirstOrDefault();
Part 2
products = db.Products.Include(p=>p.Keywords).ToList();

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

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:

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 implement this relationship in Eloquent?

I have SQL this query:
SELECT f.follower_id, u.fname, u.lname
FROM followers f
INNER JOIN users u ON f.follower_id = u.id
WHERE f.user_id = $user_id
AND u.status = 1
ORDER BY fname, lname
LIMIT 10
I have two models: User and Follower.
A user can have many followers, and each follower has its own user data. I want to be able to get all of a user's followers (who have a status of 1) by doing something like this:
$followers = User::get_followers();
Add this to your User model:
public function followers()
{
return $this->belongsToMany(User::class, 'followers', 'user_id', 'follower_id')
->where('status', 1)
->orderBy('fname')
->orderBy('lname');
}
Then you can access them like this:
$followers = $user->followers;

Distinct and group by at the same time in LINQ?

Let say I have the following classes:
Product { ID, Name }
Meta { ID, Object, Key, Value }
Category { ID, Name }
Relation {ID, ChildID, ParentID } (Child = Product, Parent = Category)
and some sample data:
Product:
ID Name
1 Chair
2 Table
Meta
ID Object Key Value
1 1 Color "Red"
2 1 Size "Large"
3 2 Color "Blue"
4 2 Size "Small"
Category
ID Name
1 Indoor
2 Outdoor
Relation
ID ChildID ParentID
1 1 1
2 1 2
3 2 1
Can we use Distinct and Group by to produce the following format (ProductDetail)
ID=1,
Name=Chair,
Parent=
{
{ ID=1, Name="Indoor" },
{ ID=2, Name="Outdoor" }
},
Properties { Color="Red", Size="Large" }
ID=2,
Name=Table,
Parent=
{
{ ID=1, Name="Indoor"}
},
Properties { Color = "Blue", Size = "Small" }
which we can get the "Color" value of the first item by using
ProductDetails[0].Properties.Color
Any helps would be appreciated!
No, you can't do this based on what you've said - because "Color" and "Size" are part of the data, rather than part of the model. They're only known at execution time, so unless you use dynamic typing, you're not going to be able to access it by Properties.Color.
You could, however, use Properties["Color"] potentially:
var query = from product in db.Products
join meta in db.Meta
on product.ID equals meta.Object
into properties
select new { Product = product,
Properties = properties.ToDictionary(m => m.Key,
m => m.Value) };
So for each product, you'll have a dictionary of properties. That works logically, but you may need to tweak it to get it to work in the entity framework - I don't know how well that supports ToDictionary.
EDIT: Okay, I'll leave the above up as the "ideal" solution, but if EF doesn't support ToDictionary, you'd have to do that part in-process:
var query = (from product in db.Products
join meta in db.Meta
on product.ID equals meta.Object
into properties
select new { product, properties })
.AsEnumerable()
.Select(p => new {
Product = p.product,
Properties = p.properties.ToDictionary(m => m.Key,
m => m.Value) });
I just came across this question while learning LINQ, but I wasn't satisfied that Jon's output matched the question (sorry Jon). The following code returns a List of anonymously-typed objects that better match the structure of your question:
var ProductDetails = (from p in Product
let Parents = from r in Relation
where r.ChildID == p.ID
join c in Category on r.ParentID equals c.ID
into RelationCategory
from rc in RelationCategory
select new
{
rc.ID,
rc.Name
}
join m in Meta on p.ID equals m.Object into ProductMeta
select new
{
p.ID,
p.Name,
Parent = Parents.ToList(),
ProductMeta
})
.AsEnumerable()
.Select(p => new
{
p.ID,
p.Name,
p.Parent,
Properties = p.ProductMeta
.ToDictionary(e => e.Key, e => e.Value)
}).ToList();
Credit goes mostly to Jon Skeet and the Visual Studio debugger ;)
I realise that you've probably moved on by now but hopefully this might help someone else looking to learn LINQ, as I was.