-
Notifications
You must be signed in to change notification settings - Fork 246
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Restore ISingleResultSpecificationOfT #196
Conversation
I'm having second thoughts on the SingleResultSpecification classes... On one hand it does conform to the existing Specification base classes and it avoids the nessecity of defining the generic type twice. On the other hand, it creates the situation where developers can create ISingleResultSpecifications in multiple ways, which may lead to inconsistent code. interface IRepository<T>
{
// doesn't work for SingleCustomerSpecification_3
T Single(ISingleResultSpecification<T> spec);
// works for all specifications alternatives
T SingleWithoutGenericSingleResultSpecification<Spec>(Spec spec)
where Spec : ISingleResultSpecification, ISpecification<T>;
}
// option 1 -- uses the new SingleResultSpecification base class
class SingleCustomerSpecification_1 : SingleResultSpecification<Customer>
{
public SingleCustomerSpecification_1(int id)
{
Query.Where(s => s.Id == id);
}
}
// option 2 -- need to define the generic type twice
class SingleCustomerSpecification_2 : Specification<Customer>, ISingleResultSpecification<Customer>
{
public SingleCustomerSpecification_2(int id)
{
Query.Where(s => s.Id == id);
}
}
// option 3 -- cleanest declaration imo
class SingleCustomerSpecification_3 : Specification<Customer>, ISingleResultSpecification
{
public SingleCustomerSpecification_3(int id)
{
Query.Where(s => s.Id == id);
}
} Reviewing the code above, it seems to me that it might be better to remove the What do you guys think? |
I don't understand what is the point of public interface IReadRepositoryBase<T> where T : class
{
// other methods
Task<T?> GetBySpecAsync(ISpecification<T> specification, CancellationToken cancellationToken = default);
}
public abstract class RepositoryBase<T> : IRepositoryBase<T> where T : class
{
// other methods
public virtual async Task<T?> GetBySpecAsync(ISpecification<T> specification, CancellationToken cancellationToken = default)
{
return await ApplySpecification(specification).FirstOrDefaultAsync(cancellationToken);
}
} |
The
For the first point, consider the example below. Using the // The first word in this specification is plural, to indicate multiple customers are returned
public class CustomersWithAddressesAndPurchaseHistory : Specification<Customer>
{
public CustomersWithAddressesAndPurchaseHistory(bool includeAddress, bool includePurchaseHistory)
{
// Query logic
}
}
// The first word in the specification name is singular, but this is hard to detect.
// The ISingleResultSpecification marker interface is more obvious, which helps to understand the intent.
public class CustomerWithAddressesAndPurchaseHistory : Specification<Customer>, ISingleResultSpecification
{
public CustomerWithAddressesAndPurchaseHistory(int id, bool includeAddress, bool includePurchaseHistory)
{
// Query logic
}
} For the second point, consider the example below. When used in combination with carefully designed repository interfaces, one can generate compile time errors for when a developer accidentally uses the wrong specification. Imagine you have two specifications with similar names and untrivial constructor parameters, where one returns a list of entities and the other a single entity. We can then use the compiler to generate errors if one accidentally picked the wrong specification. interface IRepository<T>
{
T List(ISpecification<T> spec);
T Single<Spec>(Spec spec)
where Spec : ISingleResultSpecification, ISpecification<T>;
} Of course, finding good naming conventions and/or well chosen constructors also help in avoiding these kind of mistakes. And even though some might argue that marker interfaces shouldn't be used because they don't actually do anything, I still believe the examples shown here here illustrate that using marker this interfaces is in fact beneficial. |
The generic |
@vittorelli Thank you for explanation. But is it really a specification responsibility to define resulting object? Would you create marker interfaces for TwoResults, ThreeResults, NResults, IAsyncEnumerableResult or ListResult, or other complex type specifications? If so, should ISomeResultSpecification create that result and not just mark that it 'wants' it? IMHO Specification builds query and not materializes it (or tells you how to materialize). Sorry if this is not a right place for such discussion 😅 |
Hi, The As for the additional base class EDIT: Just to clarify, I don't have a strong opinion here. If the community wants it OOTB, sure we can add it. |
Ok, I agree. Better to keep the package clean and leave it up to end users if they want to use I've updated the PR so it only restores the generic interface so as to not have breaking changes in the next NuGet release. |
Thank you @vittorelli. We appreciate your help. |
The ISingleResultSpecificationOfT seems to be have been removed in 73693ac while being included in the current Nuget package. I assume this was accidental?
Furthermore, I've added two base classes SingleResultSpecification based on the existing Specification classes.