asp.net-core - 这是否有效:EF Core 3.1,多个 (0..1)-to-1 关系
问题描述
这开始是一个关于如何做到这一点的问题,我花了大部分时间在旋转,然后在尝试提出一个好问题的过程中,我想我解决了我的问题。由于我已经编写了问题和示例代码(至少五次迭代),所以无论如何我都会发布它,并问这个:
- 这听起来像一个有效的方法吗?
- 我错过了什么我会后悔的事情吗?
- 有什么简单的方法可以将 IX 放在 CountryID 上吗?这不会是歧视性的(90% 的地址将在加利福尼亚州,另外 9% 在美国,也许还有几个遥远的地址)。
它还可以帮助其他任何人搜索,因为我发现以前的答案是针对其他/旧版本的 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
我错过了什么吗?有什么建议么?
解决方案
这都是非常标准的,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 的话题
推荐阅读
- javascript - Random Transform of 3d dice- get dice throw result
- google-apps-script - 根据其他单元格值应用 ClearContents 函数
- sql-server - 查询2016年SSISDB中的SSIS配置
- regex - 正则表达式匹配可变数量的注释
- r - 如何通过使用与所需输出合并来合并 R 中的两个数据帧?
- angular - 当 TemplateRef 通过 @Input 传入时,Angular @ContentChildren QueryList 未检测到更改
- elasticsearch - 升级弹性搜索后出现结果问题
- reactjs - React Native 应用程序在启动时加载数据以供离线使用
- javascript - 在具有多个图案的图案中旋转单个图案
- c++ - 构造 std::tuple 类型的索引数组