如何查询在 Npgsql.EntityFrameworkCore 中具有 JSON 数组的列

How to query against a column that has a JSON Array in Npgsql.EntityFrameworkCore

备注:

我有以下 table(称为 Cars):

它有两列:

汽车 class 看起来是这样的:

public class Car
{
   public string LicenseNumber {get;set;}

   public List<Kitchen> KitchenIntegrations {get;set;}
}

Kitchen.cs 看起来像这样:

public class Kitchen
{
   public int Id {get;set;}

   public string KitchenName {get;set;}
}

最后我的 CarContext.cs 看起来像这样:

public class CarContext : DbContext
{
   public DbSet<Car> Cars { get; set; }

   public CarContext()
   {
   }

   public CarContext(DbContextOptions<CarContext> options) : base(options)
   {
   }

   protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
   {
      optionsBuilder.UseNpgsql("ConnectionStringGoesHere");
   }

   protected override void OnModelCreating(ModelBuilder modelBuilder)
   {
      modelBuilder.HasDefaultSchema("public");
      
      modelBuilder.Entity<Car>(
                builder =>
                {
                    builder.HasKey(i => i.LicenseNumber);
                    builder.Property(i => i.KitchenIntegrations).HasColumnType("jsonb").IsRequired(false);
                }
            );
        }
    }

Cars table 中,我只需要获取 ID = 1 的 KitchenIntegration。

我可以在 PSQL 中轻松做到这一点,但我在尝试查询 JSON 数组时遇到问题。

我试过了:

var integrations = context.Cars.Select(i => i.KitchenIntegrations.First(o => o.Id == 1)).ToList();

但是遇到无法翻译成 SQL/PSQL 的问题。

所以我的问题是如何在 EntityFrameworkCore 中遍历 JSON 的数组或列表? (如果可能的话,只做服务器端而不是客户端)。

谢谢!非常感谢任何帮助!

我认为这是因为 Entity Framework 核心中的 Lazy Loading

这可以用 var integrations = context.Cars.Select(c => c.KitchenIntegrations).Select(l => l.First(o => o.Id == 1)).ToList(); 来验证。它会抛出:

When called from 'VisitLambda', rewriting a node of type 'System.Linq.Expressions.ParameterExpression' must return a non-null value of the same type. Alternatively, override 'VisitLambda' and change it to not visit children of this type.

但是,我尝试 Eager Loadingvar integrations = context.Cars.Include(c => c.KitchenIntegrations).ToList();(以及 ThenInclude),但它抛出错误:

Lambda expression used inside Include is not valid.

我必须将所有 Cars 预加载到内存中才能 使其工作:

var integrations = context.Cars
                       .ToList() // Eager load to memory
                       .Select(i => i.KitchenIntegrations.First(o => o.Id == 1))
                       .ToList();

我觉得应该有更好(或正确)的方式来进行预先加载。

(当前)不支持将此翻译成 SQL - 对数据库 JSON 列的操作是有限的,请参阅 the docs 以获取支持的翻译列表。

在这种特殊情况下,尚不清楚如何(有效地)将其准确地转换为 SQL。有关类似问题,请参阅 https://github.com/npgsql/efcore.pg/issues/1534

您确实可以按照@han-zhao的建议在客户端执行投影。但是,使用 AsEnumerable 来触发客户端评估而不是 ToList:

var integrations = context.Cars
    .AsEnumerable()
    .Select(i => i.KitchenIntegrations.First(o => o.Id == 1))
    .ToList();

这确实有下载很多不需要的厨房实例的缺点,只能在客户端过滤掉它们。如果 perf-wise 有问题,请考虑使用 raw SQL(尽管同样,您想要做的事情并非微不足道)。

我的 2 美分。 假设您有一个具有以下结构的 table fixtures(Id, JsonProperty)。 假设您在数据库中有一条这样的记录。

1, [{"Name": "Test", "Value": "123"}, {"Name": "Test2", "Value": "pesho"}]
2, [{"Name": "Test", "Value": "321"}, {"Name": "Test2", "Value": "pesho"}]
3, [{"Name": "Test", "Value": "1123"}, {"Name": "Test2", "Value": "pesho"}]

然后使用 EF Core 3.1Npgsql.EntityFrameworkCore.PostgreSQ 3.14 你可以这样做:

    var search = "[{\"Value\": \"123\"}]";
    var result = dbContext.Fixtures
                    .FirstOrDefault(s => EF.Functions.JsonContains(s.JsonProperty, search));
    var search2 = "[{\"Name\": \"Test\"}]";
    var multipleResults = dbContext.Fixtures
                    .Where(s => EF.Functions.JsonContains(s.JsonProperty, search2));