views:

90

answers:

2

Hi Guys,

Come across a problem with the repository pattern combined with the use of abstract classes.

I have a repository which implements a single method returning an ICollection of an abstract type.

Here's my abstract class:

public abstract class Location
{
   public abstract string Name { get; set; }
   public abstract LocationType Type { get; }
}

Here's a concrete implementation of that abstract class:

public class Country : Location
{
   public override string Name { get; set; }
   public override LocationType Type { get { return LocationType.Country; } }
}

Here's my repository:

public class LocationsRepository : Locations.Repository.ILocationsRepository
{
   public ICollection<Location> GetAllLocations()
   {
      Country america = new Country { Name = "United States" };
      Country australia = new Country { Name = "Australia" };
      State california = new State { Name = "California", Country = america };

      return new List<Location>() { america, australia, california };
    }
}

All good so far.

Now the service:

public class CountryService : ICountryService
{
   private ILocationsRepository repository;

   public CountryService()
   {
      // in reality this is done by DI, but made 'greedy' for simplicity.
      this.repository = new LocationsRepository();
   }

   public List<Country> GetAllCountries()
   {
      // errors thrown by compiler
      return repository.GetAllLocations()
                       .Where(l => l.Type == LocationType.Country)
                       .ToList<Country>();
   }
}

There's the problem. I'm trying to return a list of concrete types (Country) from a repository which returns an ICollection<T> of an abstract type.

Get 2 compile-time errors:

'System.Collections.Generic.IEnumerable' does not contain a definition for 'ToList' and the best extension method overload 'System.Linq.ParallelEnumerable.ToList(System.Linq.ParallelQuery)' has some invalid arguments

and

Instance argument: cannot convert from 'System.Collections.Generic.IEnumerable' to 'System.Linq.ParallelQuery'

So, how can i implement this pattern?

I can kind of understand the issue (you can't instantiate an abstract type), so does the Enumerator (.ToList) attempt to instantiate this, hence the error?

In case you don't understand what im trying to do:

  • I want my repository to return an ICollection<T> of an abstract type
  • I want my services (i will have one for each concrete type) to return a List of concrete types based on that single repository method

Is this just a case of LINQ syntax? Or is my design pattern totally wrong?

+1  A: 

The solution to your problem is pretty easy, you need to create a new Country in your LINQ expression:

return repository.GetAllLocations()
    .Where(l => l.Type == LocationType.Country)
    .Select(l => l as Country).ToList();

I think you were mistaking the generic ToList<T> method to being able to create a list of a new type, wheres T is always inferred from the source collection. Generally, whenever you want to convert a collection of one type to a collection of items of another type you use Select.

Igor Zevaka
I believe Igor is correct - cast it to a new element in the select clause. I do this all of the time in my repos where i map Linq-to-Sql entities to my domain entities, and this inline Select() looks accurate. +1 I also use internal property setters/gettings as well, allowing me to set internals to the domain Entity, that uses logic to make additional members publically accessable.
eduncan911
This works too - but im leaning towards @Necros answer because, well...less code is better (and it doesnt rely on the enum)Can anyone envision any differences between this answer and @Necros answer?
RPM1984
+4  A: 
repository.GetAllLocations().OfType<Country>().ToList();

And you don't even need the LocationType enum

Necros
+1 That is totally the best solution.
Igor Zevaka
Awesome, so it was a LINQ issue, not a abstract/repository issue. Thanks!!
RPM1984