7

I have a repository class with some generic methods. One is

public IEnumerable<T> FindAll<T>(Expression<Func<T, bool>> predicate) where T : class
{
    return GetDbSet<T>().Where(predicate);
}

For unit testing I have a TestRepository which uses in-memory objects instead of a database. The TestRepository overrides the FindAll method and I want to control what is returned. So I want to be able to do something like this:

public override IEnumerable<T> FindAll<T>(Expression<Func<T, bool>> predicate)
{
    return MyEntities.Where(predicate).Cast<T>();
}

But MyEntities.Where() only accepts a Expression<Func<MyEntity, bool>>.

How can I cast/convert the generic expression to the strongly typed expression?

Chris Mantle
  • 6,595
  • 3
  • 34
  • 48
Nicklas Møller Jepsen
  • 1,248
  • 2
  • 16
  • 34
  • Have you thought about mocking your repository instead? – Jakub Konecki Nov 15 '13 at 11:03
  • @JakubKonecki: Isn't that exactly what he is doing here? – Jon Nov 15 '13 at 11:04
  • @Jon - no, he's stubbing not mocking. – Jakub Konecki Nov 15 '13 at 11:05
  • Reading about [Covariance & Contravariance](http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)) might help. – Saeed Neamati Nov 15 '13 at 11:05
  • @SaeedNeamati: Or it might not. No relation to this case. – Jon Nov 15 '13 at 11:06
  • @NicklasJepsen: I asked [this question](http://stackoverflow.com/q/2797261/50079) some time ago, which you will find interesting. – Jon Nov 15 '13 at 11:07
  • @Jon, how not related? It's like the [`Liskove's substitution principle`](http://en.wikipedia.org/wiki/Liskov_substitution_principle) here. If you can accept type A, then you should be able to accept subtypes of A too. He can extend his `MyEntities` and all other `Entity` classes he has, and pass those derived types to the expression. Am I incorrect? – Saeed Neamati Nov 15 '13 at 11:10
  • @Jon, I have thought about mocking as a solution for this specific case. But isn't it possible to make this conversion? I will read the question you linked to and see if I will change my mind. – Nicklas Møller Jepsen Nov 15 '13 at 11:15
  • @Grundy: A Collection of MyEntity. – Nicklas Møller Jepsen Nov 15 '13 at 11:16
  • @SaeedNeamati: Variance in C# is only applicable to generic interfaces and delegates. `Expression` is neither, so variance does not apply here. – Jon Nov 15 '13 at 11:18
  • you can try this `return MyEntities.Cast().Where(predicate);` – Grundy Nov 15 '13 at 11:19
  • @NicklasJepsen: It is possible, if I 'm not mistaken you have the same problem as I did with the difference that in my case the `T` was fixed -- which does not change anything when talking about expression trees. – Jon Nov 15 '13 at 11:20
  • @Grundy: That is exactly what I did try. Did you read the question? – Nicklas Møller Jepsen Nov 15 '13 at 11:22
  • in my comment before `cast` and then `where` in your - vice versa – Grundy Nov 15 '13 at 11:23
  • @Grundy: I'm sorry, I didn't read your comment.. Will try some solutions from these comments and return with the result. – Nicklas Møller Jepsen Nov 15 '13 at 11:28
  • @NicklasJepsen: Grundy's approach would work, at least on the surface: `return MyEntities.Cast().AsQueryable().Where(predicate);`. But that raises the question, what exactly would that be testing? – Jon Nov 15 '13 at 11:30
  • @Jon test will be work if `T` is `MyEntity` :-) – Grundy Nov 15 '13 at 11:32
  • @Jon: Your attempted solution in the post you refer to actually does work for me. – Nicklas Møller Jepsen Nov 15 '13 at 11:34
  • @NicklasJepsen: That solution is "hardcore" in the sense that it maintains the `IQueryable` property of your alternate data source. It looks like you don't need that here, so Grundy's would be much easier to use. It loses queryability, but do you care? – Jon Nov 15 '13 at 11:39
  • @Jon: If I try Grundy's solution I get: 'System.Collections.Generic.IEnumerable' does not contain a definition for 'Where' and the best extension method overload 'System.Linq.Queryable.Where(System.Linq.IQueryable, System.Linq.Expressions.Expression>)' has some invalid arguments. – Nicklas Møller Jepsen Nov 15 '13 at 11:43

2 Answers2

2

You can do something like this. Not sure whether it's a good idea, but it works. Basically, your overload can compare the type parameter T to your entity class. If the predicate has the right type, you can cast. Otherwise, you don't have anything to return.

public class MyEntity { public int x; }

MyEntity[] MyEntitiesList = Enumerable.Range(1,5).Select(y => new MyEntity() { x = y }).ToArray();

public IEnumerable<T> FindAll<T>(Expression<Func<T, bool>> predicate)
{
     if (typeof(T) == typeof(MyEntity))
     {
         return (IEnumerable<T>)MyEntitiesList.Where((predicate as Expression<Func<MyEntity, bool>>).Compile());
     }
     return new T[0];
}

Usage:

var res = FindAll((MyEntity y) => y.x % 2 == 0).ToList();
Console.WriteLine(res.Count);
Rob
  • 4,327
  • 6
  • 29
  • 55
0

It looks like your repository implementation has a major flaw in a sense that the caller is not aware that he can only pass arguments of certain types (e.g. it looks like I can do FindAll<int>(v => v > 0) but in fact, the underlying implementation only works with MyEntity). In other words, it's trying to be too "smart", it's not intuitive and error-prone.

One way of fixing this can be by introducing an interface/base class:

public interface IRepository<T>
{
  IEnumerable<T> FindAll<T>(Expression<Func<T, bool>> predicate);
}

// A base class that can carry helper functionality.
public abstract class Repository<T> : IRepository<T>
{
  private readonly IEnumerable<T> _entities;

  protected Repository(IEnumerable<T> entities)
  {
    _entities = entities;
  }

  public IEnumerable<T> FindAll(Expression<Func<T, bool>> predicate)
  {
    return _entities.Where(predicate);
  }
}

// Concrete implementation
public class MyEntityRepository : Repository<MyEntity>
{
  public MyEntityRepository() : base(new MyDbContext().MyEntities) { }
}

In the above example I'm referring to MyDbContext just for demonstration purposes (so it looks a bit more familiar if you worked with Entity Framework).

Now you can instantiate MyEntityRepository and use it throughout the application. In case you're using some sort of IoC, you can modify the code slightly:

public interface IMyEntityRepository : IRepository<MyEntity>
{
  // ...
}

public class MyEntityRepository : Repository<MyEntity>, IMyEntityRepository
{
  // ...
}

And now you can easily inject and mock IMyEntityRepository within your application.

Hope this helps.

UPDATE

As it turns out, since the given implementation is used for testing purposes only, you could try producing the expression of a desired type the following way:

return MyEntities.Where(Expression.Lambda<Func<MyEntity, bool>>(predicate.Body, 
  predicate.Parameters)).Cast<T>();

You can also apply some casting to the lambda parameter, if explicit one is needed.

volpav
  • 5,090
  • 19
  • 27
  • The implemented repository is only used for unit testing. I have a completely generic repository which I use to query the database. When not running the tests, all types are accepted. – Nicklas Møller Jepsen Nov 15 '13 at 11:32
  • I don't think this is the way to go for me because I do not want to have a repository for each different entity type. – Nicklas Møller Jepsen Nov 15 '13 at 11:55
  • @NicklasJepsen Are you referring to the last paragraph with `Expression.Lambda`? I left the first part in case someone with similar issues might want to consider using the approach I provided (by the way, we widely use it in our current project and it has proven to be quite flexible and maintainable). – volpav Nov 15 '13 at 12:02