'EF Core Linq to SQLite could not be translated, works on SQL Server
I have a linq expression that is working fine on the production database but throws error on the SQLite in memory db of the test context. The error I got says:
The LINQ expression (EntityShaperExpression:
EntityType: Item
ValueBufferExpression:
(ProjectionBindingExpression: Inner)
IsNullable: True ).Price * (Nullable<decimal>)(decimal)(EntityShaperExpression:
EntityType: ISItem
ValueBufferExpression:
(ProjectionBindingExpression: Outer)
IsNullable: False ).Qty' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
The linq expression:
var locationsQuery = context.DbContext.Locations
.Include(x => x.Check)
.Include(x => x.Scan)
.Include(x => x.EScan)
.ThenInclude(es => es!.Items)
.ThenInclude(isi => isi.Item)
.Where(x => x.ProjectId == query.ProjectId)
.Select(x => x);
Then I have a projection:
LocationId = entity.Id,
LHA = entity.LHA,
Zone = entity.Zone,
Area = entity.Area,
LocationState = $"DB.{nameof(LocationState)}.{entity.State.ToString()}",
CheckUserId = entity.Check != null ? entity.Check.ScanUserId : (int?)null,
ScanUserId = entity.Scan != null ? entity.Scan.ScanUserId : (int?)null,
CheckUserName = entity.Check != null ? entity.Check.ScanUser.Name : null,
ScanUserName = entity.Scan != null ? entity.Scan.ScanUser.Name : null,
SumPrice = entity.EffectiveScan != null // This cannot be evaluated
? entity.EScan.Items
.Where(x => x.Item != null)
.Sum(x => x.Item!.Price * (decimal)x.Qty)
: null,
SumQty = entity.EScan != null
? entity.EScan.Items
.Sum(x => x.Qty)
: (double?)null
If I remove the SumPrice calculation it works as expected (as on the production system). What can I do to this query to work the same on SqlServer and SQLite In memory db?
Solution 1:[1]
You probably are going to use the code in production environment with some database other than sqllite. You may not want to change your code based on a dependency on your development database. in your context class, in OnModelCreating method add below snippet and remove casting from your code.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
if (Database.ProviderName == "Microsoft.EntityFrameworkCore.Sqlite")
{
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
var properties = entityType.ClrType.GetProperties().Where(p => p.PropertyType == typeof(decimal));
var dateTimeProperties = entityType.ClrType.GetProperties()
.Where(p => p.PropertyType == typeof(DateTimeOffset));
foreach (var property in properties)
{
modelBuilder.Entity(entityType.Name).Property(property.Name).HasConversion<double>();
}
foreach (var property in dateTimeProperties)
{
modelBuilder.Entity(entityType.Name).Property(property.Name)
.HasConversion(new DateTimeOffsetToBinaryConverter());
}
}
}
}
Solution 2:[2]
Okay, the solution was to change my projection class property (SumPrice) to use double instead of decimal and convert the value to double:
SumPrice = entity.EffectiveScan != null
? entity.EffectiveScan.Items
.Where(x => x.Item != null)
.Sum(x => x.Qty * (double?)x.Item!.Price)
: (double?)null,
I don't know what caused this. Does SQLite or its provider has problem with the decimal type?
Solution 3:[3]
Or you can query all the data first and process it locally instead of needing to translate to sql
var result = _context.Votes
.Where(x => x.Id == id)
.Select(x => new VotePublicViewModel
{
Title = x.Title,
Deadline = x.Deadline,
IsMultiple = x.IsMultiple,
IsPublish = x.IsPublish,
VoteItems = x.VoteItems
.Select(item => new VotePublicViewModel.Item
{
Id = item.Id,
ItemName = item.ItemName,
Count = item.Count,
// notice: not supported sqlite
// Percentage = item.Count == 0 ? "0%" :
// (item.Count / (decimal)x.VoteItems.Where(v => v.Count != 0).Sum(v => v.Count)).ToString("p"),
})
})
.SingleOrDefault();
if (result != null)
{
foreach (var item in result.VoteItems)
{
item.Percentage = item.Count == 0
? "0%"
: (item.Count / (decimal) result.VoteItems.Where(v => v.Count != 0).Sum(v => v.Count)).ToString("p");
}
}
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 | Iman |
| Solution 2 | Perrier |
| Solution 3 |
