views:

94

answers:

3

I have a couple of tables where there are one to many relationships. Let's say I have a Country table, a State table with a FK to Country, and a City table with a FK to State.

I'd like to be able to create a count of all Cities for a given country, and also a count of cities based on a filter - something like:

foreach( var country in Model.Country ) {
    total = country.State.All().City.All().Count() ;
    filtered = country.State.All().City.Any(c=>c.field == value).Count();
}

Obviously, this doesn't work - is there any way to do this?

Update:

I can iterate thru the objects:

    foreach (var item in Model ) {

      ... other stuff in here ...

      int tot = 0;
      int filtered = 0;
      foreach (var state in item.State)
      {
        foreach (var city in state.City)
        {
          tot++;
          if (city.Field == somevalue)
            filtered ++;
        }
      }

      ... other stuff in here ...

    }

but that doesn't seem very elegant.

Update: @AD has a couple of suggestions, but what worked to solve the problem was:

int tot = item.States.Sum(s=>s.City.Count);
int filtered = item.States.Sum(s=>s.City.Where(c=>c.Field == somevalue).Count());
A: 

You have to explicitly load children in the Entity Framework. If you load all the children then you can get counts just fine.

IEnumberable<Country> countries = Model.Country.Include("State");
total = countries[i].State.Count();

Assuming of course that the iteration through all countries is important. Otherwise why not just query against City filtered by State and Country?

In your state foreach you should just be able to do

tot += state.City.Where(x=> x.Field == value).Count();
Russell Steen
My model has all the objects loaded - I'm iterating thru them and displaying details, but I'd like some stats ( "4/7 cities in .." ) type of thing.
chris
If it's fully loaded just nest your loop, iterating through each state instead of doing states.All()
Russell Steen
I just updated my question to show how iterating works. It seems rather un-elegant, though.
chris
A: 

Why dont you reverse it?

foreach( var country in Model.Country ) {
    var city  = Model.State.Where(x=>x.StateID==country.State.StateID).Select(x=>City)
    total  = city.Count();
    filtered = city.All(c=>c.field == value).Count();
}
Nix
+1  A: 

You can try, assuming you already have the givenCountry and value variable populated:

int total = EntityModel.CitySet.Where( it => it.State.Country.ID == givenCountry.ID ).Count();

Above, you take your entire set of cities (EntityMode.CitySet). This set contains all the cities in all the states in all the countries. The problem becomes: what subset of those cities are in country 'givenCountry'? To figure it out, you apply the Where() to the entire set and you compare the countries id to see if they are the same. However, since the city only knows which state it is in (and not the country) you first have to reference its state (it.State). it.State references the state object and that object has a Country property that will reference the country. Then it.State.Country references the country 'it' is in and 'it' is the city, creating a link between the city and the country.

Note that you could have done this is reverse as well with

int total = givenCountry.Sum( c => c.States.Sum( s.Cities.Count() ) )

However, here you will have to make sure that givenCountry has its States collection loaded in memory and also that each State has its Cities collection loaded. That is because you are using Linq-to-Entities on a loaded object and not on an Entity Framework instance object has was the case in the first example. There is a way to craft the last query to use the entity framework object however:

int total = EntityModel.CountrySet.Where( c => c.ID == givenCountry.ID ).Sum( c => c.States.Sum( s.Cities.Count() ) )

As for the number of cities with a specific field, you take a similar approach with a Where() call:

int filtered = EntityModel.CitySet.Where( it => it.field == value ).Count();
ADB
The problem is that linq to entities doesn't work that way. My model is a collection of Countries, and a Country doesn't have a City property - it has a collection of States, and each state has a collection of City.
chris
If a Country has a collection of States, then reciprocally, the State has a single Country it is attached to. The same goes for States and Cities. So, it means that from a City, you can get to the State and then to the Country. Of course, that is assuming you are working with the Entity Framework and you are using the Entities relationships (built either from Foreign Keys in the DB or manually from the Model Viewer). If you built the Country/State/City object manually or from another Model provider, then the relationship will depend on those models, which you will need to provide us.
ADB
Using EF and asp.net MVC 1.0. Model contains IEnemurable<Country>, and relationships are there. The problem with your solution is that the the Country doesn't have a City navigation property - just a collection of States.
chris
Yes, indeed, the Country only has a list of States, but that list of states give you access to the Cities. And that is valid going up and down the relationship. I believe I understand which part of my answer is confusing you. I'll edit it to clarify.
ADB
That bit in there about the reverse is exactly what I'm looking for. Thanks.
chris