Home > Mobile >  What is the best way to isolate .ToListAsync()?
What is the best way to isolate .ToListAsync()?

Time:12-15

I have a Blazor App which uses EntityFrameworkCore and a Generic repository.

In my Services I query the repository, but when I try to call .ToListAsync(); - it requires that I add - using Microsoft.EntityFrameworkCore; This means it will create a dependency to EF. I don't want to bind my service to EF.

.ToListAsync() is an extension method. What is the best way to isolate the Service from it? I achieved it through an additional wrapper class - AsyncConverter. Is there a better way?

public class AsyncConverter : IAsyncConverter
{
    public Task<List<TSource>> ConvertToListAsync<TSource>(IQueryable<TSource> source, CancellationToken cancellationToken = default)
    {
        return source.ToListAsync(cancellationToken);
    }
}

public class EfRepository<TEntity> : IRepository<TEntity>
        where TEntity : class
{
    public EfRepository(ApplicationDbContext context)
    {
        this.Context = context ?? throw new ArgumentNullException(nameof(context));
        this.DbSet = this.Context.Set<TEntity>();
    }

    protected DbSet<TEntity> DbSet { get; set; }

    protected ApplicationDbContext Context { get; set; }

    public virtual IQueryable<TEntity> All() => this.DbSet;
}




public class ItemsDataService : IItemsDataService
{
    private readonly IRepository<Item> _itemsRepository;
    private readonly IMapper _mapper;
    private readonly IAsyncConverter _asyncConverter;
    private readonly IStringLocalizer<Errors> _localizer;


    public ItemsDataService(IRepository<Item> itemsRepository, 
        IMapper mapper,
        IAsyncConverter asyncConverter,
        IStringLocalizer<Errors> localizer = null)
    {
        _itemsRepository = itemsRepository;
        _mapper = mapper;
        _asyncConverter = asyncConverter;
        _localizer = localizer;
    }

    public async Task<Response<IEnumerable<ItemNameDto>>> GetItemsNamesAsync(string searchWord, string userId)
    {
        try
        {
            searchWord.ThrowIfNull();
            userId.ThrowIfNullOrEmpty();

            var query = _itemsRepository.All()
                .Where(x => x.UserId == userId);

            var names = new List<ItemNameDto>();

            if (!string.IsNullOrEmpty(searchWord))
            {
                query = query.Where(x => x.Name.ToLower().Contains(searchWord.ToLower()));
            }

            var queryProjection = _mapper.ProjectTo<ItemNameDto>(query); **/*.ToListAsync(); - This would add a dependency to EntityFrameworkCore. That it why I introduced AsyncConverter*/**
            names = await _asyncConverter.ConvertToListAsync(queryProjection);

            var response = ResponseBuilder.Success<IEnumerable<ItemNameDto>>(names);
            return response;
        }
        catch (Exception ex)
        {
            var response = ResponseBuilder.Failure<IEnumerable<ItemNameDto>>("An error occured while getting items names.");
            return response;
        }
    }

CodePudding user response:

My usual approach is to put all the query-building code in my repositories, and have them return a materialized result. So I can mock the repository layer when unit-testing my services class.

Trying to unit-test the data access layer can be fraught, since it's inherently designed to be an integration layer. Queries that work one way when you are using in-memory objects may have different behavior when they're translated by Entity Framework.

If you prefer building your queries in the service layer, Ian Kemp's approach can work, basically putting your ConvertToListAsync method into the EfRepository class and its interface instead of having a whole new service to inject.

  • Related