Preamble:
My core question is very similar to this one: How can I write a clean Repository without exposing IQueryable to the rest of my application? which has remained unanswered. I am hoping that if I approach the problem in a different way, and ask a slightly different question, I may get a result. I will repeat some of the content from this other question to avoid requiring readers to read it for context.
Problem:
I'm working with POCO entities, and Entity Framework 4. I am trying to allow for sophisticated ad-hoc filtering of entity sets at the application layer, while simultaneously trying to avoid exposing IQueryable<T>
past my repository boundary. This is leaving me with some complications.
I do not want to create a single massive filter method on the repository that takes a huge list of parameters, such as:
IEnumerable GetFilteredCustomers(string nameFilter, string addressFilter, bool isActive, int customerId, ...)
Not only is this extremely cumbersome to use, but it's super ugly to look at, especially if it's mostly a bunch of nulls, etc. It's also not as maintainable as I would like.
I do not want to create a huge set of filter methods on the repository, such as:
IEnumerable GetActiveCustomers()
IEnumerable GetCustomersByName()
There are a number of problems with this approach, including needing a huge list of methods which grows to n!
where n is the number of available filter conditions if I want to be able to combine them in arbitrary ways. (i.e. all active customers with name George). Also highly difficult to maintain.
I do not want to create chainable methods (Fluent Interface) that manipulate IEnumerable<T>
, because ultimately that involves bringing back a huge result set from the database and filtering it down in-memory which is not a scalable solution.
I can't create a Fluent Interface that manipulates IQueryable<T>
because as I've already said, I don't want to expose the IQueryable<T>
past the repositories.
I'd like to avoid simply rehashing the single massive filter method by passing in an object full of parameters instead of a large parameter list, although at this point this might be the least ugly solution.
Ideas:
Ultimately, I think an ideal solution would be discovering some way to create a full query that doesn't know the source, and store it as a parameter. I could then pass that into the repository, where the source is known, and apply the query to the source and return the results.
To clarify; in contrast to simply creating an object of parameters as mentioned above, I'd like to use the raw LINQ queries, but store them in a variable somehow, and apply them to a data source later. I suspect the return type would have to be known ahead of time, but I'm perfectly fine with defining that and having it known in advance.
To view it from yet another perspective, consider the following:
IQueryable<Customer> filteredCustomers = customerRepository.GetAll()
.Where(c => c.FirstName == "Dave")
.Where(c => c.IsActive == true)
.Where(c => c.HasAddress == true)
;
I want to package up the three Where clauses as a query object, completely separate from the customerRepository.GetAll(), pass it around as a parameter and apply it later.
It will return an empty enumerable. It won't be null.
LINQ provides you three different ways to write a LINQ query in C# or VB.
In a LINQ query, you are always working with objects. You use the same basic coding patterns to query and transform data in XML documents, SQL databases, ADO.NET Datasets, . NET collections, and any other format for which a LINQ provider is available.
There are the following two ways to write LINQ queries using the Standard Query operators, in other words Select, From, Where, Orderby, Join, Groupby and many more. Using lambda expressions. Using SQL like query expressions.
Sure. You can write a method like:
public Expression<Func<Customer, bool>> GetDave()
{
return c => c.FirstName == "Dave"
&& c.IsActive
&& c.HasAddress;
}
...and repository methods like:
public IEnumerable<Customer> GetOneGuy(Expression<Func<Customer, bool>> criteria)
{
return Context.Customers.Where(criteria);
}
...and call:
var dave = Repository.GetOneGuy(this.GetDave()).Single();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With