EF Core 初学者,请耐心等待。我们正在尝试将现有应用程序从 NHibernate 迁移到 EF Core,但我们面临着几个有趣的问题。其中之一与单向多对多关系有关。
根据文档,EF 支持单向多对多关系。不幸的是,我在尝试配置它时遇到了一些问题。这是一个片段,展示了我们需要映射到表中的类:
// this is the class that has the reference to the other entity
public class GuiaAbate : Entity
{
private IList<Equipamento> _equipamentos = new List<Equipamento>( );
// other properties/methods removed
// unidirectional navigation to many
public IEnumerable<Equipamento> Equipamentos {
get => new ReadOnlyCollectionBuilder<Equipamento>(_equipamentos);
internal set => _equipamentos = value.ToList( );
}
}
// even though I believe it's not that important, here's the other class
// this is an abstract class since there are several derived classes
// for this type, all derived types are mapped to the same table
public abstract class Equipamento : Entity, IVersionamento
{
// it has some properties and collections which reference other entiites
// however, there's nothing point back to GuiaAbate (ie, no IEnumerable<GuiaAbate> prop)
// because we're only interested in the other side of the navigation
}
我们无法更改数据库表,所以现在的样子:
CREATE TABLE [dbo].[GuiaAbate](
[IDGuiaAbate] [int] IDENTITY(1,1) NOT NULL,
[Data] [datetime] NOT NULL,
[Login] [varchar](50) NOT NULL,
[LoginAD] [int] NOT NULL,
CONSTRAINT [PK_GuiaAbate] PRIMARY KEY CLUSTERED
(
[IDGuiaAbate] ASC
) ) ON [PRIMARY]
CREATE TABLE [dbo].[Equipamento](
[IDEquipamento] [int] IDENTITY(1,1) NOT NULL,
/* other fields removed */
CONSTRAINT [PK_Equipamento] PRIMARY KEY CLUSTERED
(
[IDEquipamento] ASC
)) ON [PRIMARY]
CREATE TABLE [dbo].[GuiaAbateEquipamento](
[IDGuiaAbateEquipamento] [bigint] IDENTITY(1,1) NOT NULL,
[IDGuiaAbate] [int] NOT NULL,
[IDEquipamento] [int] NOT NULL,
CONSTRAINT [PK_GuiaAbateEquipamento] PRIMARY KEY CLUSTERED
(
[IDGuiaAbateEquipamento] ASC
)) ON [PRIMARY]
如你所见,没什么太花哨的。
现在,由于我无法重用默认约定,因此我尝试了以下
GuiaAbate
类型的映射:
public sealed class GuiaAbateTypeConfiguration: IEntityTypeConfiguration<GuiaAbate>
{
public void Configure(EntityTypeBuilder<GuiaAbate> builder)
{
builder.ToTable("GuiaAbate");
builder.HasKey(g => g.Id);
builder.Property(g => g.Id).HasColumnName("IdGuiaAbate");
builder.Property(g => g.Data)
.HasConversion(d => d,
d => new DateTime(d.Ticks, DateTimeKind.Utc));
builder.Property(g => g.Data)
.HasConversion(
d => d,
d => new DateTime(d.Ticks, DateTimeKind.Utc));
builder.Property(g => g.Tecnico).HasColumnName("Login");
builder.HasMany(g => g.Equipamentos)
.WithMany( )
.UsingEntity(
"GuiaAbateEquipamento",
r => r.HasOne(typeof( Equipamento )).WithMany( ).HasForeignKey("IdEquipamento"),
l => l.HasOne(typeof( GuiaAbate )).WithMany( ).HasForeignKey("IdGuiaAbate"));
}
}
这个想法是指定连接表的名称以及应用作该表上的外键的列的名称。不幸的是,我一定在这里遗漏了一些东西,因为我最终得到了一个错误,说明了这一点
The skip navigation 'Equipamento.GuiaTransporte' doesn't have a foreign key associated with it. Every skip navigation must have a configured foreign key.
有人可以帮忙吗?
谢谢。
你有一张桌子
GuiaAbate设备
虽然这张表上有 2 个“FK”列....
你也有
IDGuiaAbateEquipamento
虽然看起来微不足道,但这是“关系上的财产”。
..
因此,您的设计需要第三个实体..来捕获关系。
我将展示一个通用示例。
public partial class EmployeeEntity
{
public long EmployeeKey { get; set; } /* PK */
public string LastName { get; set; }
/* Navigation */
public ICollection<EmployeeToJobTitleLinkEntity> EmployeeToJobTitleLinks { get; set; } = new List<EmployeeToJobTitleLinkEntity>();
}
和
public partial class JobTitleEntity
{
public long JobTitleKey { get; init; } /* PK */
public string JobTitleName { get; init; }
public string JobTitleDescription { get; init; }
/* Navigation */
public ICollection<EmployeeToJobTitleLinkEntity> EmployeeToJobTitleLinks { get; set; } = new List<EmployeeToJobTitleLinkEntity>();
}
和
public class EmployeeToJobTitleLinkEntity
{
public long EmployeeToJobTitleLinkKey { get; set; } /* PK */
public long EmployeeKey { get; set; } /* FK as scalar (long) */
public long JobTitleKey { get; set; } /* FK as scalar (long) */
/* navigation properties */
public EmployeeEntity Employee { get; set; }
public JobTitleEntity JobTitle { get; set; }
}
请注意,我有一个专用的“EntityObject”(在我的通用示例中为 EmployeeToJobTitleLinkEntity),用于处理关系。
这是“地图”。 (我使用 Fluent)。
“EmployeeMap”(未显示)和“JobTitleMap”很简单,正如您所期望的那样。
更重要的地图如下所示。
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
public class EmployeeToJobTitleLinkMap : IEntityTypeConfiguration<EmployeeToJobTitleLinkEntity>
{
public void Configure(EntityTypeBuilder<EmployeeToJobTitleLinkEntity> builder)
{
builder.ToTable("EmployeeToJobTitleLink", "dbo");
builder.HasKey(k => k.EmployeeToJobTitleLinkKey);
builder.Property(cn => cn.EmployeeToJobTitleLinkKey)
.HasColumnName("EmployeeToJobTitleLinkKey");
builder.Property(req => req.EmployeeToJobTitleLinkKey).IsRequired();
builder.Property(cn => cn.EmployeeKey).HasColumnName("EmployeeKey");
builder.Property(req => req.EmployeeKey).IsRequired();
builder.Property(cn => cn.JobTitleKey).HasColumnName("JobTitleKey");
builder.Property(req => req.JobTitleKey).IsRequired();
builder.HasOne(ent => ent.Employee)
.WithMany(c => c.EmployeeToJobTitleLinks)
.HasForeignKey(chd => chd.EmployeeKey)
.HasConstraintName(
"Your_Custom_Name_1");
builder.HasOne(ent => ent.JobTitle)
.WithMany(c => c.EmployeeToJobTitleLinks)
.HasForeignKey(chd => chd.JobTitleKey)
.HasConstraintName("Your_Custom_Name_2");
}
}
“问题”(恕我直言)是所有学习网站。
例如:
https://www.learnentityframeworkcore.com/configuration/many-to-many-relationship-configuration
不要区分“琐碎的 M:N”和“非琐碎的、关系上的财产”M:N。
...
虽然您的“链接”表上只有指定的 PK 列,但它是关系的属性。
在我的通用示例中...假设您想在 RELATIONSHIP 上添加额外的属性。
“EmployeeStartedThisJobTitleDateOf”。
“Jose”于 2020 年 1 月 1 日开始担任开发人员。 “Jose”于 2021 年 9 月 2 日开始担任建筑师。
“Maria”于 2022 年 2 月 28 日开始担任开发人员。 “Maria”于 2023 年 7 月 14 日开始担任建筑师。
这个“开始日期”不是员工的值。 它不是职位名称上的值。
这是 M:N 关系上的值。
..
现在,您可能会想忽略链接表上的 PK 列...但是,如果您想向链接添加新列,这确实会“将您逼入墙角”,以便进行重大重构 -表。
因此,99% 的情况下,我只是继续将原始版本编码为“高级“M:N”设置”。