public class Country
{
public List<State> States { get; set; } = new List<State>();
}
public class State
{
public List<City> Cities { get; set; } = new List<City>();
}
public class City
{
public decimal IdSeaLevel { get; set; }
}
IdSeaLevel
Has these possible expected values: 0, 1, 2
.
Then is needed to check al values inserted by user to prevent some different value.
Suppose that the user send us an country (object of Country
class) with its list filled (and nested too).
How to get all Distinct
IdSeaLevel
inserted value by the user?
I was thinking like:
List<decimal> AllowedIdSeaLevellist = new List<decimal>(new decimal[] { 0, 1, 2 });
Now, I get a Distict inserted Values
HashSet<decimal> SentIdSeaLevelSet = country.States
.Select(s => s.Cities.IdSeaLevel).ToHashSet();
Check
bool badRequest= SentIdSeaLevelSet
.Where(s => AllowedIdSeaLevellist.All(a => a != s)).Any();
.SelectMany
will map List of lists into single list (flattened)
var allSeaLevels = country.States
.SelectMany(s => s.Cities)
.Select(city => city.SeaLevelId)
.ToHashSet();
To get "invalid" sea levels you can alternatively to gather them while looping through sealevels.
var validSeaLevels = new[] { 0, 1, 2 }.ToHashSet();
var invalidSeaLevels = country.States
.SelectMany(s => s.Cities)
.Select(city => city.SeaLevelId)
.Where(level => validSeaLevels.Contains(level) == false)
.ToArray();
if (invalidSeaLevels.Any())
{
return BadRequest(invalidSeaLevels);
}
This type of deep linking is where SelectMany<T>
becomes helpful:
HashSet<decimal> SentIdSeaLevelSet = country.States
.SelectMany(s => s.Cities.Select(c => c.IdSeaLevel)).Distinct().ToHashSet()
We want to project the IdSeaLevel
but Cities
is a List, so at some point you need the inner Cities.Select()
but that can be inside or after the SelectMany
which effectively flattens the hierarchy so that all of the nested Cities
become a single list, the following would also work:
HashSet<decimal> SentIdSeaLevelSet = country.States
.SelectMany(s => s.Cities).Select(c => c.IdSeaLevel).Distinct().ToHashSet()
I prefer to use projection first inside the SelectMany
it we never need any other properties from the City
objects (the first example) but different applications and structures might dictate that the second expression performs better.
For the final comparison your logic looks ok, another way to compare lists is by using except:
bool badRequest= SentIdSeaLevelSet
.Except(AllowedIdSeaLevellist).Any();
This is functionally equivalent to your previous comparison and works because the types of the collections being compared are the same, runtime might be marginally faster but at this level you base your decision on code readability, which is a subjective topic on its own, but I prefer the except version specifically when we are comparing lists.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With