'Cast IQueryable<DomainModel> to IQueryable<EntityModel> for EntityFramework

I'm trying to convert an IQueryable to IQueryable to allow me to take advantage of the dynamic filtering offered by Linq in the EntityFramework context. I'm trying to adhere to repository pattern and not expose anything EntityFramework related outside of the repository.

The code...

My Domain Model

public class Invoice
{
    public Invoice()
    {
        InvoiceItems = new HashSet<InvoiceItem>();
    }

    public Guid Id { get; set; }
    public string Number { get; set; }
    public DateTime UtcDate { get; set; }
    public DateTime? UtcDueDate { get; set; }
    public decimal Amount { get; set; }
    public bool IsPublished { get; set; }
    public bool IsPosted { get; set; }
    public DateTime UtcCreatedAt { get; set; }
    public DateTime? UtcUpdatedAt { get; set; }
    public DateTime? UtcDeletedAt { get; set; }

    public ICollection<InvoiceItem> InvoiceItems { get; set; }
}

My Entity Model

public class InvoiceModel
{
    public InvoiceModel()
    {
        InvoiceItems = new HashSet<InvoiceItemModel>();
    }

    public int Id { get; set; }
    public Guid PublicId { get; set; }
    public string Number { get; set; }
    public DateTime UtcDate { get; set; }
    public DateTime? UtcDueDate { get; set; }
    public decimal Amount { get; set; }
    public bool IsPublished { get; set; }
    public bool IsPosted { get; set; }
    public DateTime UtcCreatedAt { get; set; }
    public DateTime? UtcUpdatedAt { get; set; }
    public DateTime? UtcDeletedAt { get; set; }

    public ICollection<InvoiceItemModel> InvoiceItems { get; set; }
}

My Repository

public class SampleRepository : ISampleRepository
{
    private readonly IDbContextFactory<FinancialsSqlDbContext> _factory;

    public SampleRepository(IDbContextFactory<FinancialsSqlDbContext> factory)
    {
        _factory = factory;
    }

    #region Entity Mapping Methods
    private Invoice FromEntity(InvoiceModel invoice)
        => new Invoice
        {
            Id = invoice.PublicId,
            Number = invoice.Number,
            UtcDate = invoice.UtcDate,
            UtcDueDate = invoice.UtcDueDate,
            Amount = invoice.Amount,
            IsPublished = invoice.IsPublished,
            IsPosted = invoice.IsPosted,
            UtcCreatedAt = invoice.UtcCreatedAt,
            UtcUpdatedAt = invoice.UtcUpdatedAt,
            UtcDeletedAt = invoice.UtcDeletedAt
        };

    private InvoiceModel ToAddEntity(Invoice invoice)
        => new InvoiceModel
        {
            Number = invoice.Number,
            UtcDate = invoice.UtcDate,
            UtcDueDate = invoice.UtcDueDate,
            Amount = invoice.Amount,
            IsPublished = invoice.IsPublished,
            IsPosted = invoice.IsPosted,
            UtcCreatedAt = DateTime.UtcNow
        };

    private InvoiceModel ToUpdateEntity(InvoiceModel model, Invoice invoice)
        => new InvoiceModel
        {
            Id = model.Id,
            PublicId = model.PublicId,
            Number = invoice.Number,
            UtcDate = invoice.UtcDate,
            UtcDueDate = invoice.UtcDueDate,
            Amount = invoice.Amount,
            IsPublished = invoice.IsPublished,
            IsPosted = invoice.IsPosted,
            UtcCreatedAt = model.UtcCreatedAt,
            UtcUpdatedAt = DateTime.UtcNow
        };

    private InvoiceModel ToRemoveEntity(InvoiceModel model, Invoice invoice)
        => new InvoiceModel
        {
            Id = model.Id,
            PublicId = model.PublicId,
            Number = invoice.Number,
            UtcDate = invoice.UtcDate,
            UtcDueDate = invoice.UtcDueDate,
            Amount = invoice.Amount,
            IsPublished = invoice.IsPublished,
            IsPosted = invoice.IsPosted,
            UtcCreatedAt = model.UtcCreatedAt,
            UtcUpdatedAt = model.UtcUpdatedAt
        };
    #endregion

    public async Task<List<Invoice>> ListAsync(Func<IQueryable<Invoice>, IQueryable<Invoice>> predicate = null)
    {
        using (var context = _factory.CreateDbContext())
        {
            var query = predicate != null ? predicate.Invoke(new List<Invoice>().AsQueryable()).ToDTO<Invoice, InvoiceModel>() : context.Invoices.AsQueryable();

            var result = await query.ToListAsync();

            return result.Select(FromEntity).ToList();
        }
    }
}

The method that throws is

public async Task<List<Invoice>> ListAsync(Func<IQueryable<Invoice>, IQueryable<Invoice>> predicate = null)
{
    using (var context = _factory.CreateDbContext())
    {
        var query = predicate != null ? predicate.Invoke(new List<Invoice>().AsQueryable()).ToDTO<Invoice, InvoiceModel>() : context.Invoices.AsQueryable();

        var result = await query.ToListAsync();

        return result.Select(FromEntity).ToList();
    }
}

My latest attempt is from this answer Cast IQueryable<EntityObject> to IQueryable<Specific> which doesn't work when I call ToListAsync() as it throws the following exception:

System.InvalidOperationException: The source 'IQueryable' doesn't implement 'IAsyncEnumerable<Financials.Server.Data.Sql.Models.InvoiceModel>'. Only sources that implement 'IAsyncEnumerable' can be used for Entity Framework asynchronous operations.
  at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.AsAsyncEnumerable[TSource](IQueryable`1 source)
  at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
  at Financials.Server.Data.Sql.Repositories.SampleRepository.ListAsync(Func`2 predicate)

I am not quite sure the best way to go about doing this or if it is even possible.



Solution 1:[1]

Try AutoMapper. It allow you to Map from One entity to Another and have a multiple ways to work with IQueryable and EF. If you have any question, check out this PoC.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 José Ramón Barro