Skip to main content
Version: Next

Migrating from 1.7 to 1.7.1

The 1.7.1 — Obsoletes Cleanup release removes the long-deprecated IQueryableRepository, IPageableRepository, and IFilterableRepository interfaces (in both <TEntity> and <TEntity, TKey> arities), together with their associated extension classes:

  • Kista.PageableRepositoryExtensions
  • Kista.QueryableRepositoryExtensions
  • The AsFilterable() and AsQueryable() helper extensions on RepositoryExtensions

All functionality they provided is available through the Repository<TEntity, TKey> base class (via protected members) and through extension methods on IRepository<TEntity>. This is a breaking change for code that explicitly implemented, injected, or called the removed types — such code will no longer compile.

Why the interfaces were removed

The three legacy interfaces exposed query internals (IQueryable<T>, PageQuery<T>, IQueryFilter) as a public contract, leaking the data layer into consumer code. The same capabilities are already provided by the Repository<TEntity, TKey> abstract class through protected members, which keeps query composition inside the data layer and avoids the IQueryable<T> leak that produced NotSupportedExceptions far from the repository.

See the Repository Pattern page for the design rationale, and Customize the Repository for the recommended patterns going forward.

Migration table

Before (1.7.0)After (1.7.1)
Inject IQueryableRepository<T> / IQueryableRepository<T, TKey>Inject IRepository<T> and call a domain-specific method on a custom repository interface
Inject IPageableRepository<T> / IPageableRepository<T, TKey>Inject IRepository<T> and call GetPageAsync(PageRequest) for unsorted pagination, or a domain-specific paged method for filtered/sorted pages
Inject IFilterableRepository<T> / IFilterableRepository<T, TKey>Inject IRepository<T> and call extension methods (FindAllAsync, CountAsync, ExistsAsync, FindFirstAsync) that accept IQueryFilter / lambda expressions
repository.AsQueryable()Define a domain-specific method on a custom repository (e.g. IProductRepository.FindDiscontinuedAsync) implemented via the protected Queryable() hatch
repository.AsFilterable()Call FindAllAsync, CountAsync, ExistsAsync, FindFirstAsync directly on IRepository<T> (extension methods) or on the Repository<T,TKey> base class from a subclass
repository.GetPageAsync(PageQuery<T>)repository.GetPageAsync(PageRequest) for unsorted pages; for filtered/sorted pages expose a domain-specific paged method implemented via the protected QueryPageAsync(PageQuery<T>)
Class : Repository<T, TKey>, IQueryableRepository<T, TKey>Class : Repository<T, TKey> only — the Repository base class already provides the query capabilities through protected members

Replace AsQueryable() consumer code

The AsQueryable() extension leaked the underlying IQueryable<T> to consumers. Replace it with a domain-specific method on a custom repository, implemented through the protected Queryable() hatch.

Before (1.7.0):

// Consumer code — leaks IQueryable
public class ProductSearch {
private readonly IQueryableRepository<Product> _products;

public ProductSearch(IQueryableRepository<Product> products) {
_products = products;
}

public IReadOnlyList<Product> ActiveSorted() {
return _products.AsQueryable()
.Where(p => p.IsActive)
.OrderBy(p => p.Name)
.ToList();
}
}

After (1.7.1):

// Domain interface — query is encapsulated
public interface IProductRepository : IRepository<Product, Guid> {
Task<IReadOnlyList<Product>> FindActiveSortedAsync(CancellationToken ct = default);
}

// Implementation — uses the protected Queryable() hatch
public class ProductRepository : InMemoryRepository<Product, Guid>, IProductRepository {
public async Task<IReadOnlyList<Product>> FindActiveSortedAsync(CancellationToken ct = default) {
return await Queryable()
.Where(p => p.IsActive)
.OrderBy(p => p.Name)
.ToListAsync(ct);
}
}

// Consumer code — depends on the domain contract, not on IQueryable
public class ProductSearch {
private readonly IProductRepository _products;

public ProductSearch(IProductRepository products) {
_products = products;
}

public Task<IReadOnlyList<Product>> ActiveSorted(CancellationToken ct = default)
=> _products.FindActiveSortedAsync(ct);
}

Replace AsFilterable() and filter-based queries

Filter-based queries no longer require resolving IFilterableRepository<T>. Call the extension methods on IRepository<T> directly, or use the protected members on a Repository<T, TKey> subclass.

Before (1.7.0):

var filterable = repository.AsFilterable();
var items = await filterable.FindAllAsync(filter);
var count = await filterable.CountAsync(filter);
var exists = await filterable.ExistsAsync(filter);

After (1.7.1):

// Extension methods on IRepository<T> accept IQueryFilter or lambdas
var items = await repository.FindAllAsync(filter);
var count = await repository.CountAsync(filter);
var exists = await repository.ExistsAsync(filter);

// Lambda shorthand also works
var active = await repository.FindAllAsync(p => p.IsActive);

From inside a Repository<T, TKey> subclass, call the protected members directly:

public async Task<bool> CodeExistsAsync(string code, CancellationToken ct = default) {
var filter = new ExpressionQueryFilter<Product>(p => p.Code == code);
return await ExistsAsync(filter, ct); // protected member
}

Replace GetPageAsync(PageQuery<T>)

The public IPageableRepository<T>.GetPageAsync(PageQuery<T>) overload is gone. For unsorted pagination, use IRepository<T>.GetPageAsync(PageRequest):

var page = await repository.GetPageAsync(new PageRequest(page: 1, size: 20));

For filtered/sorted pagination, expose a domain-specific paged method on your custom repository, implemented through the protected QueryPageAsync(PageQuery<T>):

public interface IProductRepository : IRepository<Product, Guid> {
Task<PageQueryResult<Product>> FindByCategoryPagedAsync(
string category, PageRequest request, CancellationToken ct = default);
}

public class ProductRepository : InMemoryRepository<Product, Guid>, IProductRepository {
public async Task<PageQueryResult<Product>> FindByCategoryPagedAsync(
string category, PageRequest request, CancellationToken ct = default) {
var query = new PageQuery<Product>(request.Page, request.Size)
.Where(p => p.Category == category)
.OrderBy(p => p.Name);
return await QueryPageAsync(query, ct); // protected member
}
}

Remove the legacy interfaces from repository declarations

Concrete repositories that explicitly implemented the removed interfaces must drop those declarations. The Repository<TEntity, TKey> base class already provides the equivalent protected members.

Before (1.7.0):

public class LegacyRepository : Repository<Product, Guid>,
IQueryableRepository<Product, Guid>,
IPageableRepository<Product, Guid>,
IFilterableRepository<Product, Guid> {
// explicit interface method implementations...
}

After (1.7.1):

public class LegacyRepository : Repository<Product, Guid>, IProductRepository {
// domain-specific methods using protected members (Queryable(), ExistsAsync, etc.)
}

Drop any explicit interface method implementations (IFilterableRepository<...>.ExistsAsync, IPageableRepository<...>.GetPageAsync, etc.) — the base class now handles these through its protected members, and the public extension methods on IRepository<T> cover the consumer-side surface.

DI registration

AddRepository<T>() and ScanRepositories() no longer register IQueryableRepository<T>, IPageableRepository<T>, or IFilterableRepository<T> as services. This is expected — those interfaces no longer exist. Resolve IRepository<T> or your custom interface instead. See Registration for the current behaviour.

Reference