You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

[NotMapped]标记对象列表属性引发EF Core查询翻译报错问题

问题描述

我有如下模型:

using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
using MyProject.Class.Dto;

namespace SageApi.Model;

public partial class FArticle
{
    public string ArRef { get; set; } = null!;

    [NotMapped]
    public int? Prices { get; set; }

    public static void ConfigureModelBuilder(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<FArticle>(entity =>
        {
            entity.HasIndex(e => e.ArRef, "UKA_F_ARTICLE_AR_Ref").IsUnique();
            entity.Property(e => e.ArRef)
                .HasMaxLength(19)
                .IsUnicode(false)
                .HasColumnName("AR_Ref");
        });
    }
}

我使用HotChocolate Query通过GraphQL获取数据:

using SageApi.Model.Sage;

namespace SageApi.Services.GraphQl;
public class Query
{
    [UseOffsetPaging(MaxPageSize = 100, IncludeTotalCount = true, DefaultPageSize = 20)]
    [UseProjection]
    [UseFiltering]
    [UseSorting]
    public IEnumerable<FArticle> GetFArticles([Service] SageDbContext context) =>
        context.FArticles;
}

执行以下查询时,一切正常,prices返回值为null:

{
    fArticles(
        skip: 0
        take: 1
    ) {
        items {
            arRef
            prices
        }
    }
}

现在我创建一个Dto:

namespace SageApi.Class.Dto;

public class PriceDto
{
    public short? SomeValue { get; set; }
}

并将

[NotMapped]
    public int? Prices { get; set; }

修改为

[NotMapped]
    public IList<PriceDto>? Prices { get; set; }

此时执行查询

{
    fArticles(
        skip: 0
        take: 1
    ) {
        items {
            arRef
            prices{
                someValue
            }
        }
    }
}

会出现错误:

The LINQ expression 'p1 => new PriceDto{ SomeValue = p1.SomeValue }
' 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 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'.

为何我明明标记了[NotMapped]属性,.NET仍试图从数据库获取SomeValue?该问题在Prices为String或Int等简单类型时不会出现。我遗漏了什么?

补充说明

给PriceDto类添加NotMapped特性,无法解决问题:

using System.ComponentModel.DataAnnotations.Schema;

namespace SageApi.Class.Dto;

[NotMapped]
public class PriceDto
{
    public short? SomeValue { get; set; }
}

在ModelBuilder中添加entity.Ignore(e => e.Prices);,同样无法解决问题:

using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
using MyProject.Class.Dto;

namespace SageApi.Model;

public partial class FArticle
{
    public string ArRef { get; set; } = null!;

    [NotMapped]
    public IList<PriceDto>? Prices { get; set; }

    public static void ConfigureModelBuilder(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<FArticle>(entity =>
        {
            entity.HasIndex(e => e.ArRef, "UKA_F_ARTICLE_AR_Ref").IsUnique();
            entity.Property(e => e.ArRef)
                .HasMaxLength(19)
                .IsUnicode(false)
                .HasColumnName("AR_Ref");
            entity.Ignore(e => e.Prices);
        });
    }
}

补充说明2

已提供最小可复现示例


问题根源

问题出在HotChocolate的[UseProjection]特性和EF Core的查询翻译逻辑的交互上:

  • 对于int?这类简单类型的[NotMapped]属性,EF Core明确知道该字段不存在于数据库,HotChocolate在生成投影时,只会直接返回属性的默认值(比如null),不会触发EF的SQL翻译。
  • 当属性变成IList<PriceDto>这种复杂类型时,HotChocolate会生成一个投影表达式,试图从FArticle实体中构造PriceDto对象。但EF Core无法识别这个表达式是针对[NotMapped]属性的,仍然尝试将整个投影逻辑转换成SQL查询——而PriceDto的SomeValue字段根本不存在于数据库表中,自然会导致翻译失败。

[NotMapped]和entity.Ignore(e => e.Prices)只是告诉EF Core不要将该属性映射到数据库列,但无法阻止HotChocolate生成针对该属性的投影表达式,也无法让EF Core忽略这个投影的翻译尝试。

解决方案

1. 切换到客户端评估(简单直接,但需注意性能)

在查询方法中,先调用AsEnumerable()将EF的IQueryable转换为内存中的Enumerable,让EF先加载数据到内存,再由HotChocolate处理投影:

public IEnumerable<FArticle> GetFArticles([Service] SageDbContext context) =>
    context.FArticles.AsEnumerable();

⚠️ 注意:这样会失去服务器端分页、过滤的优势——所有符合条件的数据都会先加载到内存,数据量大时会影响性能。

2. 使用GraphQL专用DTO层(推荐)

不要直接返回EF实体,而是创建一个专门用于GraphQL的DTO,在服务层完成实体到DTO的映射:

  • 定义GraphQL DTO:
public class FArticleGraphDto
{
    public string ArRef { get; set; } = null!;
    public IList<PriceDto>? Prices { get; set; }
}
  • 修改查询方法:
[UseOffsetPaging(MaxPageSize = 100, IncludeTotalCount = true, DefaultPageSize = 20)]
[UseProjection]
[UseFiltering]
[UseSorting]
public IEnumerable<FArticleGraphDto> GetFArticles([Service] SageDbContext context) =>
    context.FArticles.Select(a => new FArticleGraphDto
    {
        ArRef = a.ArRef,
        Prices = null // 根据需求设置默认值或自定义逻辑
    });

这样HotChocolate操作的是DTO,EF只会翻译针对FArticle的必要查询,不会涉及Prices属性的数据库操作。

3. 忽略GraphQL中的该属性投影

如果不需要查询Prices属性,可以在该属性上添加[GraphQLIgnore]特性,阻止HotChocolate为其生成投影:

[NotMapped]
[GraphQLIgnore]
public IList<PriceDto>? Prices { get; set; }

但这样就无法在GraphQL查询中获取该字段了,适合不需要暴露该属性的场景。

4. 自定义投影处理(进阶)

通过HotChocolate的投影扩展,自定义Prices属性的投影逻辑,告诉它直接返回默认值而不触发EF的查询:

可以使用[Projection]特性指定自定义表达式,确保EF不会尝试翻译该部分逻辑:

[NotMapped]
[Projection("null")]
public IList<PriceDto>? Prices { get; set; }

内容的提问来源于stack exchange,提问作者Lenny4

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 15:17:02