EF 导航属性未加载

Posted

技术标签:

【中文标题】EF 导航属性未加载【英文标题】:EF Navigation Property Not Loading 【发布时间】:2016-12-21 08:01:42 【问题描述】:

我有以下型号 - PersonAddress

Person 可以在没有Address 的情况下存在 Address 始终属于Person

类:

public class Person     
        // properties

        [ForeignKey("Address")]
        public int? AddressId  get; set; 
        public virtual Address Address  get; set; 


public class Address 
        // properties

        [ForeignKey("Person")]
        public int PersonId  get; set; 
        public virtual Person Person  get; set; 

人员配置

HasOptional(a => a.Address)
                .WithMany()
                .HasForeignKey(u => u.AddressId);

地址配置

HasRequired(a => a.Person)
            .WithMany()
            .HasForeignKey(u => u.PersonId);

问题

SSMS 显示所有 FK 和约束都符合预期。但是,当我执行以下操作时:

var dbPerson = db.Persons.Include(s => s.Address).ToList();

返回的Person 对象(具有地址的对象)均未填充AddressAddressId。一切都是空的。

当我对db.Address 执行相同操作时,我会按预期填充所有属性 - 有效关系保持不变。是什么导致我的 1:1 可选关系的主体端不引入依赖实体?

我应该注意,我确实需要在上面定义的两个实体上都可以访问 FK ID

【问题讨论】:

使用流利的或注释。代码看起来不错。应该工作。 【参考方案1】:

让我告诉你,One-One/Optional 关系不是这样建立的。我正在分享如何建立 1:1/0 关系的代码。 此外,当您使用 fluent API 时,无需使用 Data Annotation 属性。只使用其中一种,流畅的API更好,因为关系看起来很清晰。

在 1:1/0 关系中,外键不单独定义。外键只在任何一张表中定义,一个实体的主键成为另一个相关实体的主键和外键。在此示例中,我将 Id 字段作为 Person 实体(表)的主键,并将 Id 作为地址实体(表)的主键和外键。这是 1:1/0 关系的正确方式。如果我们不遵守这个约定,关系就不能正常建立并面临问题。

这里是代码

public class Person

    // properties
    public int Id  get; set; 
    public string Name  get; set; 

    public virtual Address Address  get; set; 


public class Address

    // properties
    public int Id  get; set; 
    public string Location  get; set; 

    public virtual Person Person  get; set; 


public class PersonConfiguration : EntityTypeConfiguration<Person>

    public PersonConfiguration()
    
        ToTable("Person");
        HasKey(p => p.Id);

    


public class AddressConfiguration : EntityTypeConfiguration<Address>

    public AddressConfiguration()
    
        ToTable("Address");
        HasKey(p => p.Id);
        Property(a => a.Id).HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.None);

        HasRequired(p => p.Person)
            .WithOptional(a => a.Address);
    


public class AppObjectContext : DbContext

    public AppObjectContext() : base("AppConnectionString")
    

    

    public DbSet<Person> People  get; set; 
    public DbSet<Address> Addresses  get; set; 

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    
        base.OnModelCreating(modelBuilder);

        modelBuilder.Configurations.Add(new PersonConfiguration());
        modelBuilder.Configurations.Add(new AddressConfiguration());
    

这是最终的屏幕截图

在截图中可以看到,由于映射关系,我们可以从 Person 实例访问 Address 实例,从 Address 实例访问 Person 实例。

这是我放在表格中的数据。

这是表格结构

人员表 SQL 脚本

CREATE TABLE [dbo].[Person](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [Name] [nvarchar](max) NULL,
 CONSTRAINT [PK_dbo.Person] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

地址表 SQL 脚本

CREATE TABLE [dbo].[Address](
    [Id] [int] NOT NULL,
    [Location] [nvarchar](max) NULL,
 CONSTRAINT [PK_dbo.Address] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

ALTER TABLE [dbo].[Address]  WITH CHECK ADD  CONSTRAINT [FK_dbo.Address_dbo.Person_Id] FOREIGN KEY([Id])
REFERENCES [dbo].[Person] ([Id])
GO

ALTER TABLE [dbo].[Address] CHECK CONSTRAINT [FK_dbo.Address_dbo.Person_Id]
GO

回应您的说明:

我应该注意,我确实需要两个实体都可以访问的 FK ID,因为 上面定义的。

这违反了1:1/0关系的惯例,但更好的方法如下。

在一对一关系中,外键和主键值相同,也就是说,如果你访问一个实体的主键实体,它也是另一个实体的外键和主键。

比如person的主键是20,那么这个人映射的地址的外键和主键也是20,这样才是正确的访问方式。

【讨论】:

【参考方案2】:
class Program

    static void Main(string[] args)
    
        using (var db = new NavigationContext())
        
            Console.Write("Enter address: ");
            var addr = Console.ReadLine();

            Console.Write("Enter person: ");
            var prs = Console.ReadLine();

            Address address = new Address  Name = addr ;
            db.Addresses.Add(address);

            Person person = new Person  Name = prs, AddressID = address.AddressID ;
            db.Persons.Add(person);
            db.SaveChanges();

            Console.WriteLine("Press any key to exit...");
            Console.ReadKey();
         
    



[Table("Person")]
public class Person

    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int PersonID  get; set; 
    public string Name  get; set; 
    public int? AddressID  get; set; 
    //[ForeignKey("AddressID")]
    //public virtual Address Address  get; set; 


[Table("Address")]
public class Address

    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int AddressID  get; set; 
    public string Name  get; set; 
    public int PersonID  get; set; 
    [ForeignKey("PersonID")]
    public virtual Person Person  get; set; 




public class NavigationContext : DbContext

    public NavigationContext()
        : base("SQLDBConnection")
    

    
    public DbSet<Person> Persons  get; set; 
    public DbSet<Address> Addresses  get; set; 

【讨论】:

以上是关于EF 导航属性未加载的主要内容,如果未能解决你的问题,请参考以下文章

EF的三种数据加载方式

在 EF6 中重新加载导航属性

无法加载EF Core中的 "一对多 "导航属性。

EF CTP5 - 强类型急切加载 - 如何包含嵌套导航属性?

EF学习笔记:读取关联数据

EF5.X Code First表关联与延迟加载