首页 > 解决方案 > 这是否有效:EF Core 3.1,多个 (0..1)-to-1 关系

问题描述

这开始是一个关于如何做到这一点的问题,我花了大部分时间在旋转,然后在尝试提出一个好问题的过程中,我想我解决了我的问题。由于我已经编写了问题和示例代码(至少五次迭代),所以无论如何我都会发布它,并问这个:

它还可以帮助其他任何人搜索,因为我发现以前的答案是针对其他/旧版本的 EF 或不同的场景(例如使用帐单和送货地址)。

--

这是问题以及我认为我找到的解决方案:

我想对邮寄地址和使用它们的其他实体之间的零或一关系建模。

鉴于:

我结束了这些课程:

public class PostalAddress
{
    public int ID { get; set; }
    // a bunch of typical address stuff omitted.
    public string postalCode { get; set; }
    [ForeignKey("Country")]
    public string countryID { get; set; }

    [MaxLength(6)]
    [ForeignKey("Branch")]
    public string branchID { get; set; }      // This address is used by a branch

    [ForeignKey("User")]
    public int? userID { get; set; }      // This address is used by a user

    public Country? country { get; set; }     // Address has a many-to-1 to country
    public Branch? branch { get; set; }
    public User? user { get; set; }
}

public class User
{
    public int ID { get; set; }
    public string displayName { get; set; }
}

public class Branch
{
    [DisplayName("ID")]
    [MaxLength(6)]
    public string branchID { get; set; }
    public string name { get; set; }
}

public class Country
{
    [Key]
    [MaxLength(4)]
    [Required]
    [DisplayName("Country Code")]
    // countryID is the ISO Alpha 2 Code, but this name is simpler and meets EF Conventions
    public string countryID { get; set; }
    [MaxLength(50)]
    [Required]
    [DisplayName("Country")]
    public string name { get; set; }
    [Required]
    [DisplayName("Sort Order")]
    public int SortOrder { get; set; }
    [MaxLength(4)]
    [DisplayName("ISO Alpha-3 Code")]
    public string A3 { get; set; }
    [MaxLength(60)]
    [DisplayName("English Common Name")]
    public string name_en { get; set; }
    [MaxLength(60)]
    [DisplayName("French Common Name")]
    public string name_fr { get; set; }
    [DisplayName("ISO Numeric Code")]
    public int numeric { get; set; }
    [MaxLength(70)]
    [DisplayName("English Formal Name")]
    public string formal_name_en { get; set; }
    [MaxLength(70)]
    [DisplayName("English Short Name")]
    public string short_name_en { get; set; }
    [MaxLength(70)]
    [DisplayName("French Formal Name")]
    public string formal_name_fr { get; set; }
    [MaxLength(30)]
    [DisplayName("Capital")]
    public string Capital { get; set; }
    [MaxLength(4)]
    [DisplayName("Internet TLD")]
    public string TLD { get; set; }

    public ICollection<PostalAddress> postalAddress;

}

注释和约定似乎创建了 FK 关系,但没有什么能保证它不能是一对多的。因此,添加这个 Fluent API:

protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<PostalAddress>()
            .HasOne(b => b.branch)
            .WithOne();

        modelBuilder.Entity<PostalAddress>()
            .HasOne(u => u.user)
            .WithOne();
    }

似乎已经创造了我需要的一切。SQL 具有从地址到每个其他表的 FK 和一个唯一索引以强制执行“一体性”。

CREATE TABLE [PostalAddress] (
    [ID] int NOT NULL IDENTITY,
    [postalCode] nvarchar(max) NULL,
    [countryID] nvarchar(4) NULL,
    [branchID] nvarchar(6) NULL,
    [userID] int NULL,
    CONSTRAINT [PK_PostalAddress] PRIMARY KEY ([ID]),
    CONSTRAINT [FK_PostalAddress_Branch_branchID] FOREIGN KEY ([branchID]) REFERENCES [Branch] ([branchID]) ON DELETE NO ACTION,
    CONSTRAINT [FK_PostalAddress_Country_countryID] FOREIGN KEY ([countryID]) REFERENCES [Country] ([countryID]) ON DELETE NO ACTION,
    CONSTRAINT [FK_PostalAddress_User_userID] FOREIGN KEY ([userID]) REFERENCES [User] ([ID]) ON DELETE NO ACTION
);
GO

CREATE UNIQUE INDEX [IX_PostalAddress_branchID] ON [PostalAddress] ([branchID]) WHERE [branchID] IS NOT NULL;
GO

CREATE INDEX [IX_PostalAddress_countryID] ON [PostalAddress] ([countryID]);
GO

CREATE UNIQUE INDEX [IX_PostalAddress_userID] ON [PostalAddress] ([userID]) WHERE [userID] IS NOT NULL;
GO

我错过了什么吗?有什么建议么?

标签: asp.net-coreentity-framework-corerelationship

解决方案


这都是非常标准的,EF Core 无需进行配置即可识别一对一关系(只要您定义外键),因此您不必配置一对一关系(在大多数情况下)。

您可以将实体 ID 公开为另一个实体中的外键,也可以自己定义一个自定义 ID,并使用指向导航属性的外键属性对其进行装饰。

只要您定义可为空的外键,您就可以实现“零或 1 到 1 的关系”,即主体实体可以包含依赖实体或 null。

我会在关系的两端添加导航属性,以便我可以更轻松地访问不同场景的信息。

建议您将实体 ID 命名为:PostalAddressId、BranchId...

public class User
{
    public int UserId { get; set; }
    public string displayName { get; set; }
    public virtual PostalAddress PostalAddress { get; set; }
}

public class Branch
{
    [DisplayName("ID")]
    [MaxLength(6)]
    public string BranchID { get; set; }
    public string name { get; set; }
    public virtual PostalAddress PostalAddress { get; set; }
}

而且由于您要定义一对一的地址,因此不会按设计共享地址。

编辑:

流利的 api 是必不可少的,但 ef 核心可以根据定义的外键按约定理解(标准)一对一关系,即,当您在另一个实体中定义第一个实体 id 时,ef 核心可以在不需要流利的情况下获取它api(因此仅在这种情况下不需要)但是只要您以不同的方式命名外键,您的关系就需要流畅的 api 或数据注释来指向定义的外键,因为现在您没有遵循约定你需要教 efcore 哪个是你想要的外键。

这里。这个网站有很多关于 efcore 的话题


推荐阅读