Having trouble with a Group By in LINQ - c#

Here's my LINQ query:
var results = from l in leads
from qr in l.QuoteRevisions
from rp in qr.RevisionProducts
select new QuoteSearchItem
{
ID = l.ID,
....
ProductCodes = l.QuoteRevisions.SelectMany(qr => qr.RevisionProducts)
.Select(p => p.Product.ProductCode).ToList()
};
The object we are trying to fill up looks like this:
public class QuoteSearchItem
{
public LeadID {get; set; }
....
public List<string> ProductCodes { get; set; }
....
}
The results I'm getting are almost accurate. But the problem is, when there is more than one product code, I get identical rows for each lead. So in stead of getting this:
{"LeadID": "12", "ProductCodes": ["Code1", Code2"]}
I get this:
{"LeadID": "12", "ProductCodes": ["Code1", Code2"]}
{"LeadID": "12", "ProductCodes": ["Code1", Code2"]}
So, I need to Group By l.LeadID. But I'm having trouble with that syntax. I tried this:
var results = from l in leads
from qr in l.QuoteRevisions
from rp in qr.RevisionProducts
group l by l.ID into lds
select new QuoteSearchItem
{
ID = lds.ID,
....
ProductCodes = lds.QuoteRevisions.SelectMany(qr => qr.RevisionProducts)
.Select(p => p.Product.ProductCode).ToList()
};
But then "lds" doesn't seen to contain anything. Not sure how to do this.
Thanks!

You are selecting all revision products and then constructing a list of leads. That is the problem, because now for every revision product you get one element containing all revision products for its lead.
Try removing subsequent from:
var results = from l in leads
select new QuoteSearchItem
{
ID = l.ID,
....
ProductCodes =
l.QuoteRevisions
.SelectMany(qr => qr.RevisionProducts)
.Select(p => p.Product.ProductCode)
.ToList()
};

Related

LINQ join failing between table and list of objects

I need to perform an update on a table with values from a List of objects in C# .NET Core 3.0. I tried to use the Join method, but receive this error:
Processing of the LINQ expression
DbSet<Room>
.Join(
outer: __p_0,
inner: p => p.RoomId,
outerKeySelector: s => s.ruId,
innerKeySelector: (s, p) => new {
kuku = s,
riku = p
})
by 'NavigationExpandingExpressionVisitor' failed. This may indicate either a bug or a limitation in EF Core. See link for more detailed information.
public class Room
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Key]
public int RoomId { get; set; }
[StringLength(50, MinimumLength = 3)]
public string RoomAddress { get; set; }
}
public class roomsForUpdate
{
public int ruId { get; set; }
public string ruName { get; set; }
}
var roomList = new List<roomsForUpdate>() { new roomsForUpdate { ruId = 1, ruName = "aa" }, new roomsForUpdate { ruId = 2, ruName = "bb" } };
var result = _context.Room.Join(roomList, p => p.RoomId, s => s.ruId, (s, p) => new { kuku = s, riku = p }).ToList();
You cannot join the EF Core LINQ query with a local list, because it can't be translated into SQL. Better first you get the database data and then join in memory.
LINQ is not meant to change the sources, it can only extract data from the sources. If you need to update data, you first fetch the items that must be updated, then you update them. Alternatively you can use plain old SQL to update the data without fetching it first.
In local memory, you have a sequence of RoomsForUpdate. Every RoomForUpdate has an Id (RuId) and a Name.
In your database you have a table with Rooms, Every Room in this table has an Id in RoomId and a RoomAddress.
It seems to me, that you want to update all Rooms that have an RoomId, that is one of the RuIds in your sequence of RoomsForUpdate. In other words: fetch (some properties of) all Rooms that have a value for RoomId that is a RuId in your sequence of RoomsForUpdate:
var roomsToUpdate = new List<roomsForUpdate>()
{
new roomsForUpdate { ruId = 1, ruName = "aa" },
new roomsForUpdate { ruId = 2, ruName = "bb" }
};
// Extract the Ids of the rooms that must be fetched
var roomToUpdateIds = roomsToUpdate.Select(room => room.ruId);
// Fetch all rooms from the database that have a RoomId that is in this sequence
var fetchedRooms = dbContext.Rooms
.Where(room => roomToUpdateIds.Contains(room => room.RoomId)
.ToList();
Of course you can put everything into one big LINQ statement. This will not improve efficiency, however it will deteriorate readability of your code.
Now to update the Rooms, you'll have to enumerate them one by one, and give the fetched rooms new values. You didn't say which new value you want. I have an inkling that you want to assign RuName to RoomAddress. This means that you have to combine the Room with the new value for the RoomAddress.
This can be done by LINQ:
var roomsWithExpectedNewValues = fetchedRooms.Join(roomsToUpdate,
fetchedRoom => fetchedRoom.RoomId, // from every fetched room take the Id
roomToUpdate => roomToUpdate.RuId, // from every room to update take the RuId
// for every fetchedRoom with its matching room to update, make one new:
(fetchedRoom, roomToUpdate) => new
{
Room = fetchedRoom,
NewValue = roomToUpdate.RuName,
})
.ToList();
To actually perform the update, you'll have to enumerate this sequence:
foreach (var itemToUpdate in roomsWithExpectedNewValues)
{
// assign RuName to RoomName
itemToUpdate.Room.RoomName = itemToUpdate.NewValue;
}
dbContext.SaveChanges();
A little less LINQ
Although this works, there seems to be a lot of magic going on. The join will internally make a Dictionary for fast lookup, and throws it away. I think a little less LINQ will make it way more easy to understand what's going on.
// your original roomsToUpdate
var roomsToUpdate = new List<roomsForUpdate>()
{
new roomsForUpdate { ruId = 1, ruName = "aa" },
new roomsForUpdate { ruId = 2, ruName = "bb" }
};
var updateDictionary = roomsToUpdate.ToDictionary(
room => room.RuId, // key
room => room.RuName) // value
The Keys of the dictionary are the IDs of the rooms that you want to fetch:
// fetch the rooms that must be updated:
var fetchedRooms = dbContext.Rooms
.Where(room => updateDictionary.Keys.Contains(room => room.RoomId)
.ToList();
// Update:
foreach (var fetchedRoom in fetchedRooms)
{
// from the dictionary fetch the ruName:
var ruName = updateDicationary[fetchedRoom.RoomId];
// assign the ruName to RoomAddress
fetchedRoom.RoomAddress = ruName;
// or if you want, do this in one statement:
fetchedRoom.RoomAddress = updateDicationary[fetchedRoom.RoomId];
}
dbContext.SaveChanges();

One to many database to json

I got stuck when I retrieve data from database that is one to many relationship and I get two rows like this
ID NAME CHOICE_ID CHOICE_NAME CHOICE_VALUE
1 1 1 1 true
1 1 1 2 false
How can I make a json like this?
[{
id:1
name:1
choice:[
{
choiceId:1
choiceName:1
choice_value:true
},
{
choiceId:1
choiceName:2
choiceValue:false
}
]
]}
My code is like :
var tbl = from a in db.users
join b in db.choice
on a.choice_id = b.choice_id
select new {
a.id
a.name
a.choice_id
b.choice_name
b.choice_value
}
table.Select(p => new User()
{
id = p.id
name = p.name
choice = new List<Choice>()
{
new Choice()
{
choiceId = p.choiceId
choiceName = p.choiceName
choiceValue = p.choiceValue
}
}
}).toList();
It's will get two with the same id and name I want to get one that contain all choice.
So a pure T-SQL Solution would be something like this:
SELECT Id,
Name,
(
SELECT choice_id, choice_Name, choice_Value
FROM choice c
WHERE c.choice_id = u.choice_id
FOR JSON AUTO
) As choice
FROM users u
FOR JSON AUTO
First is the query. You'll need to work in a "Group By" that groups the entities on the id and name and have the child entities underneath it.
var result = tbl
.GroupBy(p => new { p.id, p.name })
.Select(g => new
{
id = g.Key.id,
name = g.Key.name,
choice = g.Select(p =>
new Choice()
{
choiceId = p.choiceId
choiceName = p.choiceName
choiceValue = p.choiceValue
})
}).toList();
Second, to get it in JSON depends on the libraries you're using. With C# it's likely the .Net Web API or MVC. Either way you can wire it to serialize its responses to JSON which is an entirely different question. If you want to serialize it to a variable directly I'd recommend the Newtonsoft.Json which would be a pretty simple call at this point:
string json = JsonConvert.SerializeObject(result);

What is the best way to get the percentage for an object in linq list and map it to JSON?

As a result of a join in linq I am getting a list of RoleViewModel objects. What I want to do after is to get the percentage for each WorkRole in the list and map the work role's percentage and it's name to a json.
So, if I have two objects total in var list - one has RoleName "Role1" and the other has RoleName "Role2", what's the best way to get a JSON like :
myObj = {
"rolename":"Role1",
"perc":50
},
{
"rolename":"Role2",
"perc":50
},
Here is the query for my list :
var list= list1.
Join(db.WorkRolesUsersDetails,
o => o.WorkRoleId, od => od.WorkRoleId,
(o, od) => new
{
WorkRoleId = o.WorkRoleId,
RoleName = o.RoleName,
RoleDescription = o.RoleDescription,
CompanyId = o.CompanyId,
WRUDId = od.WRUDId,
UserDetailsId = od.UserDetailsId,
FocusStart = od.FocusStart,
FocusEnd = od.FocusEnd
}).ToList()
.Select(item => new RoleViewModel(
item.WorkRoleId,
item.RoleName,
item.RoleDescription,
item.CompanyId,
item.WRUDId,
item.UserDetailsId,
item.FocusStart,
item.FocusEnd)).ToList();
So, any tips on how can I do what I want in the best and easiest way? I am new to c#.
It should work like that:
var perclist = list.GroupBy(i=>i.RoleName)
.Select(i=>
new {
rolename=i.Key,
perc = ((double)(i.Count()) / (double)(list.Count()) )*100
});
var json = JsonConvert.SerializeObject(perclist);
I user Json.NET for serialization

LINQ Getting Distinct Data and looping through to get related data

I'm new to Linq. I want to know whether this is the best way or are there any other ways to do this.
I have a requirement where from a web service, I receive a list of items:
class Item {
string ItemName { get; set;}
string GroupName { get; set; }
}
I receive the following data:
ItemName: Item1; GroupName: A
ItemName: Item2; GroupName: B
ItemName: Item3; GroupName: B
ItemName: Item4; GroupName: A
ItemName: Item5; GroupName: A
Now I want to get all of the unique Groups in the list, and associate all the Items to that Group. So I made a class:
class Group {
string GroupName { get; set; }
List<string> Items { get; set; }
}
So that there is a single group and all associated Items will be under the List.
I made two LINQ statements:
var uniqueGroups = (from g in webservice
where g.Group != null
select g.GroupName).Distinct();
Then I loop through it
foreach (var gn in uniqueGroups)
{
var itemsAssociated = (from item in webservice
where item.GroupName = gn.ToString()
select new {
});
}
and then I got the items, and save them to my object.
Is this the best way to do this or are there any LINQ statement that can do all these in one go?
Thanks.
Sounds like you want GroupBy
var itemsByGroup = items.GroupBy(i => i.GroupName);
foreach (var group in itemsByGroup)
{
var groupName = group.Key;
var itemsForThisGroup = group;
foreach (var item in itemsForThisGroup)
{
Console.Out.WriteLine(item.ItemName);
}
}
You can try this:
//List<Item> webservice = list with items from your webservice
var result = (from i in items
group i by i.GroupName into groups
select new Group()
{
GroupName = groups.Key,
Items = groups.Select(g => g.ItemName).ToList()
}).ToList();
I would use:
webservice.ToLookup(k => k.GroupName);
That would eliminate the need for the extra class.
Hope this helps!
That could be done all at once with an anonymous type and Enumerable.GroupBy:
var groupItems =
webservice.Where(i => i.GroupName != null)
.GroupBy(i => i.GroupName)
.Select(grp => new { Group = grp.Key, Items = grp.ToList() });
foreach (var groupItem in groupItems)
Console.WriteLine("Groupname: {0} Items: {1}"
, groupItem.Group
, string.Join(",", groupItem.Items.Select(i => i.ItemName)));
Distinct is useless since GroupBy will always make the groups distinct, that's the nature of a group.
Here's running code: http://ideone.com/R3jjZ

C# Sorting a List based on the sequence of values from another List (different classes)

I'm currently stuck at a problem where I have 2 Lists, and I want to sort the second List according to a value from the first List, here is an example:
public class data
{
public string Name{get; set;}
public int ID{get; set}
}
public class dataToSort
{
public int ID{get; set;}
public string retrievedData{get; set}
public string timeStamp{get; set}
}
So lets say I have 2 List objects, one for data and one for dataToSort, their contents below:
data: "Alpha", "80" dataToSort: "21", "XA", "YA"
"Beta", "47" "47", "XB", "YB"
"Charlie", "153" "80", "XC", "YC"
"Delta", "21" "153","XD", "YD"
So what I want to do is to make the order of dataToSort equal to the order of the IDs in data, like this:
dataToSort: "80", "XC", "YC"
"47", "XB", "YB"
"153","XD", "YD"
"21", "XA", "YA"
I have tried googling for a way to Sort these but all the LINQ syntax confuses me and I have problems due to the difference in classes of each object :( The only way I can think of is to have a for loop to get the index of one List's ID and do something like a bubble sort, but its too much of a hassle and also inefficient. Help is greatly appreciated!
You can join the two lists on the IDs:
var query = from x in data
join y in dataToSort on x.ID equals y.ID
select y;
var result = query.ToList();
This keeps the order of the first list data.
You can also easily combine the two lists this way:
var query = from x in data
join y in dataToSort on x.ID equals y.ID
select new
{
x.Name,
x.ID,
y.retrievedData,
y.timeStamp,
};
var result = query.ToList();
Something like this can do the trick (pseudocode):
dataToSort sortedData;
foreach data
{
sortedData.AddRange(from unsortedData.Where(x => x.ID == data.ID));
}
List<data> lstofdata = new List<data>();
List<dataToSort> lstdataToSort = new List<dataToSort>();
var q = from d in lstofdata
join s in lstdataToSort on d.ID equals s.ID
orderby d.Name
select s
You can join both lists and order by IndexOf
var ordered = (from ds in dataToSort
join d in data
on ds.ID equals d.ID
orderby data.IndexOf(d)
select ds).ToList();
This should do the trick:
var list = dataList
.Select(data => dataToSortList
.First(x => x.ID == data.ID)
)
.ToList();

Categories

Resources