Always select true/higher value in LINQ statement group by (Multiple columns) - c#

Say I've got 2 lists that look something like
ID | IsChipCollected | IsShirtCollected | IsPackCollected
A 0 1 0 1
1 0 1 0
2 0 0 1
B 0 0 1 0
1 0 0 1
2 1 1 1
Basically I need to compare and merge the 2 lists and if a certain flag has been set to say true in B, i should select it instead of A
I know how to achieve this with a single column, but I'm struggling wrapping my head around doing it in a scenario with 2+ columns
var result = A.Concat(B)
.GroupBy(x => x.Id)
.Select(g => g.OrderByDescending(x => x.IsShirtCollected).First())
I'll update my code sample if i make any progress on this, (as I'm currently still hacking away :P)
Expected Result
ID | IsChipCollected | IsShirtCollected | IsPackCollected
0 1 1 1
1 0 1 1
2 1 1 1

var result = A.Concat(B)
.GroupBy(x => x.Id)
.Select(g => new {//or new <A and B's generic type>, then change the underCode with proper case
id = g.Key,
isShipCollected = g.Max(m => m.IsChipCollected),
isShirtCollected = g.Max(m => m.IsShirtCollected),
isPackCollected = g.Max(m => m.IsPackCollected)
});
Edit
Following Servy's (good) idea (changing 1 / 0 to boolean values)
var result = A.Concat(B)
.GroupBy(x => x.Id)
.Select(g => new {
id = g.Key,
isShipCollected = g.Any(m => m.IsChipCollected == 1),
isShirtCollected = g.Any(m => m.IsShirtCollected == 1),
isPackCollected = g.Any(m => m.IsPackCollected == 1)
});

Not sure if it would be faster/slower than a concat/groupby, but technically the operation that you're performing is a Join, so it makes more sense in terms of readability to actually perform that join.
var query = listA.Join(listB, item => item.ID, item => item.ID,
(a, b) => new
{
ID = a.ID,
IsChipCollected = a.IsChipCollected | b.IsChipCollected,
IsShirtCollected = a.IsShirtCollected | b.IsShirtCollected,
IsPackCollected = a.IsPackCollected | b.IsPackCollected,
});

Related

Determine Duplicate data using LINQ to EF

I have a dataset that i want to groupby to determine duplicate data.
Example i have a dataset that looks like this.
|id | Number | ContactID
1 1234 5
2 9873 6
3 1234 7
4 9873 6
Now i want to select data that has more than one occurrence of Number but only if the ContactID is not the same.
So basically return
| Number | Count |
1234 2
Any help would be appreciated using LINQ to EF, thanks.
Update:
All thanks to #DrCopyPaste, as he told me that I misunderstood your problem. Here is the correct solution:-
var result = from c in db.list
group c by c.Number into g
let count = g.GroupBy(x => x.ContactID).Where(x => x.Count() == 1).Count()
where count != 0
select new
{
Number = g.Key,
Count = count
};
Sample Fiddle.
This query avoids making a custom IEqualityComparer as if I remember correctly don't think they play well with EF.
var results = data.GroupBy(number => number.Number)
.Where(number => number.Count() > 1)
.Select(number => new
{
Number = number.Key,
Count = number.GroupBy(contactId => contactId.ContactId).Count(x => x.Count() == 1)
})
.Where(x => x.Count > 0).ToList();
Fiddle
It does an initial GroupBy to get all Numbers that are duplicated. It then selects a new type that contains the number and a second GroupBy that groups by ContactId then counts all groups with exactly one entry. Then it takes all results whose count is greater than zero.
Have not testing it against EF, but the query uses only standard Linq operators so EF shouldn't have any issues translating it.
Another way of doing this(using 1 level of grouping):
var results = data
.Where(x => data.Any(y => y.Id != x.Id && y.Number == x.Number && y.ContactId != x.ContactId))
.GroupBy(x => x.Number)
.Select(grp => new { Number = grp.Key, Count = grp.Count() })
.ToList();
Fiddle

LINQ get Highest value and + 1

I'm newbie to LINQ.
I will like to get know what's the highest value for 'Question Position' and i want increase it by 1 for new Question and save it into database from MVC 4 view.
My db data : (highest position value is 2)
====================
Question | Position
====================
Q1 | 1
Q2 | 2
After added new Question : ( increment the highest position (2) + 1 )
====================
Question | Position
====================
Q1 | 1
Q2 | 2
Q3 | 3
My Code :
var query =
db.SURV_Question_Model
.Where(r => r.Question_Survey_ID == viewModel.Survey_ID)
.GroupBy(r => new { r.Question_Position })
.Select(grp => grp.OrderByDescending(i => i.Question_Position).FirstOrDefault());
After i get the highest value from query, can i do something like below?
* int i = query.Question_Position + 1 ???
Appreciate your guidance.
You can use Max:
var maxId = db.SURV_Question_Model
.Where(r => r.Question_Survey_ID == viewModel.Survey_ID)
.Max(x => x.Position);
But, if there is not any record it will throw an exception. So, it will be better to change your code as:
var maxId = ...Max(x => (int?)x.Position) ?? 0;
You don't need the GorupBy method, this should be enough:
var maxId =
db.SURV_Question_Model
.Where(r => r.Question_Survey_ID == viewModel.Survey_ID)
.OrderByDescending(x => x.Position)
.FirstOrDefault());
I actually don't know the usage of the where condition, I left there anyway.

Entity framework Group by query

Let's say I have a table with the following data:
Id | title | image | page
-------------------------
1 test a.jpg 1
2 test b.jpg 2
3 test 1 c.jpg 1
4 test 1 d.jpg 2
How would I go about grouping the data by title and retrieving the first results. Like so:
Id | title | image | page
-------------------------
1 test a.jpg 1
3 test 1 c.jpg 1
What I have tried so far but without luck is:
var result = _db.Records.Select(r => new Records
{
Id = r.Id,
title = r.title,
image = r.image,
page = r.page
}).OrderByDescending(x => x.Id)
.GroupBy(x => x.title)
.Select(x => x.First()).AsQueryable();
Am I going about this the right way? Any help appreciated.
Why order by and why return AsQueryable? This is what I have done. If you must return a queryable, appending AsQueryable() will still work.
Records.GroupBy (r => r.Title)
.Select (r =>r.First ())
The first Select doesn't seem to do anything. You already have Records and you're selecting Records.
The second Select is also not needed. Instead of calling x => x.First() why not call First() ? Or am I missing something?
var result = _db.Records
//.OrderByDescending(x => x.Id)
.GroupBy(x => x.title)
.First();
Edit: the OrderBy is doing work that is negated (somewhat) by the GroupBy
Edit 2: The above will only get the first group. So the x => x.First() was correct:
var result = _db.Records
.GroupBy(x => x.title)
.Select(group => group.First());
var results
= _db.Records.GroupBy(
i => i.title,
(key, group) => group.First()
)
Hope this helps

linq query selecting all elements of certain value but none of another value

I have a table that looks somewhat like this:
| FruitID | BasketID | FruitType |
I'm passing in the query a list of BasketIDs and I want the list of FruitIDs that are within the BasketID AND that are only of a certain FruitType (values can only 1 or 2).
This is what I have:
var TheQuery = (from a in MyDC.MyTable
where TheBasketIDs.Contains(a.BasketID) &&
a.FruitType == 1 // need help here
select a.FruitID).ToList();
I'm having some difficulty expressing the second where condition. I want the FruitIDs where all the FruitType are all 1s and none are 2s.
| FruitID | BasketID | FruitType |
| 23 | 2 | 1 |
| 23 | 5 | 1 |
| 19 | 2 | 1 |
| 19 | 5 | 2 |
For instance, Fruit 23 is ok because its FruitType is always 1 but Fruit 19 isn't ok because it also has a FruitType of 2, even if the list of TheBasketIDs I'm passing in doesn't contain a 5.
One way to do this would be to group by fruit id, and then examine the resultant groups with LINQ expressions:
var ids = MyDC.MyTable
.GroupBy(r => r.FruitID)
// The following condition examines g, the group of rows with identical FruitID:
.Where(g => g.Any(item => TheBasketIDs.Contains(item.BasketID))
&& g.Any(item => item.FruitType == 1)
&& g.All(item => item.FruitType != 2))
.Select(g => g.Key);
This produces the list of FruitIDs of your desired type.
EDIT: (in response to a comment below)
Type is only 1 or 2 but never 3
Then you can simplify your query as follows:
var ids = MyDC.MyTable
.GroupBy(r => r.FruitID)
// The following condition examines g, the group of rows with identical FruitID:
.Where(g => g.Any(item => TheBasketIDs.Contains(item.BasketID))
// When there is no 3-rd state, FruitType==1 will keep FruitType==2 out
&& g.All(item => item.FruitType == 1))
.Select(g => g.Key);
var TheQuery = (from a in MyDC.MyTable
group a by a.FruitID into g
where g.Any(b => TheBasketIDs.Contains(b.BasketID)) && g.All(b => b.FruitType == 1)
select g.Key).ToList();

Multiple group by with aggregate in Linq

I currently have this code:
foreach (var newsToPolitician in news.NewsToPoliticians)
{
var politician = newsToPolitician.Politician;
var votes = (from s in db.Scores
where o.IDPolitician == politician.IDPolitician
&& o.IDNews == IDNews
group o by o.IDAtribute
into g
select new{
Atribute= g.Key,
TotalScore= g.Sum(x => x.Score)
}).ToList();
}
It works alright, but I want to avoid making multiple queries to my database in foreach loop.
My table Scores looks like this:
IDScore | IDNews | IDUser | IDPolitician | IDAtribute | Score
1 40 1010 35 1 1
2 40 1010 35 2 -1
3 40 1002 35 1 1
4 40 1002 35 2 1
5 40 1002 40 1 -1
...
My goal is to aggregate all the scores for all politicians in a news. A news can have up to 7 politicians.
Is it expensive to call my database up to seven times in a foreach loop. I know that isn't best practice so I'm interested is there any way to avoid it in this particular case and make one call to database and then process it on the server side?
Update - Due to user comments have re-jigged to try and ensure aggregation on the server.
In this case we can group on the server by both IDPolitician and IDAttribute and then pull the groups in with ToLookup locally as so:
var result = db.Scores.Where(s => s.IDNews == IDNews)
.Where(s => news.NewsToPoliticians
.Select(n => n.Politician.IDPolitician)
.Contains(s.IDPolitician))
.GroupBy(s => new
{
s.IDPolitician,
s.IDAttribute
},
(k,g ) => new
{
k.IDPolitician,
k.IDAttribute,
Sum = g.Sum(x => x.Score)
})
.ToLookup(anon => anon.IDPolitician,
anon => new { anon.IDAttribute, anon.Sum })
Legacy -
You want to use GroupJoin here, it would be something along the lines of:
var result = news.NewsToPoliticians
.GroupJoin( db.Scores.Where(s= > s.IDNews == IDNews),
p => p.IDPolitician,
s => s.IDPolitician,
(k,g) => new
{
PoliticianId = k,
GroupedVotes = g.GroupBy(s => s.IDAtribute,
(id, group) => new
{
Atribute = id,
TotalScore = group.Sum(x => x.Score)
})
})
.ToList();
However you are at the mercy of your provider as to how it translates this so it might still be multiple queries to get round this you could use something like:
var politicianIds = news.NewsToPoliticians.Select(p => p.IDPolitician).ToList()
var result = db.Scores.Where(s= > s.IDNews == IDNews)
.Where(s => politicianIds.Contains(s.IDPolitician))
.GroupBy(p => p.IDPolitician,
(k,g) => new
{
PoliticianId = k,
GroupedVotes = g.GroupBy(s => s.IDAtribute,
(id, group) => new
{
Atribute = id,
TotalScore = group.Sum(x => x.Score)
})
})
.ToList();
Which hopefully should be at most 2 query (depending on whether NewsToPoliticians is db dependent). You'll just have to try it out and see.
Use a stored procedure and get the SQL server engine to do all the work. You can still use Linq to call the stored procedure and this will minimize all the calls to the database

Categories