views:

95

answers:

4

Is this a good or bad idea?

public interface IRepository<T>
{
    ...
    IList<T> Get(Func<T, bool> predicate)
    ...
}

I mean - it seems really powerful addition and i could implement it too (at least - to some level) but my guts tells me it's kind a wrong. Could anyone enlighten me?

+3  A: 

I think this is better :

public interface IRepository<T>
{
IQueryable<T> GetAll();
}

then you can write your custom queries as :

var employees=Repositroy<Employee>.GetAll().Where(emp=>emp.Salary>10000).Select(emp=>emp).ToList();
Beatles1692
It is if we know that exposing full querying API ain't bad idea.
Arnis L.
+1  A: 

Well, it certainly is a powerful feature. The problem is: introducing it in the interface will force all implementations of IRepository to provide a suitable definition -- which might not be possible (or at least really hard) depending on the implementation (say, an implementation backed by a database connection or something).

Maybe, you should instead do

public interface IRepository<T>
{
    ...
}

public interface IAdvancedRepository<T>: IRepository<T> 
{
    IList<T> Get(Func<T, bool> predicate)
}

and provide Get only, if the implementation can do it efficiently.

Dirk
Thought about this too. But i wanted to know - is that's a good idea to provide this kind of API (querying by hyper generic predicate) in general. That leads to abscence of necessity of repository methods like 'FindByCountryName' and 'GetByLastName'. Basically - i'm moving outside from repositories an answer to question what exactly i'm querying. It just seems that i'm missing something and this can lead to serious problems in future.
Arnis L.
The problem is, that you are effectively forcing a "full table scan" with this solution, that is, even if your repository implementation had an index on (say) LastName (a hash table, a DB index, etc.), you could not use this in the implementation of Get, as you don't know, which properties get actually tested by the predicate. This imposes severe constraints on the implementation of `IRespository`.
Dirk
You shouldn't delete that point about indexes. I think that's a valid point. Lack of explicitness can lead to harder maintenance of database.
Arnis L.
A: 

I tend to link 'Get' with getting one simple instance, not a list. Therefore, when I have a method which returns a collection, I usually name that method 'Find' or something like that.

(Note that FxCop will tell you it is a bad idea to use 'Get' as a function name).

Ah, Ok, I missed the point. :) I would not create a method which takes a a criteria as an argument. I'd rather have specialized methods on my repository instead of passing in all sorts of criteria. I know, it is not as flexible, but it is just more explicit.

Frederik Gheysels
That's already closer to point. But is it just about explicitness?
Arnis L.
A: 

For me it smells like you don't really need a repository as .NET devs usually understand it.

I think you need something like this:

public interface IStore {
    // Low-level abstraction over a real database
}

public interface IAction<TInput, TResult> { // Or maybe ICommand?
    TResult Execute(TInput input);
}

// then:
public interface IGetCustomerById : IAction<int, Customer> {
}
public class GetCustomerById : IGetCustomerById {
    IStore store;
    public GetCustomerById(IStore store) {
        this.store = store;
    }

    public Customer Execute(int id) {
        return store.ResultOfExecutedSqlOrWhatever(id);
    }
}
// it might not only be READ action but more complex one, reusing other actions too
public class ApproveCustomer<ApprovalInfo,ApprovalResult> : IAction {
    IStore store;
    IGetCustomerById getCustomer;
    public ApproveCustomer(IStore store, IGetCustomerById getCustomer) {
        this.store = store;
        this.getCustomer = getCustomer;
    }

    public ApprovalResult Execute(ApprovalInfo approval) {
        var customer = getCustomer.Execute(approval.CustomerId);
        var result = new ApprovalResult();
        if (customer == null) {
            result.Message = "No such customer";
            return result;
        }
        if (!customer.IsEligibleForSomething()) { // This can be another action!
            result.Message = "Not eligible"
            return result;
        }
        result.Message = "Successfully approved"
        result.IsApproved = true;
        return result;
    }
}

Additional benefit here is that all these actions can be easily used with dependency injection.

Dmytrii Nagirniak
What about drawbacks?
Arnis L.
One drawback is probably it does require a bit more code.
Dmytrii Nagirniak