arth81 arth81 - 2 months ago 6
C# Question

Implementing hierarchy in the entity model

I need to create a model and method which will be populated with the lists of relevant models.

My model looks like:

public class SearchHierarchyModel
{
public IList<Continent> Continent { get; set; }
public IList<Country> Country { get; set; }
public IList<City> City { get; set; }
}


My methods should do something like:

public IList<SearchHierarchyModel> GetHierarchyFull(int Coninent_Id)
{
//pseduocode now
create lists of countries based on continent id
create lists of cities based on countries id from the prev. step
return a list of lists with relevant countries and cities
}


Model classes

public class Contient
{
public int Id { get; set; }
public string Name { get; set; }
}

public class Country
{
public int Id { get; set; }
public string Name { get; set; }
public int ContientId { get; set; }
}

public class City
{
public int Id { get; set; }
public string Name { get; set; }
public int CityId { get; set; }
}


Perhaps there is a better way to implement such kind of hierarchy? Any ideas how to do this? Maybe the the model should look differently?

Answer

Depending on how your relationships are setup, it would be possible to do something similar to.

public IList<SearchHierarchyModel> GetHierarchyFull(int Continent_Id)
{

    var continent = db.Continents.FirstOrDefault(c => c.Id == Continent_Id);

    //Get all countries with a specified ContinentId
    var countryList = db.Countries.Where(c => c.ContinentId == Continent_Id);

    //Get all cities that have a matching CountryId from any country in the first list.
    var cityList = db.Cities.Where(c => countryList.Any(cl => cl.Id == c.CountryId)).ToList();

    //We need to get the original countryList as a true list rather than a collection of entities.
    //If we had called ToList above, it would error out.
    //If we had called ToList in the ForEach loop, we also would have issues.
    var countryList2 = countryList.ToList();

    var searchList = new List<SearchHierarchyModel>
    {
        new SearchHierarchyModel()
        {
            Continent = new List<Continent> { continent },
            Country = countryList2,
            City = cityList
        }
    };
    return searchList;
}

Now some comments about the above. It seems like to me, you simply want a list of countries and their cities for a given Continent. If that's the case, entity framework makes it even easier for you.

I would change my core models to be:

public class Continent
{
    public int Id { get; set; }
    public string Name { get; set; }

    public virtual ICollection<Country> Countries { get; set; }
} 

public class Country
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int ContinentId { get; set; }

    public virtual Continent Continent { get; set; }
    public virtual ICollection<City> Cities { get; set; } 
}

public class City
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int CountryId { get; set; }

    public virtual Country Country { get; set; }
}

Then I'd set the hierarchy model to:

public class SearchHierarchyModel
{
    public Continent Continent { get; set; }
    public Country Country { get; set; }
    public City City { get; set; }
}

Now we can change the search function to be:

public IList<SearchHierarchyModel> GetHierarchyFull(int Continent_Id)
{

    var countries = db.Countries.Where(c => c.ContinentId == Continent_Id);
    var cities = db.Cities.Where(c => countries.Any(co => co.Id == c.Id));

    var searchList = new List<SearchHierarchyModel>();
    foreach (var item in cities)
    {
        var newItem = new SearchHierarchyModel
        {
            Continent = item.Country.Continent,
            Country = item.Country,
            City = item
        };
        searchList.Add(newItem);
    }
    return searchList;
}

Now rather than iterating over a list of lists, we're iterating over a list of all the possible return values. Plus with the modification of the models, the relationships between the models are explicitly defined. This means I can more easily reference and interact with them.


And here's a search method that does the traversal of the objects in reverse using Linq, cutting out the foreach loop and removing all the extra lists.

public IList<SearchHierarchyModel> GetHierarchyFull(int Continent_Id)
{
    var continent = db.Continents.FirstOrDefault(c => c.Id == Continent_Id);

    if (continent == null)
        return null;

    var searchList = (from item in continent.Countries
        from city in item.Cities
        select new SearchHierarchyModel
        {
            Continent = continent, 
            Country = item, 
            City = city
        }).ToList();

    return searchList;
}
Comments