I'm currently setting up a new project, and I have run into a few things, where I need a little input.
This is what i'm considering:
I would like a generic repository
I don't want to return IQueryable from my repository.
I would like to encapsulate my queries in specifications.
I have implemented the specification pattern
It needs to be easily testable
Now this is where I get a little stuck and my question is which way would be the most elegant way of calling the find method with one or more specifications:
(Fluent): bannerRepository.Find().IsAvailableForFrontend().IsSmallMediaBanner()
or express queries as lambdas with my specifications
(Lambda): bannerRepository.Find.Where(banner => banner.IsFrontendCampaignBanner && banner.IsSmallMediaBanner)
or maybe some completely other way? Most important thing is, that the guy implementing the MVC front, should have a good intuitive experience of the repository.
What I am hoping to achieve is to keep som flexibility with regard to being able to combine specifications, and give the experience of "filtering" with the specfications, but without leaking an IQueryable to the controller, but more like an ISpecifiable, that only allows to modify the query with specifications and not with Linq. But am i just back at leaking query logic to the controller this way?
The Repository pattern. Repositories are classes or components that encapsulate the logic required to access data sources. They centralize common data access functionality, providing better maintainability and decoupling the infrastructure or technology used to access databases from the domain model layer.
In computer programming, the specification pattern is a particular software design pattern, whereby business rules can be recombined by chaining the business rules together using boolean logic. The pattern is frequently used in the context of domain-driven design.
The Repository pattern allows you to easily test your application with unit tests. Remember that unit tests only test your code, not infrastructure, so the repository abstractions make it easier to achieve that goal.
The repository pattern is intended to create an abstraction layer between the data access layer and the business logic layer of an application. It is a data access pattern that prompts a more loosely coupled approach to data access.
I have seen some Fluent API's that uses Properties for specifications, so they don't add the parenthesis noise to the clients.
bannerRepository.Find.IsAvailableForFrontend.IsSmallMediaBanner.Exec()
Being Exec() a method for executing the specifications against the repo.
but even if you don't use the properties, I would go for the fluent API, since it has the minimum noise.
or maybe some completely other way?
Well, actually I don't get exactly your repository implementation (e.g. what will the method .Find()
return?), but I would choose another direction:
public class Foo
{
public Int32 Seed { get; set; }
}
public interface ISpecification<T>
{
bool IsSatisfiedBy(T item);
}
public interface IFooSpecification : ISpecification<Foo>
{
T Accept<T>(IFooSpecificationVisitor<T> visitor);
}
public class SeedGreaterThanSpecification : IFooSpecification
{
public SeedGreaterThanSpecification(int threshold)
{
this.Threshold = threshold;
}
public Int32 Threshold { get; private set; }
public bool IsSatisfiedBy(Foo item)
{
return item.Seed > this.Threshold ;
}
public T Accept<T>(IFooSpecificationVisitor<T> visitor)
{
return visitor.Visit(this);
}
}
public interface IFooSpecificationVisitor<T>
{
T Visit(SeedGreaterThanSpecification acceptor);
T Visit(SomeOtherKindOfSpecification acceptor);
...
}
public interface IFooRepository
{
IEnumerable<Foo> Select(IFooSpecification specification);
}
public interface ISqlFooSpecificationVisitor : IFooSpecificationVisitor<String> { }
public class SqlFooSpecificationVisitor : ISqlFooSpecificationVisitor
{
public string Visit(SeedGreaterThanSpecification acceptor)
{
return "Seed > " + acceptor.Threshold.ToString();
}
...
}
public class FooRepository
{
private ISqlFooSpecificationVisitor visitor;
public FooRepository(ISqlFooSpecificationVisitor visitor)
{
this.visitor = visitor;
}
public IEnumerable<Foo> Select(IFooSpecification specification)
{
string sql = "SELECT * FROM Foo WHERE " + specification.Accept(this.visitor);
return this.DoSelect(sql);
}
private IEnumerable<Foo> DoSelect(string sql)
{
//perform the actual selection;
}
}
So I have an entity, its specification interface and several implementors involved in a visitor pattern, its repository interface accepting a specification interface and its repository implementation, accepting a visitor capable to translate specifications into SQL clauses (but it's just a matter of this case, of course). Finally, I would compose specification "outside" the repository interface (using fluent interface).
Maybe this is just a naive idea, but I find it quite straightforward. Hope this helps.
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