views:

60

answers:

2

Hi Guys,

I'm working on a .NET 4 application, C#, Entity Framework 4, SQL Server 2008.

I have a 7 tables in my database, each representing a specific level of location (Country, State, City, Neighborhood, etc).

Now in my Repository I am trying to define an interface contract which has only one Find() method. To do this, I've created an abstract class called "Location" for which the POCO locations all inherit from.

Here's the method I currently have:

public IQueryable<Location> Find()
{
   return AllCountries()
             .Union(AllStates())
             .Union(AllCounties())
             .Union(AllCities())
             .Union(AllNeigbourhoods())
             .Union(AllZipCodes())
             .Union(AllStreets());
}

Those inline methods (e.g AllStates) are private IQueryable methods, e.g:

private IQueryable<Location> AllCountries()
{
   var db = new MyCustomDataContext();
   return db.Countries;
}

This all works fine, but I don't like the look of the code in the Find() method.

Essentially, I want a Repository method which returns all Countries/Cities/States etc (as an IQuerable<Location>).

That way, my service layer can do this:

var countries = repository.Find(somePredicate).OfType<Country>().ToList();

Or this:

var countries = repository.Find(somePredicate).OfType<City>().ToList();

So I only ever have to declare one Find method. You can think of the Location class as my "aggregate root".

Without using an abstract class, this is what my repository contract would look like:

IQueryable<City> FindCities();
IQueryable<State> FindStates();
IQueryable<Country> FindCountries();
 ....

Yuck!

This is what my repository contract currently looks like (and I want to keep it this way):

IQueryable<Location> Find();

So, any better ideas than having all those union's? An IQueryable<T> extension method which can dynamically chain on multiple IQueryable's?

Remembering I also have a Service Layer which performs the filtering/collection projection (delayed execution). The repository needs to return "queries", not concrete collections.

Appreciate the help.

+1  A: 

I'm assuming that the same logical entity won't exist in two separate tables (e.g., a "city" is not a "state"). In that case, you'd be better suited to use Concat rather than Union.

A brief helper method will make the call look nicer (warning: untested):

// (Defined in the static class MyHelpers)
// Concatenate all sequences into one.
public IQueryable<T> ConcatAll<T>(this IQueryable<T> first,
    params IQueryable<T>[] others)
{
  var ret = first;
  foreach (var other in others)
  {
    ret = ret.Concat(other);
  }

  return ret;
}

...

public IQueryable<Location> Find() 
{
  return MyHelpers.ConcatAll(
    AllCountries(),
    AllStates(),
    AllCounties(),
    AllCities(),
    AllNeigbourhoods(),
    AllZipCodes(),
    AllStreets());

  // OR:

  return AllCountries().ConcatAll(
    AllStates(),
    AllCounties(),
    AllCities(),
    AllNeigbourhoods(),
    AllZipCodes(),
    AllStreets());
} 
Stephen Cleary
@Yep, that's what i was looking for. However, im now rethinking my design (due to @Jacob's answer). That being said, this is probably the correct answer based on my original question. Thanks.
RPM1984
+1  A: 

The Entity Framework allows you map your tables to a data model that uses inheritance. If you had a Location table in your database containing all of your common fields, and each sub-location class (such as City) had a foreign key to that Location table, then when you retrieve Location objects from the repository, you should also receive instances of the inherited classes.

If there are no common fields within Location, then there seems to be little benefit to having a unioned collection.

Jacob
@Jacob - agree (partially). Originally i created a Location table, and had the inheritance going. But the thing is, the "common fields" within the Location object are custom properties that are created based on business logic. Ie - URL slugs, formatted address, etc. So i would only be creating the table to satisfy the EDM requirements - should be the other way around (IMO). Appreciate the answer though - will rethink my design.
RPM1984
@Jacob - let me ask it this way. Lets say i dont have a Location table. How could i return a mixed bag of City, State, Street in a single collection from a repository? ie var heapsOfStuff = repository.Find().Where(s => Name.Contains("new york")). "Name" could be the abstract member shared by all types. Know what i mean? I want the UI to be able to go - "get me all locations that have "new york" in the name". Those locations could be anything (streets, citites, etc). How can i do that?
RPM1984
I'd go with the answer you accepted if there was no base `Location` entity.
Jacob