Entity Framework Include Parent Entities - c#

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);

Related

SelectMany nested in parent's lambda vs SelectMany after SelectMany

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.

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.

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();

How do I select an object by a sub-property

i've got a List of objects, lets call them Product, which each of them contains a bunch of properties and also a List of Version (which are also objects).
Version also has a bunch of properties and does contain a List of Customer (which again are objects).
Customer again has properties, one of them is its ID (=Guid).
What i try to do is to make a List of Product, selected by a certain ID of its Product.VersionList.Version.ID.
I would prefere a join query, but every efficient way is welcome. I tried so far this, but because i have only a single ID to compare with, i don't know how to construct the join.
lp = List<Entity.Product>;
g = GetGuid();
var query = from product in Entity.ProductCollection
join g in g
on product.Version.Where(x => x.id == g)
select product;
lp.AddRange(query);
I'm guessing you mean:
var query = from product in Entity.ProductCollection
where product.Version.Any(x => x.id == g)
select product;
i.e. select all the products that have a version where the id matches the guid you were thinking of.
Note that joining to the versions would cause product duplication if any product has multiple matching versions.
Try this .... May be you wants more deep digging on it..
var query = from Product product in pc
from varsion in product.Version
let v= varsion as Entity.Version
where v.id == g
select product;
var query = Entity.ProductCollection.Where(p => p.Version.Any(v => v.Id == g));
You can use Any rather than having to do a self join.

How can I query hierarchies with LinqToSQL?

I have a hierarchy that I'd like to query with LinqToSql:
Country -> Region -> City -> ZipCode
Each entity holds both a reference to it's parent (eg. Region.Country) and a collection of it's children (eg. Region.Cities).
I'd like to eager load each entity's parent along with Countries and Regions but lazy load cities and zip codes.
To complicate things, each entity is being localized before being projected in to the model. So Country.Name changes based on the language.
Here's some snippets of what I have so far:
public IQueryable<Country> ListCountries()
{
return ProjectCountry(dataContext.GetTable<ec_Country>());
}
private IQueryable<Country> ProjectCountry(IQueryable<ec_Country> query)
{
var result = from country in query
join localized in dataContext.GetTable<ec_CountryLocalization>() on country.CountryID equals localized.CountryID
let regions = GetRegions(country.CountryID)
where localized.StatusID == 4 && localized.WebSiteID == this.webSiteID
select new Country(country.CountryID) {
CreatedDate = country.CreatedDate,
IsDeleted = country.IsDeleted,
IsoCode = country.IsoCode,
Name = country.Name,
Regions = new LazyList<Region>(regions),
Text = localized.Text,
Title = localized.Title,
UrlKey = country.UrlKey
};
return result;
}
private IQueryable<Region> GetRegions(Int32 countryID)
{
var query = from r in dataContext.GetTable<ec_Region>()
where r.CountryID == countryID
orderby r.Name
select r;
return ProjectRegion(query);
}
private IQueryable<Region> ProjectRegion(IQueryable<ec_Region> query)
{
var result = from region in query
join localized in dataContext.GetTable<ec_RegionLocalization>() on region.RegionID equals localized.RegionID
join country in ListCountries() on region.CountryID equals country.CountryID
let cities = GetCities(region.RegionID)
select new Region(region.RegionID) {
Cities = new LazyList<City>(cities),
Country = country,
CountryID = region.CountryID,
CreatedDate = region.CreatedDate,
IsDeleted = region.IsDeleted,
IsoCode = region.IsoCode,
Name = region.Name,
Text = localized.Text,
Title = localized.Title,
UrlKey = region.UrlKey
};
return result;
}
... etc.
[TestMethod]
public void DataProvider_Correctly_Projects_Country_Spike()
{
// Act
Country country = dataProvider.GetCountry(1);
// Assert
Assert.IsNotNull(country);
Assert.IsFalse(String.IsNullOrEmpty(country.Description));
Assert.IsTrue(country.Regions.Count > 0);
}
The test fails with:
System.NotSupportedException: Method 'System.Linq.IQueryable`1[Beeline.EducationCompass.Model.Region] GetRegions(Int32)' has no supported translation to SQL.
How would you recommend I go about this? Would it be simpler (or possible) if each level of the hierarchy was in the same table instead of separate ones?
You're going to want to use the linq designer to set up relationships between your objects. This gets you out of writing join after join after join by creating properties.
between a Country and its Regions
between a Region and its Cities
between a Country and its Localizations
between a Region and its Localizations
You're going to want to use ToList to seperate those operations you intend to be translated into SQL, and those operations you intend to be done in local code. If you don't do this, you'll keep seeing those "cannot translate your method into SQL" exceptions.
You're also going to want to use DataLoadOptions to eagerly load these properties in some cases. Here's my stab at it.
DataLoadOptions dlo = new DataLoadOptions();
//bring in the Regions for each Country
dlo.LoadWith<ec_Country>(c => c.Regions);
//bring in the localizations
dlo.AssociateWith<ec_Country>(c => c.Localizations
.Where(loc => loc.StatusID == 4 && loc.WebSiteID == this.webSiteID)
);
dlo.AssociateWith<ec_Region>(r => r.Localizations);
//set up the dataloadoptions to eagerly load the above.
dataContext.DataLoadOptions = dlo;
//Pull countries and all eagerly loaded data into memory.
List<ec_Country> queryResult = query.ToList();
//further map these data types to business types
List<Country> result = queryResult
.Select(c => ToCountry(c))
.ToList();
public Country ToCountry(ec_Country c)
{
return new Country()
{
Name = c.Name,
Text = c.Localizations.Single().Text,
Regions = c.Regions().Select(r => ToRegion(r)).ToList()
}
}
public Region ToRegion(ec_Region r)
{
return new Region()
{
Name = r.Name,
Text = r.Localizations.Single().Text,
Cities = r.Cities.Select(city => ToCity(city)).ToLazyList();
}
}
That's one sticky piece of code, and I wouldn't have answered this due to lack of relevant skill if anyone else had, but since you had no responses...
I can tell you what the error message means. It means the function GetRegions can't be translated into sql by the linq to sql provider. Some built-in functions can be, because the provider understands them, here is a list. Otherwise you can provide translations see here.
In your situation you need to 'inline' the logic of this query, the logic won't cross the boundary of a function call, because you are dealing with an expression tree, the sql server can't call back into your GetRegions method.
As to the exact way to do that, you'll have to have a go, I don't have the time to oblige you at the moment. (Unless someone else has time and skill?)
Good luck.

Categories

Resources