Search code examples
c#.netdata-annotations

NotMapped Attribute is ignored when used on List of object


I have this model:

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

I'm using HotChocolate Query to get my data with 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;
}

When I launch my query:

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

It works fine prices has the value null.

Now I create a Dto:

namespace SageApi.Class.Dto;

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

and I change

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

to

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

Now when I launch the query

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

I got error:

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'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.

Why does Dotnet try to get SomeValue in the database while I clearly specify the [NotMapped] attribute ? This problem doesn't occur when Prices is a String or Int (not an object).

What am I missing here ?

Edit

Also adding NotMapped to class GrillePriceDto like so, does not solve the issue:

using System.ComponentModel.DataAnnotations.Schema;

namespace SageApi.Class.Dto;

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

Also adding entity.Ignore(e => e.Prices); in modelBuilder like so, does not solve the issue:

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

Edit 2

Minimal reproductible example: https://github.com/Lenny4/NotMappedAttributeListObject


Solution

  • It's not related to EF and based on the Hotchocolate document you need to exclude the property:

    Exclude fields: If a projected field is requested, the whole subtree is processed. Sometimes you want to opt out of projections. The projections middleware skips a field in two cases. Either the visitor encounters a field that is a UseProjection field itself, or it defines IsProjected(false).

    Link: https://chillicream.com/docs/hotchocolate/v13/fetching-data/projections#exclude-fields

    So I could run your app successfully with this config:

    public class FArticle
    {
        public int CbMarq { get; set; }
    
        [IsProjected(true)] public string ArRef { get; set; } = null!;
    
        //Exclude by using: IsProjected(false)
        [IsProjected(false)][NotMapped] public IList<PriceDto>? Prices { get; set; }
    
        public static void ConfigureModelBuilder(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<FArticle>(entity =>
            {
                entity.HasKey(e => e.CbMarq).HasName("PK_CBMARQ_F_ARTICLE");
    
                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);
            });
        }
    }