'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 |