Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Repository Methods vs. Extending IQueryable

I have repositories (e.g. ContactRepository, UserRepository and so forth) which encapsulate data access to the domain model.

When I was looking at searching for data, e.g.

  • finding a contact whose first name starts with XYZ
  • a contact whose birthday is after 1960

    (etc),

I started implementing repository methods such as FirstNameStartsWith(string prefix) and YoungerThanBirthYear(int year), basically following the many examples out there.

Then I hit a problem - what if I have to combine multiple searches? Each of my repository search methods, such as above, only return a finite set of actual domain objects. In search for a better way, I started writing extension methods on IQueryable<T>, e.g. this:

public static IQueryable<Contact> FirstNameStartsWith(
               this IQueryable<Contact> contacts, String prefix)
{
    return contacts.Where(
        contact => contact.FirstName.StartsWith(prefix));
}        

Now I can do things such as

ContactRepository.GetAll().FirstNameStartsWith("tex").YoungerThanBirthYear(1960);

However, I found myself writing extension methods (and inventing crazy classes such as ContactsQueryableExtensions all over, and I lose the "nice grouping" by having everything in the appropriate repository.

Is this really the way to do it, or is there a better way to achieve the same goal?

like image 954
Alex Avatar asked Sep 13 '09 01:09

Alex


3 Answers

I have been thinking about this a lot lately, after starting at my current job. I am used to Repositories, they go the full IQueryable path using just bare bones repositories as you suggest.

I feel the repo pattern is sound and does a semi-effective job at describing how you want to work with the data in the application domain. However the issue you are describing definitely occurs. It gets messy, fast, beyond a simple application.

Are there, perhaps, ways to rethink why you are asking for the data in so many ways? If not, I really feel that a hybrid approach is the best way to go. Create repo methods for the stuff you reuse. Stuff that actually it makes sense for. DRY and all that. But those one-offs? Why not take advantage of IQueryable and the sexy things you can do with it? It is silly, as you said, to create a method for that, but it doesn't mean you don't need the data. DRY doesn't really apply there does it?

It would take discipline to do this well, but I really think it's an appropriate path.

like image 164
Chad Ruppert Avatar answered Nov 18 '22 09:11

Chad Ruppert


@Alex - i know this is an old question, but what I would be doing would be letting the Repository do really simple stuff only. This means, get all records for a table or view.

Then, in the SERVICES layer (you are using an n-tiered solution, right? :) ) i would be handling all the 'special' query stuff there.

Ok, example time.

Repository Layer

ContactRepository.cs

public IQueryable<Contact> GetContacts()
{
    return (from q in SqlContext.Contacts
            select q).AsQueryable();
}

Nice and simple. SqlContext is the instance of your EF Context .. which has an Entity on it called Contacts .. which is basically your sql Contacts class.

This means, that method basically is doing: SELECT * FROM CONTACTS ... but it's not hitting the database with that query .. it's only a query right now.

Ok .. next layer.. KICK ... up we go (Inception anyone?)

Services Layer

ContactService.cs

public  ICollection<Contact> FindContacts(string name)
{
    return FindContacts(name, null)
}

public ICollection<Contact> FindContacts(string name, int? year)
{
   IQueryable<Contact> query = _contactRepository.GetContacts();
   
   if (!string.IsNullOrEmpty(name))
   {
       query = from q in query
               where q.FirstName.StartsWith(name)
               select q;
   }

   if (int.HasValue)
   {
       query = from q in query
               where q.Birthday.Year <= year.Value
               select q);
    }

    return (from q in query
            select q).ToList();
}

Done.

So lets recap. First, we start our with a simple 'Get everything from contacts' query. Now, if we have a name provided, lets add a filter to filter all contacts by name. Next, if we have a year provided, then we filter the birthday by Year. Etc. Finally, we then hit the DB (with this modified query) and see what results we get back.

NOTES:-

  • I've omitted any Dependency Injection for simplicity. It's more than highly recommended.
  • This is all pseduo-code. Untested (against a compiler) but you get the idea ....

Takeaway points

  • The Services layer handles all the smarts. That is where you decide what data you require.
  • The Repository is a simple SELECT * FROM TABLE or a simple INSERT/UPDATE into TABLE.

Good luck :)

like image 26
Pure.Krome Avatar answered Nov 18 '22 07:11

Pure.Krome


I realize this is old, but I've been dealing with this same issue lately, and I came to the same conclusion as Chad: with a little discipline, a hybrid of extension methods and repository methods seems to work best.

Some general rules I've been following in my (Entity Framework) application:

Ordering queries

If the method is used only for ordering, I prefer to write extension methods that operate on IQueryable<T> or IOrderedQueryable<T> (to leverage the underlying provider.) e.g.

public static IOrderedQueryable<TermRegistration> ThenByStudentName(
    this IOrderedQueryable<TermRegistration> query)
{
    return query
        .ThenBy(reg => reg.Student.FamilyName)
        .ThenBy(reg => reg.Student.GivenName);
}

Now I can use ThenByStudentName() as needed within my repository class.

Queries returning single instances

If the method involves querying by primitive parameters, it usually requires an ObjectContext and can't be easily made static. These methods I leave on my repository, e.g.

public Student GetById(int id)
{
    // Calls context.ObjectSet<T>().SingleOrDefault(predicate) 
    // on my generic EntityRepository<T> class 
    return SingleOrDefault(student => student.Active && student.Id == id);
}

However, if the method instead involves querying an EntityObject using its navigation properties, it can usually be made static quite easily, and implemented as an extension method. e.g.

public static TermRegistration GetLatestRegistration(this Student student)
{
    return student.TermRegistrations.AsQueryable()
        .OrderByTerm()
        .FirstOrDefault();
}

Now I can conveniently write someStudent.GetLatestRegistration() without needing a repository instance in the current scope.

Queries returning collections

If the method returns some IEnumerable, ICollection or IList, then I like to make it static if possible, and leave it on the repository even if it uses navigation properties. e.g.

public static IList<TermRegistration> GetByTerm(Term term, bool ordered)
{
    var termReg = term.TermRegistrations;
    return (ordered)
        ? termReg.AsQueryable().OrderByStudentName().ToList()
        : termReg.ToList();
}

This is because my GetAll() methods already live on the repository, and it helps to avoid a cluttered mess of extension methods.

Another reason for not implementing these "collection getters" as extension methods is that they would require more verbose naming to be meaningful, since the return type isn't implied. For example, the last example would become GetTermRegistrationsByTerm(this Term term).

I hope this helps!

like image 5
Rob Avatar answered Nov 18 '22 07:11

Rob