【问题标题】:The EF Core child entity isn't loaded along with the parentEF Core 子实体未与父实体一起加载
【发布时间】:2021-02-16 06:56:36
【问题描述】:

数据库

我正在使用带有两个表的 SQL Server 数据库:INVOICE_HEADERINVOICE_ROWINVOICE_HEADER 有两个字段:

  • INTERNAL_ID,输入int 和主键,
  • INV_NUM,键入 varchar(13),每个业务逻辑是唯一的,但不是每个数据库结构。

INVOICE_ROW 有三个:

  • INTERNAL_ID,输入int 和主键,
  • INV_NUM,输入varchar(13)
  • INV_ROW_NUM,输入 int,每个业务逻辑也是唯一的。

来自INVOICE_ROW 的行与来自INVOICE_HEADER 的行具有相同值的INV_NUM 在业务逻辑术语中构成相同的发票,因此INVOICE_HEADER.INV_NUM 和(INVOICE_ROW.INV_Num, INVOICE_ROW.INV_ROW_NUM) 用作de就我们的查询而言,事实主键。

INVOICE_HEADER.INTERNAL_ID 中找到的值与在INVOICE_ROW.INTERNAL_ID 中找到的值不同,并且任何一个表中的字段似乎都没有与另一个表中的INTERNAL_ID 对应的值。

因此没有实际的外键将任何一个表链接到另一个表。

我需要提到数据库来自第三方,它的结构是固定的,它有更多的表,INVOICE_HEADERINVOICE_ROW 都有更多的字段?无论如何,我刚刚做到了。

实际上,INTERNAL_ID 甚至几乎没有出现在描述数据库结构的文档中,逐个字段,并且仅用于其他几个表。

使用 Entity Framework Core 访问数据库

由于上述原因,我无法使用数据库中定义的主键。

所以我创建了两个类:

    public class InvoiceHeader
    {
        public int InternalId { get; set; }
        public string InvoiceNum { get; set; }

        public virtual List<InvoiceRow> InvoiceRows { get; set; }
    }

    public class InvoiceRow
    {
        public int InternalId { get; set; }
        public string InvoiceNum { get; set; }
        public int? RowNum { get; set; }

        public virtual InvoiceHeader InvHeader { get; set; }
    }

还有一个上下文:

        public DbSet<InvoiceHeader> RepoInvHeader { get; set; }
        public DbSet<InvoiceRow> RepoInvRow { get; set; }

        protected override void OnModelCreating(ModelBuilder p_mbuModel)
        {
            base.OnModelCreating(p_mbuModel);

            // Table name
            p_mbuModel.Entity<InvoiceHeader>().ToTable("INVOICE_HEADER");

            // Primary key
            p_mbuModel.Entity<InvoiceHeader>().HasKey(t => new { t.InternalId }).
                                               HasName("PK_INTERNAL_ID_INVOICE_HEADER");

            // Fields
            p_mbuModel.Entity<InvoiceHeader>().Property(t => t.InternalId).IsRequired();
            p_mbuModel.Entity<InvoiceHeader>().Property(t => t.InternalId).HasColumnType("int");
            p_mbuModel.Entity<InvoiceHeader>().Property(t => t.InvoiceNum).HasColumnName("INV_NUM");
            p_mbuModel.Entity<InvoiceHeader>().Property(t => t.InvoiceNum).HasColumnType("varchar(13)");
            p_mbuModel.Entity<InvoiceHeader>().Property(t => t.InvoiceNum).HasMaxLength(13);

            // Foreign keys
            p_mbuModel.Entity<InvoiceHeader>().HasMany(h => h.InvoiceRows).
                                               WithOne(r => r.InvHeader).
                                               HasPrincipalKey(h => h.InvoiceNum).
                                               HasForeignKey(r => r.InvoiceNum);

            // Table name
            p_mbuModel.Entity<InvoiceRow>().ToTable("INVOICE_ROW");

            // Primary key
            p_mbuModel.Entity<InvoiceRow>().HasKey(t => new { t.InternalId }).
                                            HasName("PK_INTERNAL_ID_INVOICE_ROW");

            // Fields
            p_mbuModel.Entity<InvoiceRow>().Property(t => t.InternalId).IsRequired();
            p_mbuModel.Entity<InvoiceRow>().Property(t => t.InternalId).HasColumnType("int");
            p_mbuModel.Entity<InvoiceRow>().Property(t => t.InvoiceNum).HasColumnName("INV_NUM");
            p_mbuModel.Entity<InvoiceRow>().Property(t => t.InvoiceNum).HasColumnType("varchar(13)");
            p_mbuModel.Entity<InvoiceRow>().Property(t => t.RowNum).HasColumnName("INV_ROW_NUM");
            p_mbuModel.Entity<InvoiceRow>().Property(t => t.RowNum).HasColumnType("int");

            // Foreign keys
            p_mbuModel.Entity<InvoiceRow>().HasOne(r => r.InvHeader).
                                            WithMany(h => h.InvoiceRows).
                                            HasForeignKey(r => r.InvoiceNum).
                                            HasPrincipalKey(h => h.InvoiceNum);
        }

我觉得所有提到 HasManyWithOne 的内容对于弥补缺乏真正可用的主键是必要的。

结果

我尝试了以下方法:

            InvoiceHeader z_objHeader = z_dbcContexteSage.repoInvHeader.Include(h => h.InvoiceRows).FirstOrDefault(h => h.Piece == "XXXX");

在调试时,z_objHeader 具有我对来自INVOICE_HEADER 的字段的预期值,但它的InvoiceRows 属性是null。 (我也试过foreach (InvoiceRow z_objRow in z_objHeader.InvoiceRows):它会抛出一个NullReferenceException。)

现在,如果我添加并运行它,

            InvoiceRow z_objRow = z_dbcContexteSage.repoInvRow.FirstOrDefault(r => r.Piece == "XXXX");

查看z_objHeader.InvoiceRows 表明它现在包含一个 InvoiceRow

跑了之后就更好了

            List<InvoiceRow> z_lisRows = z_dbcContexteSage.repoInvRow.Where(r => r.Piece == "XXXX").ToList();

z_objHeader.InvoiceRows 现在拥有所有我希望它拥有的行。

所以我可以使用它,但我仍然觉得那些从 INVOICE_ROW 显式加载的命令是不需要的,甚至是有用的。

我在网上四处张望,似乎找不到我忽略或遗漏的内容。

我想问题可能在于官方但无法使用的主键/事实上的主键。

有什么想法吗?

【问题讨论】:

  • 我认为您应该研究备用键。 docs.microsoft.com/en-us/ef/core/modeling/…
  • @jeroenh:我做到了,谢谢!在您的提示下,我再次查看,发现拆分查询可能会保留答案,但文档说它们将在 5.0 版中可用(有趣的是,以现在时态表示,而我昨天更新了 EF Core,它仍然是 3.1.9) .
  • @jeroenh:我尝试使用AsSplitQuery(),然后才知道它无法编译,因为它还不可用。它让我注意到并评论了 using System.Data.Entity; 。现在它已经完成了——猜猜看——InvoiceRows 被正确加载了!谢谢。

标签: c# sql-server entity-framework-core querying


【解决方案1】:

这不是我忘记放的东西,而是我(或其他人)在某个时候放的东西。

有一个

using System.Data.Entity;

在测试类中,这显然弄乱了Update() 方法。现在它已经消失了,属性InvoiceRows 已正确加载。

感谢所有花时间阅读问题的人!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-02-04
    • 1970-01-01
    • 2020-05-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多