How can I refactor this IQueryable<T> Repository Method?
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 开发者_如何学JAVAthe filtering/collection projection (delayed execution). The repository needs to return "queries", not concrete collections.
Appreciate the help.
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());
}
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.
精彩评论