简体   繁体   English

Entity Framework 的 Eager Loading 仅填充最后一条记录

[英]Entity Framework's Eager Loading populates last record only

I have a ASP.NET Core 3.1 Web API with the following database context:我有一个具有以下数据库上下文的 ASP.NET Core 3.1 Web API:

namespace Api.Database.EF
{
    using System;
    using Microsoft.EntityFrameworkCore;
    
    using Api.Database.EF.Models;

    public class ApiDbContext : DbContext
    {
        private readonly string connectionString;
        private readonly DbContextOptions<ApiDbContext> options;

        public ApiDbContext(string connectionString)
        {
            if (string.IsNullOrEmpty(connectionString))
            {
                throw new ArgumentNullException(
                    nameof(connectionString),
                    "Connection string cannot be null or empty.");
            }

            this.connectionString = connectionString;
        }

        public ApiDbContext(DbContextOptions<ApiDbContext> options)
        {
            this.options = options;
        }

        public DbSet<SearchFilter> SearchFilter { get; set; }

        public DbSet<SearchFilterData> SearchFilterData { get; set; }

        public DbSet<FilterStatus> FilterStatus { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<SearchFilterData>()
                .HasOne<SearchFilter>(d => d.SearchFilter)
                .WithMany(f => f.SearchFilterData)
                .HasForeignKey(f => f.SearchFilterId);

            modelBuilder.Entity<SearchFilterData>()
                .HasOne<FilterStatus>(x => x.Status)
                .WithOne(x => x.SearchFiltersData)
                .HasForeignKey<SearchFilterData>(x => x.StatusId);

            modelBuilder.Entity<SearchFilterData>()
                .Property(p => p.CreatedAt)
                .HasDefaultValueSql("CURRENT_TIMESTAMP()");
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!string.IsNullOrEmpty(this.connectionString))
            {
                optionsBuilder.UseMySQL(this.connectionString);
            }
        }
    }
}

And the following 3 models:以及以下3种型号:

namespace Api.Database.EF.Models
{
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Collections.Generic;
    using Newtonsoft.Json;
    
    [Table("search_filter_status")]
    public class FilterStatus
    {
        [Key]
        [Column("id")]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }

        [Column("name")]
        public string Name { get; set; }

        [JsonIgnore]
        public virtual SearchFilterData SearchFiltersData { get; set; }
    }
}
#nullable enable
namespace Api.Database.EF.Models
{
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    using Newtonsoft.Json;

    [Table("search_filter")]
    public class SearchFilter
    {
        [Key]
        [Column("id")]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }

        [Required]
        [Column("guid")]
        public string Guid { get; set; }

        [Required]
        [Column("user")]
        public string User { get; set; }

        [Column("name")]
        public string? Name { get; set; }

        [Column("description")]
        public string? Description { get; set; }
        
        [JsonProperty("data")]
        public virtual List<SearchFilterData> SearchFilterData { get; set; }
    }
}
namespace Api.Database.EF.Models
{
    using System;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    using Newtonsoft.Json;

    [Table("search_filter_data")]
    public class SearchFilterData
    {
        [Key]
        [Column("id")]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }

        [Column("search_filter_id")]
        public int SearchFilterId { get; set; }

        [Column("created")]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public DateTimeOffset CreatedAt { get; set; }

        [Column("status_id")]
        public int StatusId { get; set; }

        [Column("data")]
        public string FilterData { get; set; }

        public virtual FilterStatus Status { get; set; }
        
        [JsonIgnore]
        public virtual SearchFilter SearchFilter { get; set; }
    }
}

This is how I try to use it:这就是我尝试使用它的方式:

public List<SearchFilterData> GetFiltersForUser(string username)
{
    return this.apiDbContext
        .SearchFilterData
        .Where(x => x.SearchFilter.User == username)
        .Include(x => x.Status)
        .ToList();
}

The issue I'm facing here is that only the last item has a value it's Status property.我在这里面临的问题是,只有最后一项具有其 Status 属性的值。 Here is a sample response:这是一个示例响应:

[
    {
        "Id": 1,
        "SearchFilterId": 1,
        "CreatedAt": "2022-07-14T13:16:43+03:00",
        "StatusId": 1,
        "FilterData": "{}",
        "Status": null
    },
    {
        "Id": 2,
        "SearchFilterId": 1,
        "CreatedAt": "2022-07-14T14:04:33+03:00",
        "StatusId": 1,
        "FilterData": "some_data",
        "Status": null
    },
    {
        "Id": 3,
        "SearchFilterId": 1,
        "CreatedAt": "2022-07-14T14:05:17+03:00",
        "StatusId": 1,
        "FilterData": "some_other_data",
        "Status": {
            "Id": 1,
            "Name": "ACTIVE"
        }
    }
]

No matter how many items I add, it's always the last one only that has the Status object inside it.无论我添加多少项目,它总是只有最后一个在其中包含 Status 对象。 Any suggestions where the issue might come from?问题可能来自哪里的任何建议?

You have duplicate StatusId values in SearchFilterData , which is against the configured one-to-one relationship (one-to-one means the StatusId is unique in the referencing side as well, ie in SearchFilterData ), hence the results you are getting from EF Core.您在SearchFilterData中有重复的StatusId值,这与配置的一对一关系背道而驰(一对一意味着StatusId在引用端也是唯一的,即在SearchFilterData中),因此您从 EF 获得的结果核。

The database tables and the data itself imply that the relationship between FilterStatus and SearchFilterData is one-to- many , like the other one.数据库表和数据本身意味着FilterStatusSearchFilterData之间的关系是一对多的,就像另一个一样。 So in order to fix the problem, you need to fix your model and fluent configuration, because EF Core behaviors heavily rely on that, and not what is really in the database, so these must be in sync.因此,为了解决问题,您需要修复模型和流畅的配置,因为 EF Core 行为严重依赖于此,而不是数据库中的真实内容,因此这些必须保持同步。

So, in FilterStatus所以,在FilterStatus

[JsonIgnore]
public virtual SearchFilterData SearchFiltersData { get; set; }

becomes变成

[JsonIgnore]
public virtual ICollection<SearchFilterData> SearchFiltersData { get; set; }

or List or any IEnumerable<SearchFilterData> type.List或任何IEnumerable<SearchFilterData>类型。

And the fluent configuration for the relationship is changed to (or just removed since the reference navigation and FK property names match the EF Core conventions):并且关系的流利配置更改为(或只是删除,因为参考导航和 FK 属性名称与 EF Core 约定匹配):

modelBuilder.Entity<SearchFilterData>()
    .HasOne(x => x.Status)
    .WithMany(x => x.SearchFiltersData) // <--
    .HasForeignKey(x => x.StatusId);


PS Note that if you have used EF Core migration to create database tables from the model, it would have created unique constraint on StatusId column, so you won't be able to input duplicate values and exhibit the issue in question. PS 请注意,如果您使用 EF Core 迁移从模型创建数据库表,它将在StatusId列上创建唯一约束,因此您将无法输入重复值并显示相关问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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