So I have done a lot of reading and it appears that, since DbContext already implements a repository and unit of work pattern combination, creating more abstraction classes would be redundant. However, I would still like to be able to encapsulate commonly-used queries for code reuse. I was thinking – perhaps I may be able to store a query-container class that uses the current instance of DbContext to make sure that queries don't cross transaction boundaries. Take for instance, this dummy code:
public partial class PersonEntities : DbContext
{
public PersonEntities() : base("name=SomeConnectionString")
{
this.Configuration.ProxyCreationEnabled = false;
this.CustomersQuery = new CustomerQueries(this);
}
public virtual DbSet<Customer> Customers { get; set; }
public virtual DbSet<Employees> Employees { get; set; }
public CustomerQueries CustomersQuery { get; }
public class CustomerQueries
{
protected PersonEntities Database { get; private set; }
internal CustomerQueries(PersonEntities db)
{
this.Database = db;
}
public IEnumerable<Customer> GetCustomersByName(string firstName, string lastName, bool ignoreCase = true)
{
/// method implementation
}
}
}
You might use it like this (concrete classes and lack of factories for simplification):
using (var context = new PersonEntities())
{
var query = context.CustomersQuery.GetCustomersByName("Alfred", "Morris", true);
/// stuff
await context.SaveChanges();
}
Pros:
- Does not add additional abstraction
- Queries can use multiple DbSets at once within the same transaction, unlike with many of the repository (especially generic repository) implementatinos I've seen.
- Related queries are neatly contained in a single place with mostly only relevant methods (plus the common object methods like ToString), meanwhile DbSet class itself has tons of methods thanks to IEnumerable/IQueryable/etc extension methods and thus extending DbSet didn't seem like a good idea.
- It also becomes possible to make some generic queries, such as:
GetPersonbyName<TPerson>(string firstName, stringLastName) where TPerson : Person
, perhaps contained in aPersonsQuery
class.
Cons
- Adds additional functionality that could be considered bloating, potentially leading to a 'god class' – however, these queries are still tightly coupled to the DbSets and are still in sub-classes so I'm not sure if this is the case.
- Perhaps others? I'm not really sure.
Are there any other drawbacks to using this style for code-reuse without redundant abstractions?
Best Answer
For this sort of thing I like to use C# extension methods against IQueryable<>. You would still have a class do define these but it wouldn't be nested in or referenced from your context.
Example:
You could then call this like: