SelectMany nested in parent's lambda vs SelectMany after SelectMany - c#

Those two examples give the same results, but different syntax tells me that are executed in a completely different way. Where is the difference? Which way should be preferred?
1st
continents
.SelectMany(continent => continent.Countries)
.SelectMany(country => country.Cities)
2nd
continents
.SelectMany(continent =>
continent.Countries.SelectMany(country => country.Cities))
EDIT: Let's not talk about deferred executions of IEnumerable, because it is not important here. Please assume that each query ends with .ToList().

Which way should be preferred?
Blockquote
Any. Because you are working with IEnumerable methods that have deffered execution (learn more: MSDN)

You don't need a second SelectMany on your 2nd solution.
In case you need to transform your IEnumerable into a List this would matter (a SelectMany taking more resources than a Select).
As Alexbogs said in his answer if you only work with an IEnumerable it won't matter.
My proposal :
continents.SelectMany(continent => continent.Countries.Select(country => country.Cities))

I'm bemused by one of the other answers. Your queries both return a simple listing of cities and if that's all you need it doesn't really matter how you chain the SelectManys. I think that's the only correct answer.
Replacing the second SelectMany by Select changes the query result significantly. It returns a nested listing of cities grouped by countries. So I'm not sure how that answers your question.
In other cases, it does matter how the parentheses are placed. In the first query the part continents.SelectMany(continent => continent.Countries) lists countries and after that, continents are out of scope. In the second query, continents can be kept in scope all the way.
The difference is best shown in query syntax. Suppose you want to list country and city names of all continents. In query syntax:
from continent in Continents
from country in continent.Countries
from city in country.Cities
select
new
{
country.CountryName,
city.CityName
}
In method syntax this amounts to:
Continents.SelectMany(continent => continent.Countries)
.SelectMany(country => country.Cities,
(country, city) => new
{
CountryName = country.CountryName,
CityName = city.CityName
} )
As you see, it adds a Selectmany after the closing parenthesis of the first SelectMany as in your first query. Only countries can be kept in scope.
If you want to list continent names besides country names, and city names, you can use an overload of your second query, in method syntax:
Continents.SelectMany
(
continent => continent.Countries.SelectMany
(
c => c.Cities
, (country, city) => new { country, city }
), (continent, x) => new
{
continent.ContinentName,
x.country.CountryName,
x.city.CityName
}
)
Again, query syntax looks a lot friendlier:
from continent in Continents
from country in continent.Countries
from city in country.Cities
select
new
{
continent.ContinentName,
country.CountryName,
city.CityName
}
But the compiled method syntax is a bit different in transfering the intermediate anonymous types:
Continents.SelectMany(continent => continent.Countries,
(continent, country) => new { continent = continent, country = country } )
.SelectMany(x => x.country.Cities,(x, city) =>
new
{
ContinentName = x.continent.ContinentName,
CountryName = x.country.CountryName,
CityName = city.CityName
}
)
Which is a variation of your first query.
So how to chain SelectManys depends on which entities you need in the end result. The benefit of query syntax is that the compiler figures this out for you.

Related

Entity framework Query with Top keword on child table

var countries= ctx.Country
.Include("cities") // I want to take only 10 cities. How to take top 10 cities and city name starts from "A"
.Include("Schools")
.Where(x => (x.CountryID == 100))
.ToList();
1 - Top 10 ciites
2 - Where criteria on CityName field
I am using Entity Framework 6
Use something like this:
var countries = ctx.Country.Select( c => new {
Country = c,
Cities = c.Cities.Where(ci = > ci.CityName.ToLower().Startwith("A".ToLower())).Take(10),
Schools = Cities.select(ci => ci.Schools)
}).Where(x => x.CountryID == 100).ToList();
I didn't test it, maybe you will get some compile errors, cuz i don't know how you named your classes.
Let me know if you need any clarification or have any question
Set up the navigation property relationships between country, school, and city then select a structure based on the data you want to receive into an anonymous type and let EF handle the query composition.
var countryData = ctx.Countries
.Include(x => x.Schools)
.Where(x => x.CountryID == 100)
.Select(x => new { Country = x, Cities = x.Cities.OrderBy(c => c.CityName).Take(10).ToList() })
.ToList(); // This likely only returns 1 row due to the CountryId Where Clause...
This will give you a structure containing the Country reference and the list of up to 10 cities associated to each country.
If you access the Cities collection on a Country object in the results you will still lazy-load all cities, but the .Cities collection returned in the above statement would be the 10 you care about.
If there are a lot of cities in a country and loading this complete set is potentially expensive then you may want to consider leaving the entities disconnected so rather than having a Cities collection associated to a country, treat cities as a top-level entity that happens to have a relationship to country. (I.e. City mapping .HasRequired(x=> Country).WithMany() rather than mapping a .HasMany(x=> x.Cities).WithRequired(x=>x.Country) on the country.)
This would change the query somewhat if you want more than one country, by using a GroupBy expression, though it'd only return countries that had at least one city based on the search criteria.

Entity Framework Include Parent Entities

When I am accessing all cities my code is like this.
public IQueryable<City> GetAll()
{
var result = from s in this.Context.Cities.Include("States.Countries") select s;
return result;
}
This is working fine and including states and countires. I want to get cities by Country Id, below is my code. In the below code, I want to include States.Countires for each city. How can i do this ?
public IEnumerable<City> GetByCountriesId(int Id)
{
var result = from s in this.Context.Countries
join a in this.Context.States on s.Id equals a.Country_Id
join b in this.Context.Cities on a.Id equals b.States_Id
where s.Id == Id
select b;
return result;
}
public IEnumerable<City> GetByCountriesId(int id)
{
return from country in this.Context.Countries
where country.Id == id
from state in country.States
from c in this.Context.Cities.Include(c => c.States.Select(s => s.Countries))
where c.States.Any(s => s == state)
select c;
}
or, even better:
public IEnumerable<City> GetByCountryId(int id)
{
return from c in this.Context.Cities
.Include(c => c.States.Select(s => s.Countries))
where c.States.Any(s => s.Countries.Any(c => c.Id == id))
select c;
}
However – while it's clear why Country has a States collection and State has a Cities collection – why does your City have a States collection and your State have a Countries collection? Shouldn't these be State and Country properties, respectively?
Assuming your City really does have a single State, and your State has a single Country, this simplifies it a lot:
return from c in this.Context.Cities
.Include(c => c.State.Select(s => s.Country))
where c.State.Country.Id == id
select c;
Are you sure a city could belong to several states? IMHO you should have a one to many relationship, where an State could have several Cities and a City should belong to one State. The same happens with State and Country. I think you have pluralized those nav. property names (States in City and Cities in Country) but there are not collections. In case you have those two one to many relationships in the same way that I describe above, you can write a query as I show as follow to achieve what you need:
var result = this.Context.Cities.Include(c=>c.State.Country).Where(c=>c.State.Country.Id==Id‌​);
Is better use the DbExtensions.Include extension method because is strongly typed.
Now, maybe you can think this query could end with a NullReferenceException due to the c.State.Country.Id expression in case one of those nav. properties could be null.But that is not going to happen because you need to set those navigation properties (or the FK properties in case that already exist in DB) when you need to save a new City or State in DB, in other word, they are required.
If you use Fluent Api to configure those relationships you will end with something like this:
modelBuilder.Entity<City>().HasRequired(c=>c.State).WithMany(s=>s.Cities).HasForeignKey(c=>c.State_Id);
modelBuilder.Entity<State>().HasRequired(s=>s.Country).WithMany(c=>c.States).HasForeignKey(s=>s.Country_Id);

C# Linq multiple GroupBy and Select

I have two objects that are linked, States and Cities, so each State has his Cities and each Citie is linked to an State. I also have some Units that have stateID and citieID but they are not linked since i have them only in Json.
What i need is to get only the States and Cities that have Units. I managed to get the first two but was wondering if there was any faster way to do it since i will have to make an update on those datas everyday:
//unitsData have a List of Units objects, this only have stateID, citieID and the unit data
var unitsData = objUnidade.BuscaUnidades();
//unitsState have all units grouped by State, here i also only have stateID and citieID, same data as above
var unitsState = unitsData.GroupBy(x => x.codigoEstado);
//Here is where i make my search inside the unidadesEstados and select only the Estados that i need
var activeStates = unitsState.Select(est => db.States.FirstOrDefault(x => x.ID == est.Key)).Where(state => state != null).ToList();
To do the Cities search i'm doing the same but using an extra foreach, is there a way to make this better ?
You are querying the database multiple times. It's better to use a SELECT ... IN query, which in LINQ looks like:
var units = objUnidad.BuscaUnidades();
var stateIds = units.Select(u => u.codigoEstado).ToList();
var activeStates = db.States.Where(s => stateIds.Contains(s.Id)).ToList();
EDIT: you asked about cities as well. It's more of the same:
var cityIds = units.Select(u => u.codigoCuidad).ToList()
var activeCities = db.Cities.Where(c => cityIds.Contains(c.Id)).ToList();
This solution gives you every city whose ID is referred to by a unit. #StriplingWarrior 's solution will give you every city in (the states that have a unit).
If db.States queries the database, then for each group in unitsState the query will get executed. If the number of states isn't extremely large, you can store them in a list.
var dbStates = db.States.ToList();
var activeStates = unitsState.Select(est => dbStates.FirstOrDefault(x => x.ID == est.Key)).Where(state => state != null).ToList();

C# List values compare and add

var bndlSummary = GetBundleSummary(GroupIds);
var cntrSummary = GetContainerSummary(GroupIds);
var finalSummary = GetFinalSummary(GroupIds);
Above var are fetching some data from Database. They all have one Common Field Name "City".
City value can be repeated many time like City = Chicago can be 3 times or more). now I want this Field City value into allCityNames. I don't want City Info to be repeated from any var.
var allCityNames = new cityAnalysisSummary();
Please help me how how should i do it. Thank you very much for your help.
bndlSummary.Select(b => b.City)
.Concat(cntrSummary.Select(c => c.City))
.Concat(finalSummary.Select(f => f.City))
.Distinct();
Use Select to get all the cities from each collection, Concat to put them all together, and Distinct to remove any duplicates.
You can also use Union which will remove duplicates while concatenating:
bndlSummary.Select(b => b.City)
.Union(cntrSummary.Select(c => c.City))
.Union(finalSummary.Select(f => f.City));

query list with linq lambda expressions

How would I get participants that are in a list of counties? I get the counties in var counties, then I want to get all of the participants that have a CountyOfParticipationId that is in the list of counties.
if (collaborationId != null)
{
var counties = (from c in db.CountyCollaborations
where c.CollaborationId == collaborationId
select c).ToList();
participants = participants.Where(p => p.CountyOfParticipationId in counties);
}
.Where(p => counties.Contains(p.CountyOfParticipationId))
Now if there's a lot of data be careful with the complexity of this. Contains in a list is O(n), so overall the algorithm is O(n*m) with n,m being the # of participants and the # of counties.
For better performance you could store the counties in a HashSet, which has O(1) Contains, meaning O(n) overall.
Of course if the number of counties is small it doesn't matter, really.
EDIT: Just noted that your list doesn't contain the ids but full objects. For the code above to work you also need to change your linq query from select c to select c.Id or something like that (don't know the name of the field).
participants = participants
.Where(p => counties.Any(c=> c.CountyId == p.CountyOfParticipationId) )
Or
participants.Where(p => p.County.CollaborationId == collaborationId)
should also work if you have set up relations properly
This might be better in some situations since you won't have to store counties separately if the linq method is translating the expression to sql behind the scences.
participants = (from p in participants
join c in
db.CountyCollaborations
.Where(cty=>cty.CollaborationId == collaborationId)
on p.CountyOfParticipationId equals c.CountyId
select p);
Assuming each county has a CountyId:
participants = participants.Where( p =>
counties.Select(c=> c.CountyId ).Contains( p.CountyOfParticipationId) );

Categories

Resources