Async enumeration of filtered projection produces exception
See original GitHub issueSource/destination types
public class Caseload
{
public int Id { get; set; }
public virtual ICollection<CaseFile> CaseFiles { get; set; }
}
public class CaseFile
{
public int Id { get; set; }
public int FileResourceId { get; set; }
}
public class CaseloadDto
{
public int Id { get; set; }
public IEnumerable<CaseFileDto> CaseFiles { get; set; }
}
public class CaseFileDto
{
public int Id { get; set; }
public int FileResourceId { get; set; }
}
Mapping configuration
var configuration = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Caseload, CaseloadDto>();
cfg.CreateMap<CaseFile, CaseFileDto>();
});
Version:
“Automapper” = “8.0.0” (also 7.0.0 seems to be affected) “Microsoft.EntityFrameworkCore” = “2.2.0” “Microsoft.EntityFrameworkCore.Sqlite” = “2.2.0” (data provider does not seem to matter, I have reproduced on SqlServer as well)
Expected behavior
I expect to to be able to filter a projection using an enumerable property as a predicate
Actual behavior
An exception is thrown:
System.ArgumentException: Expression of type ‘System.Collections.Generic.IAsyncEnumerable1[CaseFileDto]' cannot be used for parameter of type 'System.Collections.Generic.IEnumerable
1[CaseFileDto]’ of method ‘System.Collections.Generic.List1[CaseFileDto] ToList[CaseFileDto](System.Collections.Generic.IEnumerable
1[CaseFileDto])’
Parameter name: arg0
See below for specific cases. It is a narrow issue and has workarounds.
Example program
public class Program
{
public static async Task Main(string[] args)
{
using (var connection = new SqliteConnection("DataSource=:memory:"))
{
connection.Open();
var options = new DbContextOptionsBuilder<DataContext>()
.UseSqlite(connection)
.Options;
using (var context = new DataContext(options))
{
context.Database.EnsureCreated();
}
var configuration = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Caseload, CaseloadDto>();
cfg.CreateMap<CaseFile, CaseFileDto>();
});
using (var context = new DataContext(options))
{
var caseloads = context.Set<Caseload>();
var automapperMap = caseloads.ProjectTo<CaseloadDto>(configuration);
var manualMap = caseloads.Select(x => new CaseloadDto
{
Id = x.Id,
CaseFiles = x.CaseFiles.Select(y => new CaseFileDto
{
Id = y.Id,
FileResourceId = y.FileResourceId
}),
});
// this is fine:
await automapperMap.ToListAsync();
// This works:
var filteredA = manualMap.Where(x => x.CaseFiles.Any(y => y.FileResourceId > 0)); // silly predicate needed to trigger issue
await filteredA.ToListAsync();
// this works as well:
var filteredB = automapperMap.Where(x => x.CaseFiles.Any(y => y.FileResourceId > 0));
filteredB.ToList();
// this does not:
var filteredC = automapperMap.Where(x => x.CaseFiles.Any(y => y.FileResourceId > 0));
try
{
await filteredC.ToListAsync(); // this line specifically causes the issue
throw new Exception("we never get here");
}
catch (ArgumentException e)
{
Console.WriteLine(e);
}
}
connection.Close();
}
}
}
public class Caseload
{
public int Id { get; set; }
public virtual ICollection<CaseFile> CaseFiles { get; set; }
}
public class CaseFile
{
public int Id { get; set; }
public int FileResourceId { get; set; }
}
public class CaseloadDto
{
public int Id { get; set; }
public IEnumerable<CaseFileDto> CaseFiles { get; set; }
}
public class CaseFileDto
{
public int Id { get; set; }
public int FileResourceId { get; set; }
}
public class DataContext : DbContext
{
public DataContext(DbContextOptions options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Caseload>();
modelBuilder.Entity<CaseFile>();
}
}
Issue Analytics
- State:
- Created 5 years ago
- Comments:7 (3 by maintainers)
I fiddled around with this and you are right - EF Core does not like filtering things that are not entities. Attempting to do so results in errors when attempting to eager load (like you showed with the ToList example / my original case). Or it results in a select N+1 scenario with the case I included that did not have the ToList (in the cases of “filteredA.ToList()” and “filteredB.ToList()”)
Having worked on far more EF6 than EF Core I think I took for granted parity between the way queryables worked. I even had to go back and ensure I wasn’t going crazy thinking this worked fine in EF6 (it does, +1 sanity)
Your suggestion to use expression mapping works exactly like I hoped e.g.
caseloads.UseAsDataSource(configuration).For<CaseloadDto>(configuration).AsQueryable().Where(x => x.CaseFiles.Any(y => y.FileResourceId > 0))
The above produces reasonable SQL and no errors. The only downside is that it doesn’t support async but that’s really not critical (at least for me).
Thanks for the time, patience, and help 👍
No, you missed the ToList there.