EF Core Linq to SQLite 无法翻译,可在 SQL Server 上运行

问题描述 投票:0回答:4

我有一个 linq 表达式,它在生产数据库上运行良好,但在测试上下文的内存数据库中的 SQLite 上抛出错误。我收到的错误是:

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.

linq 表达式:

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

然后我有一个预测:

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

如果我删除 SumPrice 计算,它会按预期工作(与生产系统相同)。我可以对此查询做些什么,以便在 SqlServer 和 SQLite 内存数据库上同样工作?

c# entity-framework sqlite linq entity-framework-core
4个回答
19
投票

您可能会在生产环境中使用除 sqllite 之外的某些数据库的代码。您可能不想根据对开发数据库的依赖关系来更改代码。在您的上下文类中,在 OnModelCreating 方法中添加以下代码片段并从代码中删除转换。

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

1
投票

好的,解决方案是更改我的投影类属性(SumPrice)以使用双精度而不是十进制,并将值转换为双精度:

SumPrice = entity.EffectiveScan != null
             ? entity.EffectiveScan.Items
                     .Where(x => x.Item != null)
                     .Sum(x => x.Qty * (double?)x.Item!.Price)
             : (double?)null,

我不知道是什么原因造成的。 SQLite 或其提供者是否对十进制类型有问题?


0
投票

或者你可以先查询出所有数据,然后在本地处理,而不需要翻译成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");
    }
}

0
投票

与 MS SQL Server 不同,SQLite 不支持某些数据类型,例如 Decimal、DateTime、DateTimeOffset 等。在 EF Core 中,您可以使用“约定前模型配置”方便地为给定的 CLR 类型定义映射配置:

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    if (!Database.IsSqlite()) return;

    configurationBuilder
        .Properties<decimal>()
        .HaveConversion<double>();

    configurationBuilder
        .Properties<DateTimeOffset>()
        .HaveConversion<DateTimeOffsetToBinaryConverter>();
}

https://learn.microsoft.com/en-us/ef/core/modeling/bulk-configuration#pre-convention-configuration

© www.soinside.com 2019 - 2024. All rights reserved.