How to get items of an entity's collection property using Linq? - c#

Suppose we have Hotels that have rooms, and each room has a room number, how can I display all of the room numbers starting from context.Hotels using Linq instead of foreach ?
Illustration:
var Rooms = context.Hotels.Select(e => e.Rooms);
foreach (var room in Rooms)
{
var list = room.Select(r => r.Number);
foreach(number in list)
{
Console.WriteLine(number);
}
}
Obviously I can directly display the numbers from the room entity but this isn't my actual code just to illustrate what I want to do.

How about simply selecting the room number, using SelectMany, as in:
var roomNumbers = context.Hotels.SelectMany(h => h.Rooms.Select(r => r.Number));
Or in case they need to be distinct numbers:
var roomNumbers = context.Hotels.SelectMany(h => h.Rooms.Select(r => r.Number)).Distinct();

Related

My LINQ query is not ordering my dictionary

I have a Dictionary
Dictionary<string, List<Employee>> employees
that contains a list of employees. And I want to allow the user to display the list of employees in alphabetical order based on the state they are in.
Console.WriteLine($"If you would like to sort this list please enter one of the following choices..\n" +
$"'STATE', 'INCOME', 'ID', 'NAME', 'TAX' other wise enter any key.");
var sort = Console.ReadLine().ToUpper();
var employees = EmployeeRecord.employees;
List<Employee> sortedEmps = new List<Employee>();
if (sort.Contains("STATE"))
foreach (var list in employees.Values) {
var columnQuery = list.OrderBy(x => x.stateCode).ToList();
sortedEmps.AddRange(columnQuery);
}
}
//Print out the newly ordered list
foreach (Employee r in sortedEmps) {
Console.WriteLine($"ID: {r.iD} Name: {r.name} State: {r.stateCode} Income:{r.income} Tax Due: {r.taxDue}");
}
However, it still prints out the list without ordering it. How can I get it to order alphabetically by the state code?
Try sorting when you have all data merged.
if (sort.Contains("STATE")) {
foreach (var list in employees.Values) {
sortedEmps.AddRange(list);
}
sortedEmps = sortedEmps.OrderBy(x => x.stateCode).ToList();
}
Also you can shorten a little the code with SelectMany as #Robert Harvey suggested
if (sort.Contains("STATE")) {
sortedEmps = employees.Values
.SelectMany(x => x)
.ToList().OrderBy(o => o.stateCode).ToList();
}

Get Rank from list of object depending upon some condition

I have list of object of class which contain totalScore as one property.I want to get rank of Team depending upon totalscore of team.Here is the list of object I called it as List data= new List();
so data contain object of scoreboard class with total score property.
I need rank of team depending upon totalscore.Here is the code that I try but it give result like Rank=1,2,2,3 but I need Rank=1,2,2,4 like this.
data.OrderByDescending(x => x.totalScore).GroupBy(x => x.totalScore)
.SelectMany((g, i) => g.Select(e => new { data = e.Rank = i + 1 }))
.ToList();
The data list contain unique team but there total score may be same so same totalscore team must be in one rank. please help me!
If you need to update the list in-place:
int i = 0;
decimal? prevValue = null;
foreach(var item in data.OrderByDescending(x => x.totalScore))
{
item.Rank = prevValue == item.totalScore ? i : ++i;
prevValue = item.totalScore;
}
A different notation (which I prefer for readability) but essentially the same answer as provided by user3185569.
var i = 1;
var results = (from d in data orderby d.totalScore descending select new { Obj = d, Rank = i++ } ).ToList();

How to efficiently filter elements in the List based on values in another list

I have list of cars and list of rentals of those cars. Every rental has DateTime From and DateTime To and Car Car properties.
I would like to exclude from list of cars, those cars which are unavailable at the chosen period. The beginning and end of this period are values of two DateTimePickers.
I achieved my goal by this code:
public void FilterAvailableCars() {
List<Car> cars = ObjectPlus.Objects[typeof(Car)].Select(o => (Car)o).ToList();
List<Rental> rentals = ObjectPlus.Objects[typeof(Rental)].Select(r => (Rental)r).ToList();
foreach (var rent in rentals) {
if (fromDateTimePicker.Value < rent.To && rent.From < toDateTimePicker.Value) {
cars.Remove(rent.Car);
}
}
carListBox.DataSource = cars;
}
I mixed LINQ and for loops, but I would like to know how pure and efficient LINQ solution would look like.
First, you select all the rental cars based your condition:
var rentalCars = rentals.Where(rent =>
fromDateTimePicker.Value < rent.To && rent.From < toDateTimePicker.Value)
.Select(r => r.Car);
Then, use Except method to get the result:
carListBox.DataSource = cars.Except(rentalCars);
Few fixes:
// filter rentals within entered date
var rentals = ObjectPlus.Objects[typeof(Rental)].Cast<Rental>()
.Where(rent => fromDateTimePicker.Value < rent.To && rent.From < toDateTimePicker.Value);
// filter rented cars
carListBox.DataSource = ObjectPlus.Objects[typeof(Car)].Cast<Car>()
.Where(car => !rentals.Any(rent => rent == car)).ToList();
Use Cast/OfType and do filtering with single Where (creating list and removing items from it as you do is kind of inefficient).
I don't know how efficient is Except, if you don't instantiate query result (using ToList()) then it should be same efficient.
It may be more efficient (depending on number of items of Rental and Car) to check for cars inside rental's Where.
The most efficient way usually is to correlate the data using join (or antijoin like in your case):
var rentals = ObjectPlus.Objects[typeof(Rental)].Cast<Rental>()
.Where(rent => fromDateTimePicker.Value < rent.To && rent.From < toDateTimePicker.Value);
carListBox.DataSource =
(from car in ObjectPlus.Objects[typeof(Car)].Cast<Car>()
join rent in rentals on car equals rent.Car into carRentals
from carRent in carRentals.DefaultIfEmpty()
where carRent == null
select car).ToList();

C# List grouping and assigning a value

I have a list of Orders. This list contains multiple orders for the same item, see the table below.
I then want to assign each item that is the same (i.e. ABC) the same block ID. So ABC would have a block ID of 1 & each GHJ would have a block ID of 2 etc. What is the best way of doing this?
Currently I order the list by Order ID and then have a for loop and check if the current Order ID is equal to the next Order ID if so assign the two the same block ID. Is there a better way of doing this using linq or any other approach?
Order ID Block ID
ABC
ABC
ABC
GHJ
GHJ
GHJ
MNO
MNO
You can do this that way, it will assign same blockid for same orderid
var ordered = listOrder.GroupBy(x => x.OrderId).ToList();
for (int i = 0; i < ordered.Count(); i++)
{
ordered[i].ForEach(x=>x.BlockId=i+1);
}
it will group orders by orderid then assign each group next blockid. Note that it won't be done fully in linq, because linq is for querying not changing data.
Always depends of what better means for you in this context.
There are a bunch of possible solutions to this trivial problem.
On top of my head, I could think of:
var blockId = 1;
foreach(var grp in yourOrders.GroupBy(o => o.OrderId))
{
foreach(var order in grp)
{
order.BlockId = blockId;
}
blockId++;
}
or (be more "linqy"):
foreach(var t in yourOrders.GroupBy(o => o.OrderId).Zip(Enumerable.Range(1, Int32.MaxValue), (grp, bid) => new {grp, bid}))
{
foreach(var order in t.grp)
{
order.BlockId = t.bid;
}
}
or (can you still follow the code?):
var orders = yourOrders.GroupBy(o => o.OrderId)
.Zip(Enumerable.Range(1, Int16.MaxValue), (grp, id) => new {orders = grp, id})
.SelectMany(grp => grp.orders, (grp, order) => new {order, grp.id});
foreach(var item in orders)
{
item.order.BlockId = item.id;
}
or (probably the closest to a simple for loop):
Order prev = null;
blockId = 1;
foreach (var order in yourOrders.OrderBy(o => o.OrderId))
{
order.BlockId = (prev == null || prev.OrderId == order.OrderId) ?
blockId :
++blockId;
prev = order;
}
Linq? Yes.
Better than a simple loop? Uhmmmm....
Using Linq will not magically make your code better. Surely, it can make it often more declarative/readable/faster (in terms of lazy evaluation), but sure enough you can make otherwise fine imperative loops unreadable if you try to force the use of Linq just because Linq.
As a side note:
if you want to have feedback on working code, you can ask at codereview.stackexchange.com

LINQ Group-by with complete object access

What I want is better explained with code. I have this query:
var items = context.Items.GroupBy(g => new {g.Name, g.Model})
.Where(/*...*/)
.Select(i => new ItemModel{
Name=g.Key.Name,
SerialNumber = g.FirstOrDefault().SerialNumber //<-- here
});
Is there a better way to get the serial number or some other property that is not used in the key? The only way I could think of is to use FirstOrDefault.
Why not just include the serial number as part of the key via the anonymous type you're declaring:
var items = context.Items.GroupBy(g => new {g.Name, g.Model, g.SerialNumber })
.Where(/*...*/)
.Select(i => new ItemModel {
Name=g.Key.Name,
SerialNumber = g.FirstOrDefault().SerialNumber //<-- here
});
Or, alternatively, make your object the key:
var items = context.Items.Where(...).GroupBy(g => g)
.Select(i => new ItemModel {...});
Sometimes it can be easier to comprehend the query syntax (here, I've projected the Item object as part of the key):
var items = from i in context.Items
group i by new { Serial = g.Serialnumber, Item = g } into gi
where /* gi.Key.Item.GetType() == typeof(context.Items[0]) */
select new ItemModel {
Name = gi.Key.Name,
SerialNumber = gi.Key.Serial
/*...*/
};
EDIT: you could try grouping after projection like so:
var items = context.Items.Where(/*...*/).Select(i => new ItemModel { /*...*/})
.GroupBy(g => new { g.Name, g.Model });
you get an
IGrouping<AnonymousType``1, IEnumerable<ItemModel>> from this with your arbitrary group by as the key, and your ItemModels as the grouped collection.
I would strongly advise against what you're doing. The serial number is being chosen arbitrarily since you do no ordering in your queries. It would be better if you specified exactly which serial number to choose that way there are no surprises if the queries return items in a different ordering than "last time".
With that said, I think it would be cleaner to project the grouping and select the fields you need and take the first result. They all will have the same key values so that will stay the same, then you can add on any other fields you want.
var items = context.Items.GroupBy(i => new { i.Name, i.Model })
.Where(/*...*/)
.Select(g =>
g.OrderBy(i => i.Name).Select(i => new ItemModel
{
Name = i.Name,
SerialNumber = i.SerialNumber,
}).FirstOrDefault()
);
Since you need all the data, you need to store all the group data into your value (in the KeyValuePair).
I don't have the exact syntax in front of me, but it would look like:
/* ... */
.Select(g => new {
Key = g.key,
Values = g
});
After that, you can loop through the Key to get your Name group. Inside of that loop, include a loop through the Values to get your ItemModel (I guess that's the object containing 1 element).
It would look like:
foreach (var g in items)
{
Console.WriteLine("List of SerialNumber in {0} group", g.Key);
foreach (var i in g.Values)
{
Console.WriteLine(i.SerialNumber);
}
}
Hope this helps!
You might want to look at Linq 101 samples for some help on different queries.
if the serial number is unique to the name and model, you should include it in your group by object.
If it is not, then you have a list of serials per name and model, and selecting firstordefault is probably plain wrong, that is, I can think of no scenario you would want this.

Categories

Resources