简体   繁体   中英

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?

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());
                }
            }
        }
    }

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?

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");
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM