2

I'm writing a layered ASP.Net Application which consist of Bussiness layer, Repository layer, service Layer... . In repository layer I'm using EntityFramework as ORM. In service layer, I want to pass a query in lambda form (which includes OrderBy or OrderByDescending ,take, skip,...) to repository layer and run the query on an DbSet and return result entities.


In Simple words : (How I can do something like the following mock code in asp.net c#)
    public class Repository
    {
      public List<Book> findby(var query)
      {
        var dbcontext = DataContextFactory.GetDataContext();
        //The following line should do this :  dbcontext.Books.Where(B=>B.New==true && B.Id>99).OrderBy(B=>B.Date).ThenBy(B=>B.Id).Skip(2).Take(10);
        List<Book> matchedBooks = RunQueryOnBooks(dbcontext.Books,query); 
        return matchedBooks;
      }
    }

    public class Service
    {
      public List<Book> getTopNewBooks(Repository _repository)
      {
        var query = Where(B=>B.New==true && B.Id>99).OrderBy(B=>B.Date).ThenBy(B=>B.Id).Skip(2).Take(10);
        List<Book> matchedBooks = _repository.findby(query);
        return matchedBooks;
      }
    }

So the question is:

  • which type I should use instead of var for query (If there is any and is possible)
  • how I execute the query on dbcontext.Books ?

Please give a good and easy example like mine and more references. thanks in advance.

Iman Mahmoudinasab
  • 6,861
  • 4
  • 44
  • 68
  • 2
    Isn't allowing the execution of arbitrary queries defeating the purpose of the repository pattern? – Vaughan Hilts Jul 26 '13 at 20:39
  • You can just return `IQueryable` from your repository and then compose the query in service layer. The abstraction you're trying to create seems rather useless to me and is reinventing the wheel. – Slauma Jul 26 '13 at 20:49
  • @VaughanHilts : I'm newbie to Design-patterns, You mean I shouldn't do this because of repository pattern goal? why? what is wrong with me? I'm using Query Object Pattern to communicate between Service Layer and Repository Layer, which I think always be Entity Framework so I decide to use lambda as my Query Object. please give me more detail in simple words and examples or references. thanks. – Iman Mahmoudinasab Jul 27 '13 at 05:24

4 Answers4

1

I think you probably want simply a Func<IQueryable<Book>, IQueryable<Book>>:

void RunQueryOnBooks(DbSet<Book> set, Func<IQueryable<Book>, IQueryable<Book>> query)
{
    return query(set.AsQueryable());
}

The query input could then be a lambda:

Repository.FindBy(set => set.Where(B => B.new == true ... ));
McGarnagle
  • 101,349
  • 31
  • 229
  • 260
  • tnx, but you define Books as `IEnumerable set` while books is `DbSet` if you notice `dbcontext.Books`. Maybe there is a way to convert but the important thing is that I want to run lambda on `DbSet` and my lambda being translated to sql and sql server give me 10 records (not getting all 1000000 records of the table and then run lambda on them, for performance reason.) – Iman Mahmoudinasab Jul 27 '13 at 07:26
  • @Iman but I think you can just replace the first one with DbSet, no? Should be the same idea in principle... – McGarnagle Jul 27 '13 at 07:29
  • No I can't replace `IEnumerable set` with `DbSet set` because `query(set)` only accepts `IEnumerable set` and I have to convert from DbSet to IEnumerable if possible and if don't hurt performance as I mention before. Any Idea? – Iman Mahmoudinasab Jul 27 '13 at 07:40
  • @Iman not sure I'm understanding, sorry :( Maybe use IQueryable instead of IEnumerable? – McGarnagle Jul 27 '13 at 07:46
  • Yess, I changed `Func, IEnumerable>` to `Func, IQueryable>` and `query(set)` to `query(set.AsQueryable())`, It works nice , I check the generated sql for performance it was ok too because I use IQueryable instead of IEnumerable(more info: [link](http://www.codeproject.com/Tips/468215/Difference-Between-IEnumerable-and-IQueryable)). The only thing now is a question to me and just an expert of design-paterns can replay: is this approach ok for sending query from service layer to data access layer(repository layer)? – Iman Mahmoudinasab Jul 27 '13 at 10:54
  • @Iman Hard to say -- I agree with the commenter above that it tends defeats some of the point of a data access layer (there's little encapsulation if the services are allowed to run whatever Sql/Linq they like). On the plus side, such a data access layer is flexible for its consumers! – McGarnagle Jul 27 '13 at 17:58
  • This is not Service layer it is domain service which normally should run any query(Sql/Linq) on the dbcontext/tables to get the entities/records.I'm trying to following Query Object Pattern.You send your query from domain service to repository and query will be translated to the database language and return result to domain service.I think the commenter means If i use `Func, IQueryable>` there is no need to repository because it does any thing. -- Ok,thnx for all, I will search more, plz edit your answer to change it to accepted. – Iman Mahmoudinasab Jul 28 '13 at 16:30
  • @Iman only you can accept ... not sure, maybe accepting is blocked because the question got put "on hold" / closed. – McGarnagle Jul 28 '13 at 18:27
  • @@McGarnagle I know only I can accept it, but edit it change the code according to my comments. then I should accept it , but now it is not correct. Change Func, IEnumerable> to Func, IQueryable> and query(set) to query(set.AsQueryable()). – Iman Mahmoudinasab Jul 28 '13 at 21:55
  • @Iman ah, gotcha - updated. – McGarnagle Jul 28 '13 at 22:08
0

LINQ uses Lambda Expressions, which can be represented as Funcs. As long as you build a Func that returns the appropriate type and takes the appropriate parameter type, you'll be all set.

var myFunc = new Func<IEnumerable<Book>, IEnumerable<Book>>(book => book.Where(i => true /* rest of your code here */));
var test = Enumerable.Empty<Book>();
var result = myFunc(test);
Haney
  • 32,775
  • 8
  • 59
  • 68
0

If you think about it, what you're saying is that you want to take some collection of books as input and give back some filtered/ordered collection of books back. Thus, the appropriate type for query would be Func<IEnumerable<Books>, IEnumerable<Books>.

With that in mind, I'd probably make the query a function (getTopNewBooks) instead of a lambda, but both will work. Either way, you can pass the delegate method to the findby function as something like:

public class Application
{
  public static void Main()
  {
    var repo = new Repository();
    foreach (var topBook in repo.findby(Service.getTopNewBooks))
    {
      // This is a matching top book
    }
  }
}

public class Repository
{
  public List<Book> findby(Func<IEnumerable<Book>,IEnumerable<Book>> query)
  {
    var dbcontext = DataContextFactory.GetDataContext();
    List<Book> matchedBooks = query(dbcontext.Books).ToList(); 
    return matchedBooks;
  }
}

public class Service
{
  public static IEnumerable<Book> getTopNewBooks(IEnumerable<Book> input)
  {
    return input.Where(B=>B.New==true && B.Id>99).OrderBy(B=>B.Date).ThenBy(B=>B.Id).Skip(2).Take(10);
  }
}
Andrew Coonce
  • 1,557
  • 11
  • 19
0

If you hover over the "var" in Visual Studio, it will tell you the inferred type. You can use that to show what type your query ends up being.

lukegravitt
  • 892
  • 4
  • 12